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("Invalid transaction type");
472
473 using namespace jtx;
474
475 Env env(*this);
476
477 Account const alice{"alice"};
478 Account const bob{"bob"};
479 env.fund(XRP(1000000), alice, bob);
480 env.close();
481
482 auto const batchFee = batch::calcBatchFee(env, 0, 2);
483 auto const seq = env.seq(alice);
484 auto jt = env.jtnofill(
485 batch::outer(alice, env.seq(alice), batchFee, tfAllOrNothing),
486 batch::inner(pay(alice, bob, XRP(10)), seq + 1),
487 batch::inner(pay(alice, bob, XRP(10)), seq + 1));
488
489 jt.jv.removeMember(jss::TxnSignature);
490 Json::Value params;
491 params[jss::tx_json] = jt.jv;
492 auto const resp = env.rpc("json", "simulate", to_string(params));
493 BEAST_EXPECT(resp[jss::result][jss::error] == "notImpl");
494 BEAST_EXPECT(
495 resp[jss::result][jss::error_message] == "Not implemented.");
496 }
497
498 void
500 {
501 testcase("Successful transaction");
502
503 using namespace jtx;
504 Env env{*this, envconfig([&](std::unique_ptr<Config> cfg) {
505 cfg->NETWORK_ID = 0;
506 return cfg;
507 })};
508 static auto const newDomain = "123ABC";
509
510 {
511 auto validateOutput = [&](Json::Value const& resp,
512 Json::Value const& tx) {
513 auto result = resp[jss::result];
515 result, tx, 1, env.current()->fees().base);
516
517 BEAST_EXPECT(result[jss::engine_result] == "tesSUCCESS");
518 BEAST_EXPECT(result[jss::engine_result_code] == 0);
519 BEAST_EXPECT(
520 result[jss::engine_result_message] ==
521 "The simulated transaction would have been applied.");
522
523 if (BEAST_EXPECT(
524 result.isMember(jss::meta) ||
525 result.isMember(jss::meta_blob)))
526 {
527 Json::Value const metadata = getJsonMetadata(result);
528
529 if (BEAST_EXPECT(
530 metadata.isMember(sfAffectedNodes.jsonName)))
531 {
532 BEAST_EXPECT(
533 metadata[sfAffectedNodes.jsonName].size() == 1);
534 auto node = metadata[sfAffectedNodes.jsonName][0u];
535 if (BEAST_EXPECT(
536 node.isMember(sfModifiedNode.jsonName)))
537 {
538 auto modifiedNode = node[sfModifiedNode];
539 BEAST_EXPECT(
540 modifiedNode[sfLedgerEntryType] ==
541 "AccountRoot");
542 auto finalFields = modifiedNode[sfFinalFields];
543 BEAST_EXPECT(finalFields[sfDomain] == newDomain);
544 }
545 }
546 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
547 BEAST_EXPECT(
548 metadata[sfTransactionResult.jsonName] == "tesSUCCESS");
549 }
550 };
551
552 Json::Value tx;
553
554 tx[jss::Account] = env.master.human();
555 tx[jss::TransactionType] = jss::AccountSet;
556 tx[sfDomain] = newDomain;
557
558 // test with autofill
559 testTx(env, tx, validateOutput);
560
561 tx[sfSigningPubKey] = "";
562 tx[sfTxnSignature] = "";
563 tx[sfSequence] = 1;
564 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
565
566 // test without autofill
567 testTx(env, tx, validateOutput);
568 }
569 }
570
571 void
573 {
574 testcase("Transaction non-tec failure");
575
576 using namespace jtx;
577 Env env(*this);
578 Account const alice("alice");
579
580 {
581 std::function<void(Json::Value const&, Json::Value const&)> const&
582 testSimulation = [&](Json::Value const& resp,
583 Json::Value const& tx) {
584 auto result = resp[jss::result];
586 result, tx, 1, env.current()->fees().base);
587
588 BEAST_EXPECT(result[jss::engine_result] == "temBAD_AMOUNT");
589 BEAST_EXPECT(result[jss::engine_result_code] == -298);
590 BEAST_EXPECT(
591 result[jss::engine_result_message] ==
592 "Malformed: Bad amount.");
593
594 BEAST_EXPECT(
595 !result.isMember(jss::meta) &&
596 !result.isMember(jss::meta_blob));
597 };
598
599 Json::Value tx;
600
601 tx[jss::Account] = env.master.human();
602 tx[jss::TransactionType] = jss::Payment;
603 tx[sfDestination] = alice.human();
604 tx[sfAmount] = "0"; // invalid amount
605
606 // test with autofill
607 testTx(env, tx, testSimulation);
608
609 tx[sfSigningPubKey] = "";
610 tx[sfTxnSignature] = "";
611 tx[sfSequence] = 1;
612 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
613
614 // test without autofill
615 testTx(env, tx, testSimulation);
616 }
617 }
618
619 void
621 {
622 testcase("Transaction tec failure");
623
624 using namespace jtx;
625 Env env(*this);
626 Account const alice("alice");
627
628 {
629 std::function<void(Json::Value const&, Json::Value const&)> const&
630 testSimulation = [&](Json::Value const& resp,
631 Json::Value const& tx) {
632 auto result = resp[jss::result];
634 result, tx, 1, env.current()->fees().base);
635
636 BEAST_EXPECT(
637 result[jss::engine_result] == "tecNO_DST_INSUF_XRP");
638 BEAST_EXPECT(result[jss::engine_result_code] == 125);
639 BEAST_EXPECT(
640 result[jss::engine_result_message] ==
641 "Destination does not exist. Too little XRP sent to "
642 "create "
643 "it.");
644
645 if (BEAST_EXPECT(
646 result.isMember(jss::meta) ||
647 result.isMember(jss::meta_blob)))
648 {
649 Json::Value const metadata = getJsonMetadata(result);
650
651 if (BEAST_EXPECT(
652 metadata.isMember(sfAffectedNodes.jsonName)))
653 {
654 BEAST_EXPECT(
655 metadata[sfAffectedNodes.jsonName].size() == 1);
656 auto node = metadata[sfAffectedNodes.jsonName][0u];
657 if (BEAST_EXPECT(
658 node.isMember(sfModifiedNode.jsonName)))
659 {
660 auto modifiedNode = node[sfModifiedNode];
661 BEAST_EXPECT(
662 modifiedNode[sfLedgerEntryType] ==
663 "AccountRoot");
664 auto finalFields = modifiedNode[sfFinalFields];
665 BEAST_EXPECT(
666 finalFields[sfBalance] ==
668 100'000'000'000'000'000 -
669 env.current()->fees().base.drops()));
670 }
671 }
672 BEAST_EXPECT(
673 metadata[sfTransactionIndex.jsonName] == 0);
674 BEAST_EXPECT(
675 metadata[sfTransactionResult.jsonName] ==
676 "tecNO_DST_INSUF_XRP");
677 }
678 };
679
680 Json::Value tx;
681
682 tx[jss::Account] = env.master.human();
683 tx[jss::TransactionType] = jss::Payment;
684 tx[sfDestination] = alice.human();
685 tx[sfAmount] = "1"; // not enough to create an account
686
687 // test with autofill
688 testTx(env, tx, testSimulation);
689
690 tx[sfSigningPubKey] = "";
691 tx[sfTxnSignature] = "";
692 tx[sfSequence] = 1;
693 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
694
695 // test without autofill
696 testTx(env, tx, testSimulation);
697 }
698 }
699
700 void
702 {
703 testcase("Successful multi-signed transaction");
704
705 using namespace jtx;
706 Env env(*this);
707 static auto const newDomain = "123ABC";
708 Account const alice("alice");
709 Account const becky("becky");
710 Account const carol("carol");
711 env.fund(XRP(10000), alice);
712 env.close();
713
714 // set up valid multisign
715 env(signers(alice, 1, {{becky, 1}, {carol, 1}}));
716 env.close();
717
718 {
719 auto validateOutput = [&](Json::Value const& resp,
720 Json::Value const& tx) {
721 auto result = resp[jss::result];
723 result, tx, env.seq(alice), env.current()->fees().base * 2);
724
725 BEAST_EXPECT(result[jss::engine_result] == "tesSUCCESS");
726 BEAST_EXPECT(result[jss::engine_result_code] == 0);
727 BEAST_EXPECT(
728 result[jss::engine_result_message] ==
729 "The simulated transaction would have been applied.");
730
731 if (BEAST_EXPECT(
732 result.isMember(jss::meta) ||
733 result.isMember(jss::meta_blob)))
734 {
735 Json::Value const metadata = getJsonMetadata(result);
736
737 if (BEAST_EXPECT(
738 metadata.isMember(sfAffectedNodes.jsonName)))
739 {
740 BEAST_EXPECT(
741 metadata[sfAffectedNodes.jsonName].size() == 1);
742 auto node = metadata[sfAffectedNodes.jsonName][0u];
743 if (BEAST_EXPECT(
744 node.isMember(sfModifiedNode.jsonName)))
745 {
746 auto modifiedNode = node[sfModifiedNode];
747 BEAST_EXPECT(
748 modifiedNode[sfLedgerEntryType] ==
749 "AccountRoot");
750 auto finalFields = modifiedNode[sfFinalFields];
751 BEAST_EXPECT(finalFields[sfDomain] == newDomain);
752 }
753 }
754 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
755 BEAST_EXPECT(
756 metadata[sfTransactionResult.jsonName] == "tesSUCCESS");
757 }
758 };
759
760 Json::Value tx;
761
762 tx[jss::Account] = alice.human();
763 tx[jss::TransactionType] = jss::AccountSet;
764 tx[sfDomain] = newDomain;
765 tx[sfSigners] = Json::arrayValue;
766 {
768 signer[jss::Account] = becky.human();
769 Json::Value signerOuter;
770 signerOuter[sfSigner] = signer;
771 tx[sfSigners].append(signerOuter);
772 }
773
774 // test with autofill
775 testTx(env, tx, validateOutput, false);
776
777 tx[sfSigningPubKey] = "";
778 tx[sfTxnSignature] = "";
779 tx[sfSequence] = env.seq(alice);
780 // transaction requires a non-base fee
781 tx[sfFee] =
782 (env.current()->fees().base * 2).jsonClipped().asString();
783 tx[sfSigners][0u][sfSigner][jss::SigningPubKey] =
784 strHex(becky.pk().slice());
785 tx[sfSigners][0u][sfSigner][jss::TxnSignature] = "";
786
787 // test without autofill
788 testTx(env, tx, validateOutput);
789 }
790 }
791
792 void
794 {
795 testcase("Transaction with a key-related failure");
796
797 using namespace jtx;
798 Env env(*this);
799 static auto const newDomain = "123ABC";
800 Account const alice{"alice"};
801 env(regkey(env.master, alice));
802 env(fset(env.master, asfDisableMaster), sig(env.master));
803 env.close();
804
805 {
806 std::function<void(Json::Value const&, Json::Value const&)> const&
807 testSimulation =
808 [&](Json::Value const& resp, Json::Value const& tx) {
809 auto result = resp[jss::result];
811 result,
812 tx,
813 env.seq(env.master),
814 env.current()->fees().base);
815
816 BEAST_EXPECT(
817 result[jss::engine_result] == "tefMASTER_DISABLED");
818 BEAST_EXPECT(result[jss::engine_result_code] == -188);
819 BEAST_EXPECT(
820 result[jss::engine_result_message] ==
821 "Master key is disabled.");
822
823 BEAST_EXPECT(
824 !result.isMember(jss::meta) &&
825 !result.isMember(jss::meta_blob));
826 };
827
828 Json::Value tx;
829
830 tx[jss::Account] = env.master.human();
831 tx[jss::TransactionType] = jss::AccountSet;
832 tx[sfDomain] = newDomain;
833
834 // test with autofill
835 testTx(env, tx, testSimulation);
836
837 tx[sfSigningPubKey] = "";
838 tx[sfTxnSignature] = "";
839 tx[sfSequence] = env.seq(env.master);
840 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
841
842 // test without autofill
843 testTx(env, tx, testSimulation);
844 }
845 }
846
847 void
849 {
850 testcase("Multi-signed transaction with a bad public key");
851
852 using namespace jtx;
853 Env env(*this);
854 static auto const newDomain = "123ABC";
855 Account const alice("alice");
856 Account const becky("becky");
857 Account const carol("carol");
858 Account const dylan("dylan");
859 env.fund(XRP(10000), alice);
860 env.close();
861
862 // set up valid multisign
863 env(signers(alice, 1, {{becky, 1}, {carol, 1}}));
864
865 {
866 auto validateOutput = [&](Json::Value const& resp,
867 Json::Value const& tx) {
868 auto result = resp[jss::result];
870 result, tx, env.seq(alice), env.current()->fees().base * 2);
871
872 BEAST_EXPECTS(
873 result[jss::engine_result] == "tefBAD_SIGNATURE",
874 result[jss::engine_result].toStyledString());
875 BEAST_EXPECT(result[jss::engine_result_code] == -186);
876 BEAST_EXPECT(
877 result[jss::engine_result_message] ==
878 "A signature is provided for a non-signer.");
879
880 BEAST_EXPECT(
881 !result.isMember(jss::meta) &&
882 !result.isMember(jss::meta_blob));
883 };
884
885 Json::Value tx;
886
887 tx[jss::Account] = alice.human();
888 tx[jss::TransactionType] = jss::AccountSet;
889 tx[sfDomain] = newDomain;
890 tx[sfSigners] = Json::arrayValue;
891 {
893 signer[jss::Account] = becky.human();
894 signer[jss::SigningPubKey] = strHex(dylan.pk().slice());
895 Json::Value signerOuter;
896 signerOuter[sfSigner] = signer;
897 tx[sfSigners].append(signerOuter);
898 }
899
900 // test with autofill
901 testTx(env, tx, validateOutput, false);
902
903 tx[sfSigningPubKey] = "";
904 tx[sfTxnSignature] = "";
905 tx[sfSequence] = env.seq(alice);
906 // transaction requires a non-base fee
907 tx[sfFee] =
908 (env.current()->fees().base * 2).jsonClipped().asString();
909 tx[sfSigners][0u][sfSigner][jss::TxnSignature] = "";
910
911 // test without autofill
912 testTx(env, tx, validateOutput);
913 }
914 }
915
916 void
918 {
919 testcase("Credentials aren't actually deleted on `tecEXPIRED`");
920
921 // scenario setup
922
923 using namespace jtx;
924 Env env(*this);
925
926 Account const subject{"subject"};
927 Account const issuer{"issuer"};
928
929 env.fund(XRP(10000), subject, issuer);
930 env.close();
931
932 auto const credType = "123ABC";
933
934 auto jv = credentials::create(subject, issuer, credType);
935 uint32_t const t =
936 env.current()->info().parentCloseTime.time_since_epoch().count();
937 jv[sfExpiration.jsonName] = t;
938 env(jv);
939 env.close();
940
941 {
942 auto validateOutput = [&](Json::Value const& resp,
943 Json::Value const& tx) {
944 auto result = resp[jss::result];
946 result, tx, env.seq(subject), env.current()->fees().base);
947
948 BEAST_EXPECT(result[jss::engine_result] == "tecEXPIRED");
949 BEAST_EXPECT(result[jss::engine_result_code] == 148);
950 BEAST_EXPECT(
951 result[jss::engine_result_message] ==
952 "Expiration time is passed.");
953
954 if (BEAST_EXPECT(
955 result.isMember(jss::meta) ||
956 result.isMember(jss::meta_blob)))
957 {
958 Json::Value const metadata = getJsonMetadata(result);
959
960 if (BEAST_EXPECT(
961 metadata.isMember(sfAffectedNodes.jsonName)))
962 {
963 BEAST_EXPECT(
964 metadata[sfAffectedNodes.jsonName].size() == 5);
965
966 try
967 {
968 bool found = false;
969 for (auto const& node :
970 metadata[sfAffectedNodes.jsonName])
971 {
972 if (node.isMember(sfDeletedNode.jsonName) &&
973 node[sfDeletedNode.jsonName]
974 [sfLedgerEntryType.jsonName]
975 .asString() == "Credential")
976 {
977 auto const deleted =
978 node[sfDeletedNode.jsonName]
979 [sfFinalFields.jsonName];
980 found = deleted[jss::Issuer] ==
981 issuer.human() &&
982 deleted[jss::Subject] ==
983 subject.human() &&
984 deleted["CredentialType"] ==
985 strHex(std::string_view(credType));
986 break;
987 }
988 }
989 BEAST_EXPECT(found);
990 }
991 catch (...)
992 {
993 fail();
994 }
995 }
996 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
997 BEAST_EXPECT(
998 metadata[sfTransactionResult.jsonName] == "tecEXPIRED");
999 }
1000 };
1001
1002 Json::Value tx = credentials::accept(subject, issuer, credType);
1003
1004 // test with autofill
1005 testTx(env, tx, validateOutput);
1006
1007 tx[sfSigningPubKey] = "";
1008 tx[sfTxnSignature] = "";
1009 tx[sfSequence] = env.seq(subject);
1010 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
1011
1012 // test without autofill
1013 testTx(env, tx, validateOutput);
1014 }
1015
1016 // check that expired credentials weren't deleted
1017 auto const jle =
1018 credentials::ledgerEntry(env, subject, issuer, credType);
1019 BEAST_EXPECT(
1020 jle.isObject() && jle.isMember(jss::result) &&
1021 !jle[jss::result].isMember(jss::error) &&
1022 jle[jss::result].isMember(jss::node) &&
1023 jle[jss::result][jss::node].isMember("LedgerEntryType") &&
1024 jle[jss::result][jss::node]["LedgerEntryType"] == jss::Credential &&
1025 jle[jss::result][jss::node][jss::Issuer] == issuer.human() &&
1026 jle[jss::result][jss::node][jss::Subject] == subject.human() &&
1027 jle[jss::result][jss::node]["CredentialType"] ==
1028 strHex(std::string_view(credType)));
1029
1030 BEAST_EXPECT(ownerCount(env, issuer) == 1);
1031 BEAST_EXPECT(ownerCount(env, subject) == 0);
1032 }
1033
1034 void
1036 {
1037 testcase("Successful transaction with a custom network ID");
1038
1039 using namespace jtx;
1040 Env env{*this, envconfig([&](std::unique_ptr<Config> cfg) {
1041 cfg->NETWORK_ID = 1025;
1042 return cfg;
1043 })};
1044 static auto const newDomain = "123ABC";
1045
1046 {
1047 auto validateOutput = [&](Json::Value const& resp,
1048 Json::Value const& tx) {
1049 auto result = resp[jss::result];
1051 result, tx, 1, env.current()->fees().base);
1052
1053 BEAST_EXPECT(result[jss::engine_result] == "tesSUCCESS");
1054 BEAST_EXPECT(result[jss::engine_result_code] == 0);
1055 BEAST_EXPECT(
1056 result[jss::engine_result_message] ==
1057 "The simulated transaction would have been applied.");
1058
1059 if (BEAST_EXPECT(
1060 result.isMember(jss::meta) ||
1061 result.isMember(jss::meta_blob)))
1062 {
1063 Json::Value const metadata = getJsonMetadata(result);
1064
1065 if (BEAST_EXPECT(
1066 metadata.isMember(sfAffectedNodes.jsonName)))
1067 {
1068 BEAST_EXPECT(
1069 metadata[sfAffectedNodes.jsonName].size() == 1);
1070 auto node = metadata[sfAffectedNodes.jsonName][0u];
1071 if (BEAST_EXPECT(
1072 node.isMember(sfModifiedNode.jsonName)))
1073 {
1074 auto modifiedNode = node[sfModifiedNode];
1075 BEAST_EXPECT(
1076 modifiedNode[sfLedgerEntryType] ==
1077 "AccountRoot");
1078 auto finalFields = modifiedNode[sfFinalFields];
1079 BEAST_EXPECT(finalFields[sfDomain] == newDomain);
1080 }
1081 }
1082 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
1083 BEAST_EXPECT(
1084 metadata[sfTransactionResult.jsonName] == "tesSUCCESS");
1085 }
1086 };
1087
1088 Json::Value tx;
1089
1090 tx[jss::Account] = env.master.human();
1091 tx[jss::TransactionType] = jss::AccountSet;
1092 tx[sfDomain] = newDomain;
1093
1094 // test with autofill
1095 testTx(env, tx, validateOutput);
1096
1097 tx[sfSigningPubKey] = "";
1098 tx[sfTxnSignature] = "";
1099 tx[sfSequence] = 1;
1100 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
1101 tx[sfNetworkID] = 1025;
1102
1103 // test without autofill
1104 testTx(env, tx, validateOutput);
1105 }
1106 }
1107
1108public:
1109 void
1110 run() override
1111 {
1113 testFeeError();
1123 }
1124};
1125
1126BEAST_DEFINE_TESTSUITE(Simulate, rpc, ripple);
1127
1128} // namespace test
1129
1130} // namespace ripple
Represents a JSON value.
Definition: json_value.h:150
Value & append(Value const &value)
Append value to array at the end.
Definition: json_value.cpp:910
UInt size() const
Number of values in array or object.
Definition: json_value.cpp:719
Value removeMember(char const *key)
Remove and return the named member.
Definition: json_value.cpp:935
std::string asString() const
Returns the unquoted string value.
Definition: json_value.cpp:482
bool isMember(char const *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:962
Value get(UInt index, Value const &defaultValue) const
If the array contains at least index+1 elements, returns the element value, otherwise returns default...
Definition: json_value.cpp:854
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=JsonOptions::none) 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:121
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:331
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:125
Application & app()
Definition: Env.h:261
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:779
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:233
JTx jtnofill(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
Definition: Env.h:508
Adds a new Batch Txn on a JTx and autofills.
Definition: batch.h:61
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:45
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:46
Json::Value outer(jtx::Account const &account, uint32_t seq, STAmount const &fee, std::uint32_t flags)
Batch.
Definition: batch.cpp:49
XRPAmount calcBatchFee(jtx::Env const &env, uint32_t const &numSigners, uint32_t const &txns=0)
Calculate Batch Fee.
Definition: batch.cpp:38
Json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: creds.cpp:32
Json::Value accept(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: creds.cpp:48
Json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: creds.cpp:78
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
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition: pay.cpp:30
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition: envconfig.h:54
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
constexpr std::uint32_t tfAllOrNothing
Definition: TxFlags.h:246
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:80
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)
Json::Value jv
Definition: JTx.h:46
Set the sequence number on a JTx.
Definition: seq.h:34
A signer in a SignerList.
Definition: multisign.h:39
T to_string(T... args)