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#include <xrpld/app/rdb/backend/SQLiteDatabase.h>
24#include <xrpld/rpc/CTID.h>
25#include <xrpl/protocol/ErrorCodes.h>
26#include <xrpl/protocol/STBase.h>
27#include <xrpl/protocol/STParsedJSON.h>
28#include <xrpl/protocol/jss.h>
29#include <xrpl/protocol/serialize.h>
30
31#include <optional>
32#include <tuple>
33
34namespace ripple {
35
36namespace test {
37
39{
40 void
42 Json::Value const& result,
43 Json::Value const& tx,
44 int const expectedSequence,
45 std::string const& expectedFee)
46 {
47 BEAST_EXPECT(result[jss::applied] == false);
48 BEAST_EXPECT(result.isMember(jss::engine_result));
49 BEAST_EXPECT(result.isMember(jss::engine_result_code));
50 BEAST_EXPECT(result.isMember(jss::engine_result_message));
51 BEAST_EXPECT(
52 result.isMember(jss::tx_json) || result.isMember(jss::tx_blob));
53
54 Json::Value tx_json;
55 if (result.isMember(jss::tx_json))
56 {
57 tx_json = result[jss::tx_json];
58 }
59 else
60 {
61 auto const unHexed = strUnHex(result[jss::tx_blob].asString());
62 SerialIter sitTrans(makeSlice(*unHexed));
63 tx_json = STObject(std::ref(sitTrans), sfGeneric)
65 }
66 BEAST_EXPECT(tx_json[jss::TransactionType] == tx[jss::TransactionType]);
67 BEAST_EXPECT(tx_json[jss::Account] == tx[jss::Account]);
68 BEAST_EXPECT(
69 tx_json[jss::SigningPubKey] == tx.get(jss::SigningPubKey, ""));
70 BEAST_EXPECT(
71 tx_json[jss::TxnSignature] == tx.get(jss::TxnSignature, ""));
72 BEAST_EXPECT(tx_json[jss::Fee] == tx.get(jss::Fee, expectedFee));
73 BEAST_EXPECT(
74 tx_json[jss::Sequence] == tx.get(jss::Sequence, expectedSequence));
75 }
76
77 void
79 Json::Value const& result,
80 Json::Value const& tx,
81 int const expectedSequence,
82 XRPAmount const& expectedFee)
83 {
85 result, tx, expectedSequence, expectedFee.jsonClipped().asString());
86 }
87
88 void
90 jtx::Env& env,
91 Json::Value const& tx,
92 std::function<void(Json::Value const&, Json::Value const&)> const&
93 validate,
94 bool testSerialized = true)
95 {
96 Json::Value params;
97 params[jss::tx_json] = tx;
98 validate(env.rpc("json", "simulate", to_string(params)), tx);
99 params[jss::binary] = true;
100 validate(env.rpc("json", "simulate", to_string(params)), tx);
101 validate(env.rpc("simulate", to_string(tx)), tx);
102 validate(env.rpc("simulate", to_string(tx), "binary"), tx);
103 if (testSerialized)
104 {
105 // This cannot be tested in the multisign autofill scenario
106 // It is technically not a valid STObject, so the following line
107 // will crash
108 STParsedJSONObject const parsed(std::string(jss::tx_json), tx);
109 auto const tx_blob =
110 strHex(parsed.object->getSerializer().peekData());
111 if (BEAST_EXPECT(parsed.object.has_value()))
112 {
113 Json::Value params;
114 params[jss::tx_blob] = tx_blob;
115 validate(env.rpc("json", "simulate", to_string(params)), tx);
116 params[jss::binary] = true;
117 validate(env.rpc("json", "simulate", to_string(params)), tx);
118 }
119 validate(env.rpc("simulate", tx_blob), tx);
120 validate(env.rpc("simulate", tx_blob, "binary"), tx);
121 }
122 }
123
126 {
127 if (txResult.isMember(jss::meta_blob))
128 {
129 auto unHexed = strUnHex(txResult[jss::meta_blob].asString());
130 SerialIter sitTrans(makeSlice(*unHexed));
131 return STObject(std::ref(sitTrans), sfGeneric)
133 }
134
135 return txResult[jss::meta];
136 }
137
138 void
140 {
141 testcase("Test parameter errors");
142
143 using namespace jtx;
144 Env env(*this);
145 Account const alice("alice");
146
147 {
148 // No params
149 Json::Value const params = Json::objectValue;
150 auto const resp = env.rpc("json", "simulate", to_string(params));
151 BEAST_EXPECT(
152 resp[jss::result][jss::error_message] ==
153 "Neither `tx_blob` nor `tx_json` included.");
154 }
155 {
156 // Providing both `tx_json` and `tx_blob`
158 params[jss::tx_json] = Json::objectValue;
159 params[jss::tx_blob] = "1200";
160
161 auto const resp = env.rpc("json", "simulate", to_string(params));
162 BEAST_EXPECT(
163 resp[jss::result][jss::error_message] ==
164 "Can only include one of `tx_blob` and `tx_json`.");
165 }
166 {
167 // `binary` isn't a boolean
169 params[jss::tx_blob] = "1200";
170 params[jss::binary] = "100";
171 auto const resp = env.rpc("json", "simulate", to_string(params));
172 BEAST_EXPECT(
173 resp[jss::result][jss::error_message] ==
174 "Invalid field 'binary'.");
175 }
176 {
177 // Invalid `tx_blob`
179 params[jss::tx_blob] = "12";
180
181 auto const resp = env.rpc("json", "simulate", to_string(params));
182 BEAST_EXPECT(
183 resp[jss::result][jss::error_message] ==
184 "Invalid field 'tx_blob'.");
185 }
186 {
187 // Empty `tx_json`
189 params[jss::tx_json] = Json::objectValue;
190
191 auto const resp = env.rpc("json", "simulate", to_string(params));
192 BEAST_EXPECT(
193 resp[jss::result][jss::error_message] ==
194 "Missing field 'tx.TransactionType'.");
195 }
196 {
197 // No tx.Account
200 tx_json[jss::TransactionType] = jss::Payment;
201 params[jss::tx_json] = tx_json;
202
203 auto const resp = env.rpc("json", "simulate", to_string(params));
204 BEAST_EXPECT(
205 resp[jss::result][jss::error_message] ==
206 "Missing field 'tx.Account'.");
207 }
208 {
209 // Empty `tx_blob`
211 params[jss::tx_blob] = "";
212
213 auto const resp = env.rpc("json", "simulate", to_string(params));
214 BEAST_EXPECT(
215 resp[jss::result][jss::error_message] ==
216 "Invalid field 'tx_blob'.");
217 }
218 {
219 // Non-string `tx_blob`
220 Json::Value params;
221 params[jss::tx_blob] = 1.1;
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-object `tx_json`
231 params[jss::tx_json] = "";
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_json', not object.");
237 }
238 {
239 // Invalid transaction
242 tx_json[jss::TransactionType] = jss::Payment;
243 tx_json[jss::Account] = env.master.human();
244 params[jss::tx_json] = tx_json;
245
246 auto const resp = env.rpc("json", "simulate", to_string(params));
247 BEAST_EXPECT(
248 resp[jss::result][jss::error_exception] ==
249 "Field 'Destination' is required but missing.");
250 }
251 {
252 // Bad account
253 Json::Value params;
255 tx_json[jss::TransactionType] = jss::AccountSet;
256 tx_json[jss::Account] = "badAccount";
257 params[jss::tx_json] = tx_json;
258
259 auto const resp = env.rpc("json", "simulate", to_string(params));
260 BEAST_EXPECTS(
261 resp[jss::result][jss::error] == "srcActMalformed",
262 resp[jss::result][jss::error].toStyledString());
263 BEAST_EXPECT(
264 resp[jss::result][jss::error_message] ==
265 "Invalid field 'tx.Account'.");
266 }
267 {
268 // Account doesn't exist for Sequence autofill
269 Json::Value params;
271 tx_json[jss::TransactionType] = jss::AccountSet;
272 tx_json[jss::Account] = alice.human();
273 params[jss::tx_json] = tx_json;
274
275 auto const resp = env.rpc("json", "simulate", to_string(params));
276 BEAST_EXPECT(
277 resp[jss::result][jss::error_message] ==
278 "Source account not found.");
279 }
280 {
281 // Invalid Signers field
282 Json::Value params;
284 tx_json[jss::TransactionType] = jss::AccountSet;
285 tx_json[jss::Account] = env.master.human();
286 tx_json[sfSigners] = "1";
287 params[jss::tx_json] = tx_json;
288
289 auto const resp = env.rpc("json", "simulate", to_string(params));
290 BEAST_EXPECT(
291 resp[jss::result][jss::error_message] ==
292 "Invalid field 'tx.Signers'.");
293 }
294 {
295 // Invalid Signers field
296 Json::Value params;
298 tx_json[jss::TransactionType] = jss::AccountSet;
299 tx_json[jss::Account] = env.master.human();
300 tx_json[sfSigners] = Json::arrayValue;
301 tx_json[sfSigners].append("1");
302 params[jss::tx_json] = tx_json;
303
304 auto const resp = env.rpc("json", "simulate", to_string(params));
305 BEAST_EXPECT(
306 resp[jss::result][jss::error_message] ==
307 "Invalid field 'tx.Signers[0]'.");
308 }
309 {
310 // Invalid transaction
311 Json::Value params;
313 tx_json[jss::TransactionType] = jss::AccountSet;
314 tx_json[jss::Account] = env.master.human();
315 tx_json["foo"] = "bar";
316 params[jss::tx_json] = tx_json;
317
318 auto const resp = env.rpc("json", "simulate", to_string(params));
319 BEAST_EXPECT(
320 resp[jss::result][jss::error_message] ==
321 "Field 'tx_json.foo' is unknown.");
322 }
323 {
324 // non-`"binary"` second param for CLI
326 tx_json[jss::TransactionType] = jss::AccountSet;
327 tx_json[jss::Account] = alice.human();
328 auto const resp = env.rpc("simulate", to_string(tx_json), "1");
329 BEAST_EXPECT(resp[jss::error_message] == "Invalid parameters.");
330 }
331 {
332 // Signed transaction
333 Json::Value params;
335 tx_json[jss::TransactionType] = jss::AccountSet;
336 tx_json[jss::Account] = env.master.human();
337 tx_json[jss::TxnSignature] = "1200ABCD";
338 params[jss::tx_json] = tx_json;
339
340 auto const resp = env.rpc("json", "simulate", to_string(params));
341 BEAST_EXPECT(
342 resp[jss::result][jss::error_message] ==
343 "Transaction should not be signed.");
344 }
345 {
346 // Signed multisig transaction
347 Json::Value params;
349 tx_json[jss::TransactionType] = jss::AccountSet;
350 tx_json[jss::Account] = env.master.human();
351 tx_json[sfSigners] = Json::arrayValue;
352 {
354 signer[jss::Account] = alice.human();
355 signer[jss::SigningPubKey] = alice.human();
356 signer[jss::TxnSignature] = "1200ABCD";
357 Json::Value signerOuter;
358 signerOuter[sfSigner] = signer;
359 tx_json[sfSigners].append(signerOuter);
360 }
361 params[jss::tx_json] = tx_json;
362
363 auto const resp = env.rpc("json", "simulate", to_string(params));
364 BEAST_EXPECT(
365 resp[jss::result][jss::error_message] ==
366 "Transaction should not be signed.");
367 }
368 }
369
370 void
372 {
373 testcase("Fee failure");
374
375 using namespace jtx;
376
377 Env env(*this, envconfig([](std::unique_ptr<Config> cfg) {
378 cfg->section("transaction_queue")
379 .set("minimum_txn_in_ledger_standalone", "3");
380 return cfg;
381 }));
382
383 Account const alice{"alice"};
384 env.fund(XRP(1000000), alice);
385 env.close();
386
387 // fill queue
388 auto metrics = env.app().getTxQ().getMetrics(*env.current());
389 for (int i = metrics.txInLedger; i <= metrics.txPerLedger; ++i)
390 env(noop(alice));
391
392 {
393 Json::Value params;
394 params[jss::tx_json] = noop(alice);
395
396 auto const resp = env.rpc("json", "simulate", to_string(params));
397 auto const result = resp[jss::result];
398 if (BEAST_EXPECT(result.isMember(jss::error)))
399 {
400 BEAST_EXPECT(result[jss::error] == "highFee");
401 BEAST_EXPECT(result[jss::error_code] == rpcHIGH_FEE);
402 BEAST_EXPECT(
403 result[jss::error_message] ==
404 "Fee of 8889 exceeds the requested tx limit of 100");
405 }
406 }
407 }
408
409 void
411 {
412 testcase("Successful transaction");
413
414 using namespace jtx;
415 Env env(*this);
416 static auto const newDomain = "123ABC";
417
418 {
419 auto validateOutput = [&](Json::Value const& resp,
420 Json::Value const& tx) {
421 auto result = resp[jss::result];
423 result, tx, 1, env.current()->fees().base);
424
425 BEAST_EXPECT(result[jss::engine_result] == "tesSUCCESS");
426 BEAST_EXPECT(result[jss::engine_result_code] == 0);
427 BEAST_EXPECT(
428 result[jss::engine_result_message] ==
429 "The simulated transaction would have been applied.");
430
431 if (BEAST_EXPECT(
432 result.isMember(jss::meta) ||
433 result.isMember(jss::meta_blob)))
434 {
435 Json::Value const metadata = getJsonMetadata(result);
436
437 if (BEAST_EXPECT(
438 metadata.isMember(sfAffectedNodes.jsonName)))
439 {
440 BEAST_EXPECT(
441 metadata[sfAffectedNodes.jsonName].size() == 1);
442 auto node = metadata[sfAffectedNodes.jsonName][0u];
443 if (BEAST_EXPECT(
444 node.isMember(sfModifiedNode.jsonName)))
445 {
446 auto modifiedNode = node[sfModifiedNode];
447 BEAST_EXPECT(
448 modifiedNode[sfLedgerEntryType] ==
449 "AccountRoot");
450 auto finalFields = modifiedNode[sfFinalFields];
451 BEAST_EXPECT(finalFields[sfDomain] == newDomain);
452 }
453 }
454 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
455 BEAST_EXPECT(
456 metadata[sfTransactionResult.jsonName] == "tesSUCCESS");
457 }
458 };
459
460 Json::Value tx;
461
462 tx[jss::Account] = env.master.human();
463 tx[jss::TransactionType] = jss::AccountSet;
464 tx[sfDomain] = newDomain;
465
466 // test with autofill
467 testTx(env, tx, validateOutput);
468
469 tx[sfSigningPubKey] = "";
470 tx[sfTxnSignature] = "";
471 tx[sfSequence] = 1;
472 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
473
474 // test without autofill
475 testTx(env, tx, validateOutput);
476
477 // TODO: check that the ledger wasn't affected
478 }
479 }
480
481 void
483 {
484 testcase("Transaction non-tec failure");
485
486 using namespace jtx;
487 Env env(*this);
488 Account const alice("alice");
489
490 {
491 std::function<void(Json::Value const&, Json::Value const&)> const&
492 testSimulation = [&](Json::Value const& resp,
493 Json::Value const& tx) {
494 auto result = resp[jss::result];
496 result, tx, 1, env.current()->fees().base);
497
498 BEAST_EXPECT(result[jss::engine_result] == "temBAD_AMOUNT");
499 BEAST_EXPECT(result[jss::engine_result_code] == -298);
500 BEAST_EXPECT(
501 result[jss::engine_result_message] ==
502 "Malformed: Bad amount.");
503
504 BEAST_EXPECT(
505 !result.isMember(jss::meta) &&
506 !result.isMember(jss::meta_blob));
507 };
508
509 Json::Value tx;
510
511 tx[jss::Account] = env.master.human();
512 tx[jss::TransactionType] = jss::Payment;
513 tx[sfDestination] = alice.human();
514 tx[sfAmount] = "0"; // invalid amount
515
516 // test with autofill
517 testTx(env, tx, testSimulation);
518
519 tx[sfSigningPubKey] = "";
520 tx[sfTxnSignature] = "";
521 tx[sfSequence] = 1;
522 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
523
524 // test without autofill
525 testTx(env, tx, testSimulation);
526
527 // TODO: check that the ledger wasn't affected
528 }
529 }
530
531 void
533 {
534 testcase("Transaction tec failure");
535
536 using namespace jtx;
537 Env env(*this);
538 Account const alice("alice");
539
540 {
541 std::function<void(Json::Value const&, Json::Value const&)> const&
542 testSimulation = [&](Json::Value const& resp,
543 Json::Value const& tx) {
544 auto result = resp[jss::result];
546 result, tx, 1, env.current()->fees().base);
547
548 BEAST_EXPECT(
549 result[jss::engine_result] == "tecNO_DST_INSUF_XRP");
550 BEAST_EXPECT(result[jss::engine_result_code] == 125);
551 BEAST_EXPECT(
552 result[jss::engine_result_message] ==
553 "Destination does not exist. Too little XRP sent to "
554 "create "
555 "it.");
556
557 if (BEAST_EXPECT(
558 result.isMember(jss::meta) ||
559 result.isMember(jss::meta_blob)))
560 {
561 Json::Value const metadata = getJsonMetadata(result);
562
563 if (BEAST_EXPECT(
564 metadata.isMember(sfAffectedNodes.jsonName)))
565 {
566 BEAST_EXPECT(
567 metadata[sfAffectedNodes.jsonName].size() == 1);
568 auto node = metadata[sfAffectedNodes.jsonName][0u];
569 if (BEAST_EXPECT(
570 node.isMember(sfModifiedNode.jsonName)))
571 {
572 auto modifiedNode = node[sfModifiedNode];
573 BEAST_EXPECT(
574 modifiedNode[sfLedgerEntryType] ==
575 "AccountRoot");
576 auto finalFields = modifiedNode[sfFinalFields];
577 BEAST_EXPECT(
578 finalFields[sfBalance] ==
579 "99999999999999990");
580 }
581 }
582 BEAST_EXPECT(
583 metadata[sfTransactionIndex.jsonName] == 0);
584 BEAST_EXPECT(
585 metadata[sfTransactionResult.jsonName] ==
586 "tecNO_DST_INSUF_XRP");
587 }
588 };
589
590 Json::Value tx;
591
592 tx[jss::Account] = env.master.human();
593 tx[jss::TransactionType] = jss::Payment;
594 tx[sfDestination] = alice.human();
595 tx[sfAmount] = "1"; // not enough to create an account
596
597 // test with autofill
598 testTx(env, tx, testSimulation);
599
600 tx[sfSigningPubKey] = "";
601 tx[sfTxnSignature] = "";
602 tx[sfSequence] = 1;
603 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
604
605 // test without autofill
606 testTx(env, tx, testSimulation);
607
608 // TODO: check that the ledger wasn't affected
609 }
610 }
611
612 void
614 {
615 testcase("Successful multi-signed transaction");
616
617 using namespace jtx;
618 Env env(*this);
619 static auto const newDomain = "123ABC";
620 Account const alice("alice");
621 Account const becky("becky");
622 Account const carol("carol");
623 env.fund(XRP(10000), alice);
624 env.close();
625
626 // set up valid multisign
627 env(signers(alice, 1, {{becky, 1}, {carol, 1}}));
628
629 {
630 auto validateOutput = [&](Json::Value const& resp,
631 Json::Value const& tx) {
632 auto result = resp[jss::result];
634 result, tx, env.seq(alice), env.current()->fees().base * 2);
635
636 BEAST_EXPECT(result[jss::engine_result] == "tesSUCCESS");
637 BEAST_EXPECT(result[jss::engine_result_code] == 0);
638 BEAST_EXPECT(
639 result[jss::engine_result_message] ==
640 "The simulated transaction would have been applied.");
641
642 if (BEAST_EXPECT(
643 result.isMember(jss::meta) ||
644 result.isMember(jss::meta_blob)))
645 {
646 Json::Value const metadata = getJsonMetadata(result);
647
648 if (BEAST_EXPECT(
649 metadata.isMember(sfAffectedNodes.jsonName)))
650 {
651 BEAST_EXPECT(
652 metadata[sfAffectedNodes.jsonName].size() == 1);
653 auto node = metadata[sfAffectedNodes.jsonName][0u];
654 if (BEAST_EXPECT(
655 node.isMember(sfModifiedNode.jsonName)))
656 {
657 auto modifiedNode = node[sfModifiedNode];
658 BEAST_EXPECT(
659 modifiedNode[sfLedgerEntryType] ==
660 "AccountRoot");
661 auto finalFields = modifiedNode[sfFinalFields];
662 BEAST_EXPECT(finalFields[sfDomain] == newDomain);
663 }
664 }
665 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 1);
666 BEAST_EXPECT(
667 metadata[sfTransactionResult.jsonName] == "tesSUCCESS");
668 }
669 };
670
671 Json::Value tx;
672
673 tx[jss::Account] = alice.human();
674 tx[jss::TransactionType] = jss::AccountSet;
675 tx[sfDomain] = newDomain;
676 tx[sfSigners] = Json::arrayValue;
677 {
679 signer[jss::Account] = becky.human();
680 Json::Value signerOuter;
681 signerOuter[sfSigner] = signer;
682 tx[sfSigners].append(signerOuter);
683 }
684
685 // test with autofill
686 testTx(env, tx, validateOutput, false);
687
688 tx[sfSigningPubKey] = "";
689 tx[sfTxnSignature] = "";
690 tx[sfSequence] = env.seq(alice);
691 // transaction requires a non-base fee
692 tx[sfFee] =
693 (env.current()->fees().base * 2).jsonClipped().asString();
694 tx[sfSigners][0u][sfSigner][jss::SigningPubKey] =
695 strHex(becky.pk().slice());
696 tx[sfSigners][0u][sfSigner][jss::TxnSignature] = "";
697
698 // test without autofill
699 testTx(env, tx, validateOutput);
700
701 // TODO: check that the ledger wasn't affected
702 }
703 }
704
705 void
707 {
708 testcase("Transaction with a key-related failure");
709
710 using namespace jtx;
711 Env env(*this);
712 static auto const newDomain = "123ABC";
713 Account const alice{"alice"};
714 env(regkey(env.master, alice));
715 env(fset(env.master, asfDisableMaster), sig(env.master));
716 env.close();
717
718 {
719 std::function<void(Json::Value const&, Json::Value const&)> const&
720 testSimulation =
721 [&](Json::Value const& resp, Json::Value const& tx) {
722 auto result = resp[jss::result];
724 result,
725 tx,
726 env.seq(env.master),
727 env.current()->fees().base);
728
729 BEAST_EXPECT(
730 result[jss::engine_result] == "tefMASTER_DISABLED");
731 BEAST_EXPECT(result[jss::engine_result_code] == -188);
732 BEAST_EXPECT(
733 result[jss::engine_result_message] ==
734 "Master key is disabled.");
735
736 BEAST_EXPECT(
737 !result.isMember(jss::meta) &&
738 !result.isMember(jss::meta_blob));
739 };
740
741 Json::Value tx;
742
743 tx[jss::Account] = env.master.human();
744 tx[jss::TransactionType] = jss::AccountSet;
745 tx[sfDomain] = newDomain;
746
747 // test with autofill
748 testTx(env, tx, testSimulation);
749
750 tx[sfSigningPubKey] = "";
751 tx[sfTxnSignature] = "";
752 tx[sfSequence] = env.seq(env.master);
753 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
754
755 // test without autofill
756 testTx(env, tx, testSimulation);
757
758 // TODO: check that the ledger wasn't affected
759 }
760 }
761
762 void
764 {
765 testcase("Multi-signed transaction with a bad public key");
766
767 using namespace jtx;
768 Env env(*this);
769 static auto const newDomain = "123ABC";
770 Account const alice("alice");
771 Account const becky("becky");
772 Account const carol("carol");
773 Account const dylan("dylan");
774 env.fund(XRP(10000), alice);
775 env.close();
776
777 // set up valid multisign
778 env(signers(alice, 1, {{becky, 1}, {carol, 1}}));
779
780 {
781 auto validateOutput = [&](Json::Value const& resp,
782 Json::Value const& tx) {
783 auto result = resp[jss::result];
785 result, tx, env.seq(alice), env.current()->fees().base * 2);
786
787 BEAST_EXPECTS(
788 result[jss::engine_result] == "tefBAD_SIGNATURE",
789 result[jss::engine_result].toStyledString());
790 BEAST_EXPECT(result[jss::engine_result_code] == -186);
791 BEAST_EXPECT(
792 result[jss::engine_result_message] ==
793 "A signature is provided for a non-signer.");
794
795 BEAST_EXPECT(
796 !result.isMember(jss::meta) &&
797 !result.isMember(jss::meta_blob));
798 };
799
800 Json::Value tx;
801
802 tx[jss::Account] = alice.human();
803 tx[jss::TransactionType] = jss::AccountSet;
804 tx[sfDomain] = newDomain;
805 tx[sfSigners] = Json::arrayValue;
806 {
808 signer[jss::Account] = becky.human();
809 signer[jss::SigningPubKey] = strHex(dylan.pk().slice());
810 Json::Value signerOuter;
811 signerOuter[sfSigner] = signer;
812 tx[sfSigners].append(signerOuter);
813 }
814
815 // test with autofill
816 testTx(env, tx, validateOutput, false);
817
818 tx[sfSigningPubKey] = "";
819 tx[sfTxnSignature] = "";
820 tx[sfSequence] = env.seq(alice);
821 // transaction requires a non-base fee
822 tx[sfFee] =
823 (env.current()->fees().base * 2).jsonClipped().asString();
824 tx[sfSigners][0u][sfSigner][jss::TxnSignature] = "";
825
826 // test without autofill
827 testTx(env, tx, validateOutput);
828
829 // TODO: check that the ledger wasn't affected
830 }
831 }
832
833 void
835 {
836 testcase("Credentials aren't actually deleted on `tecEXPIRED`");
837
838 // scenario setup
839
840 using namespace jtx;
841 Env env(*this);
842
843 Account const subject{"subject"};
844 Account const issuer{"issuer"};
845
846 env.fund(XRP(10000), subject, issuer);
847 env.close();
848
849 auto const credType = "123ABC";
850
851 auto jv = credentials::create(subject, issuer, credType);
852 uint32_t const t =
853 env.current()->info().parentCloseTime.time_since_epoch().count();
854 jv[sfExpiration.jsonName] = t;
855 env(jv);
856 env.close();
857
858 {
859 auto validateOutput = [&](Json::Value const& resp,
860 Json::Value const& tx) {
861 auto result = resp[jss::result];
863 result, tx, env.seq(subject), env.current()->fees().base);
864
865 BEAST_EXPECT(result[jss::engine_result] == "tecEXPIRED");
866 BEAST_EXPECT(result[jss::engine_result_code] == 148);
867 BEAST_EXPECT(
868 result[jss::engine_result_message] ==
869 "Expiration time is passed.");
870
871 if (BEAST_EXPECT(
872 result.isMember(jss::meta) ||
873 result.isMember(jss::meta_blob)))
874 {
875 Json::Value const metadata = getJsonMetadata(result);
876
877 if (BEAST_EXPECT(
878 metadata.isMember(sfAffectedNodes.jsonName)))
879 {
880 BEAST_EXPECT(
881 metadata[sfAffectedNodes.jsonName].size() == 5);
882
883 try
884 {
885 bool found = false;
886 for (auto const& node :
887 metadata[sfAffectedNodes.jsonName])
888 {
889 if (node.isMember(sfDeletedNode.jsonName) &&
890 node[sfDeletedNode.jsonName]
891 [sfLedgerEntryType.jsonName]
892 .asString() == "Credential")
893 {
894 auto const deleted =
895 node[sfDeletedNode.jsonName]
896 [sfFinalFields.jsonName];
897 found = deleted[jss::Issuer] ==
898 issuer.human() &&
899 deleted[jss::Subject] ==
900 subject.human() &&
901 deleted["CredentialType"] ==
902 strHex(std::string_view(credType));
903 break;
904 }
905 }
906 BEAST_EXPECT(found);
907 }
908 catch (...)
909 {
910 fail();
911 }
912 }
913 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
914 BEAST_EXPECT(
915 metadata[sfTransactionResult.jsonName] == "tecEXPIRED");
916 }
917 };
918
919 Json::Value tx = credentials::accept(subject, issuer, credType);
920
921 // test with autofill
922 testTx(env, tx, validateOutput);
923
924 tx[sfSigningPubKey] = "";
925 tx[sfTxnSignature] = "";
926 tx[sfSequence] = env.seq(subject);
927 tx[sfFee] = env.current()->fees().base.jsonClipped().asString();
928
929 // test without autofill
930 testTx(env, tx, validateOutput);
931 }
932
933 // check that expired credentials weren't deleted
934 auto const jle =
935 credentials::ledgerEntry(env, subject, issuer, credType);
936 BEAST_EXPECT(
937 jle.isObject() && jle.isMember(jss::result) &&
938 !jle[jss::result].isMember(jss::error) &&
939 jle[jss::result].isMember(jss::node) &&
940 jle[jss::result][jss::node].isMember("LedgerEntryType") &&
941 jle[jss::result][jss::node]["LedgerEntryType"] == jss::Credential &&
942 jle[jss::result][jss::node][jss::Issuer] == issuer.human() &&
943 jle[jss::result][jss::node][jss::Subject] == subject.human() &&
944 jle[jss::result][jss::node]["CredentialType"] ==
945 strHex(std::string_view(credType)));
946
947 BEAST_EXPECT(ownerCount(env, issuer) == 1);
948 BEAST_EXPECT(ownerCount(env, subject) == 0);
949 }
950
951public:
952 void
953 run() override
954 {
956 testFeeError();
964 }
965};
966
967BEAST_DEFINE_TESTSUITE(Simulate, rpc, ripple);
968
969} // namespace test
970
971} // namespace ripple
Represents a JSON value.
Definition: json_value.h:147
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:841
UInt size() const
Number of values in array or object.
Definition: json_value.cpp:706
Value & append(const Value &value)
Append value to array at the end.
Definition: json_value.cpp:891
std::string asString() const
Returns the unquoted string value.
Definition: json_value.cpp:469
bool isMember(const char *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:943
A testsuite class.
Definition: suite.h:53
testcase_t testcase
Memberspace for declaring test cases.
Definition: suite.h:153
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition: suite.h:531
virtual TxQ & getTxQ()=0
Slice slice() const noexcept
Definition: PublicKey.h:123
Json::Value getJson(JsonOptions options) const override
Definition: STObject.cpp:795
Holds the serialized result of parsing an input JSON object.
Definition: STParsedJSON.h:32
std::optional< STObject > object
The STObject if the parse was successful.
Definition: STParsedJSON.h:50
Metrics getMetrics(OpenView const &view) const
Returns fee metrics in reference fee level units.
Definition: TxQ.cpp:1777
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:38
PublicKey const & pk() const
Return the public key.
Definition: Account.h:89
std::string const & human() const
Returns the human readable public key.
Definition: Account.h:113
A transaction testing environment.
Definition: Env.h:117
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition: Env.cpp:216
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition: Env.h:325
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:121
Account const & master
Definition: Env.h:121
Application & app()
Definition: Env.h:255
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:764
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:237
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition: rpc.h:34
Set the regular signature on a JTx.
Definition: sig.h:35
@ arrayValue
array value (ordered list)
Definition: json_value.h:42
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:43
Json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: credentials.cpp:31
Json::Value accept(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: credentials.cpp:49
Json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition: credentials.cpp:82
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:28
Json::Value signers(Account const &account, std::uint32_t quorum, std::vector< signer > const &v)
Definition: multisign.cpp:35
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition: flags.cpp:28
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:104
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:316
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:243
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:629
T ref(T... args)
A signer in a SignerList.
Definition: multisign.h:37