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>
44 int const expectedSequence,
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));
57 tx_json = result[jss::tx_json];
61 auto const unHexed =
strUnHex(result[jss::tx_blob].asString());
66 BEAST_EXPECT(tx_json[jss::TransactionType] == tx[jss::TransactionType]);
67 BEAST_EXPECT(tx_json[jss::Account] == tx[jss::Account]);
69 tx_json[jss::SigningPubKey] == tx.
get(jss::SigningPubKey,
""));
71 tx_json[jss::TxnSignature] == tx.
get(jss::TxnSignature,
""));
72 BEAST_EXPECT(tx_json[jss::Fee] == tx.
get(jss::Fee, expectedFee));
74 tx_json[jss::Sequence] == tx.
get(jss::Sequence, expectedSequence));
81 int const expectedSequence,
94 bool testSerialized =
true)
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);
102 validate(env.
rpc(
"simulate",
to_string(tx),
"binary"), tx);
111 if (BEAST_EXPECT(parsed.
object.has_value()))
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);
119 validate(env.
rpc(
"simulate", tx_blob), tx);
120 validate(env.
rpc(
"simulate", tx_blob,
"binary"), tx);
127 if (txResult.
isMember(jss::meta_blob))
129 auto unHexed =
strUnHex(txResult[jss::meta_blob].asString());
135 return txResult[jss::meta];
150 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
152 resp[jss::result][jss::error_message] ==
153 "Neither `tx_blob` nor `tx_json` included.");
159 params[jss::tx_blob] =
"1200";
161 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
163 resp[jss::result][jss::error_message] ==
164 "Can only include one of `tx_blob` and `tx_json`.");
169 params[jss::tx_blob] =
"1200";
170 params[jss::binary] =
"100";
171 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
173 resp[jss::result][jss::error_message] ==
174 "Invalid field 'binary'.");
179 params[jss::tx_blob] =
"12";
181 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
183 resp[jss::result][jss::error_message] ==
184 "Invalid field 'tx_blob'.");
191 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
193 resp[jss::result][jss::error_message] ==
194 "Missing field 'tx.TransactionType'.");
200 tx_json[jss::TransactionType] = jss::Payment;
201 params[jss::tx_json] = tx_json;
203 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
205 resp[jss::result][jss::error_message] ==
206 "Missing field 'tx.Account'.");
211 params[jss::tx_blob] =
"";
213 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
215 resp[jss::result][jss::error_message] ==
216 "Invalid field 'tx_blob'.");
221 params[jss::tx_blob] = 1.1;
223 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
225 resp[jss::result][jss::error_message] ==
226 "Invalid field 'tx_blob'.");
231 params[jss::tx_json] =
"";
233 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
235 resp[jss::result][jss::error_message] ==
236 "Invalid field 'tx_json', not object.");
242 tx_json[jss::TransactionType] = jss::Payment;
244 params[jss::tx_json] = tx_json;
246 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
248 resp[jss::result][jss::error_exception] ==
249 "Field 'Destination' is required but missing.");
255 tx_json[jss::TransactionType] = jss::AccountSet;
256 tx_json[jss::Account] =
"badAccount";
257 params[jss::tx_json] = tx_json;
259 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
261 resp[jss::result][jss::error] ==
"srcActMalformed",
262 resp[jss::result][jss::error].toStyledString());
264 resp[jss::result][jss::error_message] ==
265 "Invalid field 'tx.Account'.");
271 tx_json[jss::TransactionType] = jss::AccountSet;
272 tx_json[jss::Account] = alice.
human();
273 params[jss::tx_json] = tx_json;
275 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
277 resp[jss::result][jss::error_message] ==
278 "Source account not found.");
284 tx_json[jss::TransactionType] = jss::AccountSet;
286 tx_json[sfSigners] =
"1";
287 params[jss::tx_json] = tx_json;
289 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
291 resp[jss::result][jss::error_message] ==
292 "Invalid field 'tx.Signers'.");
298 tx_json[jss::TransactionType] = jss::AccountSet;
301 tx_json[sfSigners].
append(
"1");
302 params[jss::tx_json] = tx_json;
304 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
306 resp[jss::result][jss::error_message] ==
307 "Invalid field 'tx.Signers[0]'.");
313 tx_json[jss::TransactionType] = jss::AccountSet;
315 tx_json[
"foo"] =
"bar";
316 params[jss::tx_json] = tx_json;
318 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
320 resp[jss::result][jss::error_message] ==
321 "Field 'tx_json.foo' is unknown.");
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.");
335 tx_json[jss::TransactionType] = jss::AccountSet;
337 tx_json[jss::TxnSignature] =
"1200ABCD";
338 params[jss::tx_json] = tx_json;
340 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
342 resp[jss::result][jss::error_message] ==
343 "Transaction should not be signed.");
349 tx_json[jss::TransactionType] = jss::AccountSet;
356 signer[jss::TxnSignature] =
"1200ABCD";
358 signerOuter[sfSigner] =
signer;
359 tx_json[sfSigners].
append(signerOuter);
361 params[jss::tx_json] = tx_json;
363 auto const resp = env.
rpc(
"json",
"simulate",
to_string(params));
365 resp[jss::result][jss::error_message] ==
366 "Transaction should not be signed.");
378 cfg->section(
"transaction_queue")
379 .
set(
"minimum_txn_in_ledger_standalone",
"3");
389 for (
int i = metrics.txInLedger; i <= metrics.txPerLedger; ++i)
394 params[jss::tx_json] =
noop(alice);
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)))
400 BEAST_EXPECT(result[jss::error] ==
"highFee");
401 BEAST_EXPECT(result[jss::error_code] ==
rpcHIGH_FEE);
403 result[jss::error_message] ==
404 "Fee of 8889 exceeds the requested tx limit of 100");
416 static auto const newDomain =
"123ABC";
421 auto result = resp[jss::result];
423 result, tx, 1, env.
current()->fees().base);
425 BEAST_EXPECT(result[jss::engine_result] ==
"tesSUCCESS");
426 BEAST_EXPECT(result[jss::engine_result_code] == 0);
428 result[jss::engine_result_message] ==
429 "The simulated transaction would have been applied.");
432 result.isMember(jss::meta) ||
433 result.isMember(jss::meta_blob)))
438 metadata.
isMember(sfAffectedNodes.jsonName)))
441 metadata[sfAffectedNodes.jsonName].
size() == 1);
442 auto node = metadata[sfAffectedNodes.jsonName][0u];
444 node.isMember(sfModifiedNode.jsonName)))
446 auto modifiedNode = node[sfModifiedNode];
448 modifiedNode[sfLedgerEntryType] ==
450 auto finalFields = modifiedNode[sfFinalFields];
451 BEAST_EXPECT(finalFields[sfDomain] == newDomain);
454 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
456 metadata[sfTransactionResult.jsonName] ==
"tesSUCCESS");
463 tx[jss::TransactionType] = jss::AccountSet;
464 tx[sfDomain] = newDomain;
467 testTx(env, tx, validateOutput);
469 tx[sfSigningPubKey] =
"";
470 tx[sfTxnSignature] =
"";
472 tx[sfFee] = env.
current()->fees().base.jsonClipped().asString();
475 testTx(env, tx, validateOutput);
484 testcase(
"Transaction non-tec failure");
494 auto result = resp[jss::result];
496 result, tx, 1, env.
current()->fees().base);
498 BEAST_EXPECT(result[jss::engine_result] ==
"temBAD_AMOUNT");
499 BEAST_EXPECT(result[jss::engine_result_code] == -298);
501 result[jss::engine_result_message] ==
502 "Malformed: Bad amount.");
505 !result.isMember(jss::meta) &&
506 !result.isMember(jss::meta_blob));
512 tx[jss::TransactionType] = jss::Payment;
513 tx[sfDestination] = alice.
human();
517 testTx(env, tx, testSimulation);
519 tx[sfSigningPubKey] =
"";
520 tx[sfTxnSignature] =
"";
522 tx[sfFee] = env.
current()->fees().base.jsonClipped().asString();
525 testTx(env, tx, testSimulation);
534 testcase(
"Transaction tec failure");
544 auto result = resp[jss::result];
546 result, tx, 1, env.
current()->fees().base);
549 result[jss::engine_result] ==
"tecNO_DST_INSUF_XRP");
550 BEAST_EXPECT(result[jss::engine_result_code] == 125);
552 result[jss::engine_result_message] ==
553 "Destination does not exist. Too little XRP sent to "
558 result.isMember(jss::meta) ||
559 result.isMember(jss::meta_blob)))
564 metadata.
isMember(sfAffectedNodes.jsonName)))
567 metadata[sfAffectedNodes.jsonName].
size() == 1);
568 auto node = metadata[sfAffectedNodes.jsonName][0u];
570 node.isMember(sfModifiedNode.jsonName)))
572 auto modifiedNode = node[sfModifiedNode];
574 modifiedNode[sfLedgerEntryType] ==
576 auto finalFields = modifiedNode[sfFinalFields];
578 finalFields[sfBalance] ==
579 "99999999999999990");
583 metadata[sfTransactionIndex.jsonName] == 0);
585 metadata[sfTransactionResult.jsonName] ==
586 "tecNO_DST_INSUF_XRP");
593 tx[jss::TransactionType] = jss::Payment;
594 tx[sfDestination] = alice.
human();
598 testTx(env, tx, testSimulation);
600 tx[sfSigningPubKey] =
"";
601 tx[sfTxnSignature] =
"";
603 tx[sfFee] = env.
current()->fees().base.jsonClipped().asString();
606 testTx(env, tx, testSimulation);
615 testcase(
"Successful multi-signed transaction");
619 static auto const newDomain =
"123ABC";
627 env(
signers(alice, 1, {{becky, 1}, {carol, 1}}));
632 auto result = resp[jss::result];
634 result, tx, env.
seq(alice), env.
current()->fees().base * 2);
636 BEAST_EXPECT(result[jss::engine_result] ==
"tesSUCCESS");
637 BEAST_EXPECT(result[jss::engine_result_code] == 0);
639 result[jss::engine_result_message] ==
640 "The simulated transaction would have been applied.");
643 result.isMember(jss::meta) ||
644 result.isMember(jss::meta_blob)))
649 metadata.
isMember(sfAffectedNodes.jsonName)))
652 metadata[sfAffectedNodes.jsonName].
size() == 1);
653 auto node = metadata[sfAffectedNodes.jsonName][0u];
655 node.isMember(sfModifiedNode.jsonName)))
657 auto modifiedNode = node[sfModifiedNode];
659 modifiedNode[sfLedgerEntryType] ==
661 auto finalFields = modifiedNode[sfFinalFields];
662 BEAST_EXPECT(finalFields[sfDomain] == newDomain);
665 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 1);
667 metadata[sfTransactionResult.jsonName] ==
"tesSUCCESS");
673 tx[jss::Account] = alice.
human();
674 tx[jss::TransactionType] = jss::AccountSet;
675 tx[sfDomain] = newDomain;
681 signerOuter[sfSigner] =
signer;
682 tx[sfSigners].
append(signerOuter);
686 testTx(env, tx, validateOutput,
false);
688 tx[sfSigningPubKey] =
"";
689 tx[sfTxnSignature] =
"";
690 tx[sfSequence] = env.
seq(alice);
694 tx[sfSigners][0u][sfSigner][jss::SigningPubKey] =
696 tx[sfSigners][0u][sfSigner][jss::TxnSignature] =
"";
699 testTx(env, tx, validateOutput);
708 testcase(
"Transaction with a key-related failure");
712 static auto const newDomain =
"123ABC";
722 auto result = resp[jss::result];
730 result[jss::engine_result] ==
"tefMASTER_DISABLED");
731 BEAST_EXPECT(result[jss::engine_result_code] == -188);
733 result[jss::engine_result_message] ==
734 "Master key is disabled.");
737 !result.isMember(jss::meta) &&
738 !result.isMember(jss::meta_blob));
744 tx[jss::TransactionType] = jss::AccountSet;
745 tx[sfDomain] = newDomain;
748 testTx(env, tx, testSimulation);
750 tx[sfSigningPubKey] =
"";
751 tx[sfTxnSignature] =
"";
753 tx[sfFee] = env.
current()->fees().base.jsonClipped().asString();
756 testTx(env, tx, testSimulation);
765 testcase(
"Multi-signed transaction with a bad public key");
769 static auto const newDomain =
"123ABC";
778 env(
signers(alice, 1, {{becky, 1}, {carol, 1}}));
783 auto result = resp[jss::result];
785 result, tx, env.
seq(alice), env.
current()->fees().base * 2);
788 result[jss::engine_result] ==
"tefBAD_SIGNATURE",
789 result[jss::engine_result].toStyledString());
790 BEAST_EXPECT(result[jss::engine_result_code] == -186);
792 result[jss::engine_result_message] ==
793 "A signature is provided for a non-signer.");
796 !result.isMember(jss::meta) &&
797 !result.isMember(jss::meta_blob));
802 tx[jss::Account] = alice.
human();
803 tx[jss::TransactionType] = jss::AccountSet;
804 tx[sfDomain] = newDomain;
811 signerOuter[sfSigner] =
signer;
812 tx[sfSigners].
append(signerOuter);
816 testTx(env, tx, validateOutput,
false);
818 tx[sfSigningPubKey] =
"";
819 tx[sfTxnSignature] =
"";
820 tx[sfSequence] = env.
seq(alice);
824 tx[sfSigners][0u][sfSigner][jss::TxnSignature] =
"";
827 testTx(env, tx, validateOutput);
836 testcase(
"Credentials aren't actually deleted on `tecEXPIRED`");
843 Account const subject{
"subject"};
844 Account const issuer{
"issuer"};
846 env.
fund(
XRP(10000), subject, issuer);
849 auto const credType =
"123ABC";
853 env.
current()->info().parentCloseTime.time_since_epoch().count();
854 jv[sfExpiration.jsonName] = t;
861 auto result = resp[jss::result];
863 result, tx, env.
seq(subject), env.
current()->fees().base);
865 BEAST_EXPECT(result[jss::engine_result] ==
"tecEXPIRED");
866 BEAST_EXPECT(result[jss::engine_result_code] == 148);
868 result[jss::engine_result_message] ==
869 "Expiration time is passed.");
872 result.isMember(jss::meta) ||
873 result.isMember(jss::meta_blob)))
878 metadata.
isMember(sfAffectedNodes.jsonName)))
881 metadata[sfAffectedNodes.jsonName].
size() == 5);
886 for (
auto const& node :
887 metadata[sfAffectedNodes.jsonName])
889 if (node.isMember(sfDeletedNode.jsonName) &&
890 node[sfDeletedNode.jsonName]
891 [sfLedgerEntryType.jsonName]
892 .asString() ==
"Credential")
895 node[sfDeletedNode.jsonName]
896 [sfFinalFields.jsonName];
897 found = deleted[jss::Issuer] ==
899 deleted[jss::Subject] ==
901 deleted[
"CredentialType"] ==
913 BEAST_EXPECT(metadata[sfTransactionIndex.jsonName] == 0);
915 metadata[sfTransactionResult.jsonName] ==
"tecEXPIRED");
922 testTx(env, tx, validateOutput);
924 tx[sfSigningPubKey] =
"";
925 tx[sfTxnSignature] =
"";
926 tx[sfSequence] = env.
seq(subject);
927 tx[sfFee] = env.
current()->fees().base.jsonClipped().asString();
930 testTx(env, tx, validateOutput);
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"] ==
Value get(UInt index, const Value &defaultValue) const
If the array contains at least index+1 elements, returns the element value, otherwise returns default...
UInt size() const
Number of values in array or object.
Value & append(const Value &value)
Append value to array at the end.
std::string asString() const
Returns the unquoted string value.
bool isMember(const char *key) const
Return true if the object has a member named key.
testcase_t testcase
Memberspace for declaring test cases.
void fail(String const &reason, char const *file, int line)
Record a failure.
Slice slice() const noexcept
Json::Value getJson(JsonOptions options) const override
Holds the serialized result of parsing an input JSON object.
std::optional< STObject > object
The STObject if the parse was successful.
Metrics getMetrics(OpenView const &view) const
Returns fee metrics in reference fee level units.
Json::Value jsonClipped() const
Json::Value getJsonMetadata(Json::Value txResult) const
void testTransactionNonTecFailure()
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 testSuccessfulTransaction()
void testTransactionTecFailure()
void testMultisignedBadPubKey()
void testSuccessfulTransactionMultisigned()
void testDeleteExpiredCredentials()
void testTransactionSigningFailure()
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.
PublicKey const & pk() const
Return the public key.
std::string const & human() const
Returns the human readable public key.
A transaction testing environment.
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Set the expected result code for a JTx The test will fail if the code doesn't match.
Set the regular signature on a JTx.
@ arrayValue
array value (ordered list)
@ objectValue
object value (collection of name/value pairs).
Json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Json::Value accept(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
std::uint32_t ownerCount(Env const &env, Account const &account)
Json::Value regkey(Account const &account, disabled_t)
Disable the regular key.
Json::Value signers(Account const &account, std::uint32_t quorum, std::vector< signer > const &v)
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Json::Value noop(Account const &account)
The null transaction.
XRP_t const XRP
Converts to XRP Issue or STAmount.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
std::optional< Blob > strUnHex(std::size_t strSize, Iterator begin, Iterator end)
bool set(T &target, std::string const &name, Section const §ion)
Set a value from a configuration Section If the named value is not found or doesn't parse as a T,...
constexpr std::uint32_t asfDisableMaster
std::string strHex(FwdIt begin, FwdIt end)
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)
std::string to_string(base_uint< Bits, Tag > const &a)
A signer in a SignerList.