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,
724 tx,
725 env.seq(alice),
726 tx.isMember(jss::Signers) ? env.current()->fees().base * 2
727 : env.current()->fees().base);
728
729 BEAST_EXPECT(result[jss::engine_result] == "tesSUCCESS");
730 BEAST_EXPECT(result[jss::engine_result_code] == 0);
731 BEAST_EXPECT(
732 result[jss::engine_result_message] ==
733 "The simulated transaction would have been applied.");
734
735 if (BEAST_EXPECT(
736 result.isMember(jss::meta) ||
737 result.isMember(jss::meta_blob)))
738 {
739 Json::Value const metadata = getJsonMetadata(result);
740
741 if (BEAST_EXPECT(
742 metadata.isMember(sfAffectedNodes.jsonName)))
743 {
744 BEAST_EXPECT(
745 metadata[sfAffectedNodes.jsonName].size() == 1);
746 auto node = metadata[sfAffectedNodes.jsonName][0u];
747 if (BEAST_EXPECT(
748 node.isMember(sfModifiedNode.jsonName)))
749 {
750 auto modifiedNode = node[sfModifiedNode];
751 BEAST_EXPECT(
752 modifiedNode[sfLedgerEntryType] ==
753 "AccountRoot");
754 auto finalFields = modifiedNode[sfFinalFields];
755 BEAST_EXPECT(finalFields[sfDomain] == newDomain);
756 }
757 }
758 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
759 BEAST_EXPECT(
760 metadata[sfTransactionResult.jsonName] == "tesSUCCESS");
761 }
762 };
763
764 Json::Value tx;
765
766 tx[jss::Account] = alice.human();
767 tx[jss::TransactionType] = jss::AccountSet;
768 tx[sfDomain] = newDomain;
769
770 // test with autofill
771 testTx(env, tx, validateOutput, false);
772
773 tx[sfSigners] = Json::arrayValue;
774 {
776 signer[jss::Account] = becky.human();
777 Json::Value signerOuter;
778 signerOuter[sfSigner] = signer;
779 tx[sfSigners].append(signerOuter);
780 }
781
782 // test with just signer accounts
783 testTx(env, tx, validateOutput, false);
784
785 tx[sfSigningPubKey] = "";
786 tx[sfTxnSignature] = "";
787 tx[sfSequence] = env.seq(alice);
788 // transaction requires a non-base fee
789 tx[sfFee] =
790 (env.current()->fees().base * 2).jsonClipped().asString();
791 tx[sfSigners][0u][sfSigner][jss::SigningPubKey] = "";
792 tx[sfSigners][0u][sfSigner][jss::TxnSignature] = "";
793
794 // test without autofill
795 testTx(env, tx, validateOutput);
796 }
797 }
798
799 void
801 {
802 testcase("Transaction with a key-related failure");
803
804 using namespace jtx;
805 Env env(*this);
806 static auto const newDomain = "123ABC";
807 Account const alice{"alice"};
808 env(regkey(env.master, alice));
809 env(fset(env.master, asfDisableMaster), sig(env.master));
810 env.close();
811
812 {
813 std::function<void(Json::Value const&, Json::Value const&)> const&
814 testSimulation =
815 [&](Json::Value const& resp, Json::Value const& tx) {
816 auto result = resp[jss::result];
818 result,
819 tx,
820 env.seq(env.master),
821 env.current()->fees().base);
822
823 BEAST_EXPECT(
824 result[jss::engine_result] == "tefMASTER_DISABLED");
825 BEAST_EXPECT(result[jss::engine_result_code] == -188);
826 BEAST_EXPECT(
827 result[jss::engine_result_message] ==
828 "Master key is disabled.");
829
830 BEAST_EXPECT(
831 !result.isMember(jss::meta) &&
832 !result.isMember(jss::meta_blob));
833 };
834
835 Json::Value tx;
836
837 tx[jss::Account] = env.master.human();
838 tx[jss::TransactionType] = jss::AccountSet;
839 tx[sfDomain] = newDomain;
840 // master key is disabled, so this is invalid
841 tx[jss::SigningPubKey] = strHex(env.master.pk().slice());
842
843 // test with autofill
844 testTx(env, tx, testSimulation);
845
846 tx[sfTxnSignature] = "";
847 tx[sfSequence] = env.seq(env.master);
848 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
849
850 // test without autofill
851 testTx(env, tx, testSimulation);
852 }
853 }
854
855 void
857 {
858 testcase(
859 "Transaction with both single-signing SigningPubKey and "
860 "multi-signing Signers");
861
862 using namespace jtx;
863 Env env(*this);
864 static auto const newDomain = "123ABC";
865 Account const alice("alice");
866 Account const becky("becky");
867 Account const carol("carol");
868 env.fund(XRP(10000), alice);
869 env.close();
870
871 // set up valid multisign
872 env(signers(alice, 1, {{becky, 1}, {carol, 1}}));
873 env.close();
874
875 {
876 std::function<void(Json::Value const&, Json::Value const&)> const&
877 testSimulation = [&](Json::Value const& resp,
878 Json::Value const& tx) {
879 auto result = resp[jss::result];
881 result,
882 tx,
883 env.seq(env.master),
884 env.current()->fees().base * 2);
885
886 BEAST_EXPECT(result[jss::engine_result] == "temINVALID");
887 BEAST_EXPECT(result[jss::engine_result_code] == -277);
888 BEAST_EXPECT(
889 result[jss::engine_result_message] ==
890 "The transaction is ill-formed.");
891
892 BEAST_EXPECT(
893 !result.isMember(jss::meta) &&
894 !result.isMember(jss::meta_blob));
895 };
896
897 Json::Value tx;
898
899 tx[jss::Account] = env.master.human();
900 tx[jss::TransactionType] = jss::AccountSet;
901 tx[sfDomain] = newDomain;
902 // master key is disabled, so this is invalid
903 tx[jss::SigningPubKey] = strHex(env.master.pk().slice());
904 tx[sfSigners] = Json::arrayValue;
905 {
907 signer[jss::Account] = becky.human();
908 Json::Value signerOuter;
909 signerOuter[sfSigner] = signer;
910 tx[sfSigners].append(signerOuter);
911 }
912
913 // test with autofill
914 testTx(env, tx, testSimulation, false);
915
916 tx[sfTxnSignature] = "";
917 tx[sfSequence] = env.seq(env.master);
918 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
919 tx[sfSigners][0u][sfSigner][jss::SigningPubKey] =
920 strHex(becky.pk().slice());
921 tx[sfSigners][0u][sfSigner][jss::TxnSignature] = "";
922
923 // test without autofill
924 testTx(env, tx, testSimulation);
925 }
926 }
927
928 void
930 {
931 testcase("Multi-signed transaction with a bad public key");
932
933 using namespace jtx;
934 Env env(*this);
935 static auto const newDomain = "123ABC";
936 Account const alice("alice");
937 Account const becky("becky");
938 Account const carol("carol");
939 Account const dylan("dylan");
940 env.fund(XRP(10000), alice);
941 env.close();
942
943 // set up valid multisign
944 env(signers(alice, 1, {{becky, 1}, {carol, 1}}));
945
946 {
947 auto validateOutput = [&](Json::Value const& resp,
948 Json::Value const& tx) {
949 auto result = resp[jss::result];
951 result, tx, env.seq(alice), env.current()->fees().base * 2);
952
953 BEAST_EXPECTS(
954 result[jss::engine_result] == "tefBAD_SIGNATURE",
955 result[jss::engine_result].toStyledString());
956 BEAST_EXPECT(result[jss::engine_result_code] == -186);
957 BEAST_EXPECT(
958 result[jss::engine_result_message] ==
959 "A signature is provided for a non-signer.");
960
961 BEAST_EXPECT(
962 !result.isMember(jss::meta) &&
963 !result.isMember(jss::meta_blob));
964 };
965
966 Json::Value tx;
967
968 tx[jss::Account] = alice.human();
969 tx[jss::TransactionType] = jss::AccountSet;
970 tx[sfDomain] = newDomain;
971 tx[sfSigners] = Json::arrayValue;
972 {
974 signer[jss::Account] = becky.human();
975 signer[jss::SigningPubKey] = strHex(dylan.pk().slice());
976 Json::Value signerOuter;
977 signerOuter[sfSigner] = signer;
978 tx[sfSigners].append(signerOuter);
979 }
980
981 // test with autofill
982 testTx(env, tx, validateOutput, false);
983
984 tx[sfSigningPubKey] = "";
985 tx[sfTxnSignature] = "";
986 tx[sfSequence] = env.seq(alice);
987 // transaction requires a non-base fee
988 tx[sfFee] =
989 (env.current()->fees().base * 2).jsonClipped().asString();
990 tx[sfSigners][0u][sfSigner][jss::TxnSignature] = "";
991
992 // test without autofill
993 testTx(env, tx, validateOutput);
994 }
995 }
996
997 void
999 {
1000 testcase("Credentials aren't actually deleted on `tecEXPIRED`");
1001
1002 // scenario setup
1003
1004 using namespace jtx;
1005 Env env(*this);
1006
1007 Account const subject{"subject"};
1008 Account const issuer{"issuer"};
1009
1010 env.fund(XRP(10000), subject, issuer);
1011 env.close();
1012
1013 auto const credType = "123ABC";
1014
1015 auto jv = credentials::create(subject, issuer, credType);
1016 uint32_t const t =
1017 env.current()->info().parentCloseTime.time_since_epoch().count();
1018 jv[sfExpiration.jsonName] = t;
1019 env(jv);
1020 env.close();
1021
1022 {
1023 auto validateOutput = [&](Json::Value const& resp,
1024 Json::Value const& tx) {
1025 auto result = resp[jss::result];
1027 result, tx, env.seq(subject), env.current()->fees().base);
1028
1029 BEAST_EXPECT(result[jss::engine_result] == "tecEXPIRED");
1030 BEAST_EXPECT(result[jss::engine_result_code] == 148);
1031 BEAST_EXPECT(
1032 result[jss::engine_result_message] ==
1033 "Expiration time is passed.");
1034
1035 if (BEAST_EXPECT(
1036 result.isMember(jss::meta) ||
1037 result.isMember(jss::meta_blob)))
1038 {
1039 Json::Value const metadata = getJsonMetadata(result);
1040
1041 if (BEAST_EXPECT(
1042 metadata.isMember(sfAffectedNodes.jsonName)))
1043 {
1044 BEAST_EXPECT(
1045 metadata[sfAffectedNodes.jsonName].size() == 5);
1046
1047 try
1048 {
1049 bool found = false;
1050 for (auto const& node :
1051 metadata[sfAffectedNodes.jsonName])
1052 {
1053 if (node.isMember(sfDeletedNode.jsonName) &&
1054 node[sfDeletedNode.jsonName]
1055 [sfLedgerEntryType.jsonName]
1056 .asString() == "Credential")
1057 {
1058 auto const deleted =
1059 node[sfDeletedNode.jsonName]
1060 [sfFinalFields.jsonName];
1061 found = deleted[jss::Issuer] ==
1062 issuer.human() &&
1063 deleted[jss::Subject] ==
1064 subject.human() &&
1065 deleted["CredentialType"] ==
1066 strHex(std::string_view(credType));
1067 break;
1068 }
1069 }
1070 BEAST_EXPECT(found);
1071 }
1072 catch (...)
1073 {
1074 fail();
1075 }
1076 }
1077 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
1078 BEAST_EXPECT(
1079 metadata[sfTransactionResult.jsonName] == "tecEXPIRED");
1080 }
1081 };
1082
1083 Json::Value tx = credentials::accept(subject, issuer, credType);
1084
1085 // test with autofill
1086 testTx(env, tx, validateOutput);
1087
1088 tx[sfSigningPubKey] = "";
1089 tx[sfTxnSignature] = "";
1090 tx[sfSequence] = env.seq(subject);
1091 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
1092
1093 // test without autofill
1094 testTx(env, tx, validateOutput);
1095 }
1096
1097 // check that expired credentials weren't deleted
1098 auto const jle =
1099 credentials::ledgerEntry(env, subject, issuer, credType);
1100 BEAST_EXPECT(
1101 jle.isObject() && jle.isMember(jss::result) &&
1102 !jle[jss::result].isMember(jss::error) &&
1103 jle[jss::result].isMember(jss::node) &&
1104 jle[jss::result][jss::node].isMember("LedgerEntryType") &&
1105 jle[jss::result][jss::node]["LedgerEntryType"] == jss::Credential &&
1106 jle[jss::result][jss::node][jss::Issuer] == issuer.human() &&
1107 jle[jss::result][jss::node][jss::Subject] == subject.human() &&
1108 jle[jss::result][jss::node]["CredentialType"] ==
1109 strHex(std::string_view(credType)));
1110
1111 BEAST_EXPECT(ownerCount(env, issuer) == 1);
1112 BEAST_EXPECT(ownerCount(env, subject) == 0);
1113 }
1114
1115 void
1117 {
1118 testcase("Successful transaction with a custom network ID");
1119
1120 using namespace jtx;
1121 Env env{*this, envconfig([&](std::unique_ptr<Config> cfg) {
1122 cfg->NETWORK_ID = 1025;
1123 return cfg;
1124 })};
1125 static auto const newDomain = "123ABC";
1126
1127 {
1128 auto validateOutput = [&](Json::Value const& resp,
1129 Json::Value const& tx) {
1130 auto result = resp[jss::result];
1132 result, tx, 1, env.current()->fees().base);
1133
1134 BEAST_EXPECT(result[jss::engine_result] == "tesSUCCESS");
1135 BEAST_EXPECT(result[jss::engine_result_code] == 0);
1136 BEAST_EXPECT(
1137 result[jss::engine_result_message] ==
1138 "The simulated transaction would have been applied.");
1139
1140 if (BEAST_EXPECT(
1141 result.isMember(jss::meta) ||
1142 result.isMember(jss::meta_blob)))
1143 {
1144 Json::Value const metadata = getJsonMetadata(result);
1145
1146 if (BEAST_EXPECT(
1147 metadata.isMember(sfAffectedNodes.jsonName)))
1148 {
1149 BEAST_EXPECT(
1150 metadata[sfAffectedNodes.jsonName].size() == 1);
1151 auto node = metadata[sfAffectedNodes.jsonName][0u];
1152 if (BEAST_EXPECT(
1153 node.isMember(sfModifiedNode.jsonName)))
1154 {
1155 auto modifiedNode = node[sfModifiedNode];
1156 BEAST_EXPECT(
1157 modifiedNode[sfLedgerEntryType] ==
1158 "AccountRoot");
1159 auto finalFields = modifiedNode[sfFinalFields];
1160 BEAST_EXPECT(finalFields[sfDomain] == newDomain);
1161 }
1162 }
1163 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
1164 BEAST_EXPECT(
1165 metadata[sfTransactionResult.jsonName] == "tesSUCCESS");
1166 }
1167 };
1168
1169 Json::Value tx;
1170
1171 tx[jss::Account] = env.master.human();
1172 tx[jss::TransactionType] = jss::AccountSet;
1173 tx[sfDomain] = newDomain;
1174
1175 // test with autofill
1176 testTx(env, tx, validateOutput);
1177
1178 tx[sfSigningPubKey] = "";
1179 tx[sfTxnSignature] = "";
1180 tx[sfSequence] = 1;
1181 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
1182 tx[sfNetworkID] = 1025;
1183
1184 // test without autofill
1185 testTx(env, tx, validateOutput);
1186 }
1187 }
1188
1189public:
1190 void
1191 run() override
1192 {
1194 testFeeError();
1205 }
1206};
1207
1208BEAST_DEFINE_TESTSUITE(Simulate, rpc, ripple);
1209
1210} // namespace test
1211
1212} // namespace ripple
Represents a JSON value.
Definition: json_value.h:149
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:122
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 testInvalidSingleAndMultiSigningTransaction()
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:254
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:788
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:275
JTx jtnofill(JsonValue &&jv, FN const &... fN)
Create a JTx from parameters.
Definition: Env.h:517
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:44
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:45
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:25
constexpr std::uint32_t tfAllOrNothing
Definition: TxFlags.h:240
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)