21#include <xrpl/beast/unit_test.h>
22#include <xrpl/protocol/ErrorCodes.h>
23#include <xrpl/protocol/jss.h>
25#include <boost/container/flat_set.hpp>
39 boost::container::flat_set<std::string>
created;
40 boost::container::flat_set<std::string>
deleted;
41 boost::container::flat_set<std::string>
modified;
51 auto buildSet = [](
auto&& init) {
52 boost::container::flat_set<std::string> r;
53 r.reserve(init.size());
69 BEAST_EXPECT(
txNode[jss::validated].asBool() ==
true);
71 txNode[jss::tx][sfTransactionType.jsonName].asString() ==
75 boost::container::flat_set<std::string> createdNodes;
76 boost::container::flat_set<std::string> deletedNodes;
77 boost::container::flat_set<std::string> modifiedNodes;
80 txNode[jss::meta][sfAffectedNodes.jsonName])
82 if (metaNode.isMember(sfCreatedNode.jsonName))
84 metaNode[sfCreatedNode.jsonName][sfLedgerEntryType.jsonName]
87 else if (metaNode.isMember(sfDeletedNode.jsonName))
89 metaNode[sfDeletedNode.jsonName][sfLedgerEntryType.jsonName]
92 else if (metaNode.isMember(sfModifiedNode.jsonName))
93 modifiedNodes.insert(metaNode[sfModifiedNode.jsonName]
94 [sfLedgerEntryType.jsonName]
99 "Unexpected or unlabeled node type in metadata.",
104 BEAST_EXPECT(createdNodes == sane.
created);
105 BEAST_EXPECT(deletedNodes == sane.
deleted);
106 BEAST_EXPECT(modifiedNodes == sane.
modified);
113 using namespace test::jtx;
127 return j.isMember(jss::result) &&
128 (j[jss::result][jss::status] ==
"success") &&
129 (j[jss::result][jss::transactions].size() == 2) &&
130 (j[jss::result][jss::transactions][0u][jss::tx]
131 [jss::TransactionType] == jss::AccountSet) &&
132 (j[jss::result][jss::transactions][1u][jss::tx]
133 [jss::TransactionType] == jss::Payment) &&
134 (j[jss::result][jss::transactions][1u][jss::tx]
135 [jss::DeliverMax] ==
"10000000010") &&
136 (j[jss::result][jss::transactions][1u][jss::tx]
138 j[jss::result][jss::transactions][1u][jss::tx]
142 if (j.isMember(jss::result) &&
143 (j[jss::result][jss::status] ==
"success") &&
144 (j[jss::result][jss::transactions].size() == 2) &&
145 (j[jss::result][jss::transactions][0u][jss::tx_json]
146 [jss::TransactionType] == jss::AccountSet))
148 auto const& payment =
149 j[jss::result][jss::transactions][1u];
151 return (payment.isMember(jss::tx_json)) &&
152 (payment[jss::tx_json][jss::TransactionType] ==
154 (payment[jss::tx_json][jss::DeliverMax] ==
156 (!payment[jss::tx_json].isMember(jss::Amount)) &&
157 (!payment[jss::tx_json].isMember(jss::hash)) &&
158 (payment[jss::hash] ==
159 "9F3085D85F472D1CC29627F260DF68EDE59D42D1D0C33E345"
160 "ECF0D4CE981D0A8") &&
161 (payment[jss::validated] ==
true) &&
162 (payment[jss::ledger_index] == 3) &&
163 (payment[jss::ledger_hash] ==
164 "5476DCD816EA04CBBA57D47BBF1FC58A5217CC93A5ADD79CB"
165 "580A5AFDD727E33") &&
166 (payment[jss::close_time_iso] ==
167 "2000-01-01T00:00:10Z");
178 return j.isMember(jss::result) &&
179 (j[jss::result][jss::status] ==
"success") &&
180 (j[jss::result][jss::transactions].size() == 0);
185 j[jss::result].
isMember(jss::error) &&
190 jParms[jss::api_version] = apiVersion;
196 jParms[jss::account] =
"0xDEADBEEF";
202 jParms[jss::account] = A1.human();
204 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(jParms))));
209 p[jss::ledger_index_min] = -1;
210 p[jss::ledger_index_max] = -1;
212 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
214 p[jss::ledger_index_min] = 0;
215 p[jss::ledger_index_max] = 100;
218 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
224 p[jss::ledger_index_min] = 1;
225 p[jss::ledger_index_max] = 2;
234 p[jss::ledger_index_min] = 2;
235 p[jss::ledger_index_max] = 1;
244 p[jss::ledger_index_min] = -1;
246 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
248 p[jss::ledger_index_min] = 1;
251 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
257 p[jss::ledger_index_min] = env.
current()->info().seq;
267 p[jss::ledger_index_max] = -1;
269 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
271 p[jss::ledger_index_max] = env.
current()->info().seq;
274 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
280 p[jss::ledger_index_max] = 3;
282 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
284 p[jss::ledger_index_max] = env.
closed()->info().seq;
286 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
288 p[jss::ledger_index_max] = env.
closed()->info().seq - 1;
289 BEAST_EXPECT(noTxs(env.
rpc(
"json",
"account_tx",
to_string(p))));
296 p[jss::ledger_index] = env.
closed()->info().seq;
298 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
300 p[jss::ledger_index] = env.
closed()->info().seq - 1;
301 BEAST_EXPECT(noTxs(env.
rpc(
"json",
"account_tx",
to_string(p))));
303 p[jss::ledger_index] = env.
current()->info().seq;
308 p[jss::ledger_index] = env.
current()->info().seq + 1;
319 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
322 BEAST_EXPECT(noTxs(env.
rpc(
"json",
"account_tx",
to_string(p))));
328 jParms[jss::account] =
"0xDEADBEEF";
329 jParms[jss::account] = A1.human();
332 p[jss::ledger_index_max] = -1;
333 p[jss::ledger_index_min] = -1;
334 p[jss::ledger_index] = -1;
338 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
348 p[jss::ledger_index_max] = env.
current()->info().seq;
351 env.
rpc(apiVersion,
"json",
"account_tx",
to_string(p))));
359 auto testInvalidAccountParam = [&](
auto const& param) {
361 params[jss::account] = param;
363 "json",
"account_tx",
to_string(params))[jss::result];
364 BEAST_EXPECT(jrr[jss::error] ==
"invalidParams");
366 jrr[jss::error_message] ==
"Invalid field 'account'.");
369 testInvalidAccountParam(1);
370 testInvalidAccountParam(1.1);
371 testInvalidAccountParam(
true);
379 p[jss::binary] =
"asdf";
383 BEAST_EXPECT(result[jss::result][jss::status] ==
"success");
390 p[jss::binary] =
true;
392 BEAST_EXPECT(result[jss::result][jss::status] ==
"success");
394 p[jss::forward] =
"true";
396 BEAST_EXPECT(result[jss::result][jss::status] ==
"success");
402 p[jss::forward] =
false;
404 BEAST_EXPECT(result[jss::result][jss::status] ==
"success");
415 using namespace test::jtx;
416 using namespace std::chrono_literals;
422 auto const USD{gw[
"USD"]};
424 env.
fund(
XRP(1000000), alice, gw);
431 env(
pay(alice, gw,
XRP(100)));
438 env(
trust(alice, USD(200)),
sig(alie));
447 env(
signers(alice, 1, {{
"bogie", 1}, {
"demon", 1}}),
sig(alie));
456 escro[jss::TransactionType] = jss::EscrowCreate;
458 escro[jss::Account] = account.human();
459 escro[jss::Destination] = to.human();
467 escrowWithFinish[sfFinishAfter.jsonName] =
468 nextTime.time_since_epoch().count();
471 env(escrowWithFinish,
sig(alie));
474 escrowWithCancel[sfFinishAfter.jsonName] =
475 nextTime.time_since_epoch().count();
476 escrowWithCancel[sfCancelAfter.jsonName] =
477 nextTime.time_since_epoch().count() + 1;
480 env(escrowWithCancel,
sig(alie));
485 escrowFinish[jss::TransactionType] = jss::EscrowFinish;
487 escrowFinish[jss::Account] = alice.human();
488 escrowFinish[sfOwner.jsonName] = alice.human();
489 escrowFinish[sfOfferSequence.jsonName] = escrowFinishSeq;
490 env(escrowFinish,
sig(alie));
494 escrowCancel[jss::TransactionType] = jss::EscrowCancel;
496 escrowCancel[jss::Account] = alice.human();
497 escrowCancel[sfOwner.jsonName] = alice.human();
498 escrowCancel[sfOfferSequence.jsonName] = escrowCancelSeq;
499 env(escrowCancel,
sig(alie));
508 payChanCreate[jss::TransactionType] = jss::PaymentChannelCreate;
510 payChanCreate[jss::Account] = alice.human();
511 payChanCreate[jss::Destination] = gw.human();
512 payChanCreate[jss::Amount] =
514 payChanCreate[sfSettleDelay.jsonName] =
516 payChanCreate[sfPublicKey.jsonName] =
strHex(alice.pk().slice());
517 env(payChanCreate,
sig(alie));
525 payChanFund[jss::TransactionType] = jss::PaymentChannelFund;
527 payChanFund[jss::Account] = alice.human();
528 payChanFund[sfChannel.jsonName] = payChanIndex;
529 payChanFund[jss::Amount] =
531 env(payChanFund,
sig(alie));
536 payChanClaim[jss::TransactionType] = jss::PaymentChannelClaim;
537 payChanClaim[jss::Flags] =
tfClose;
538 payChanClaim[jss::Account] = gw.human();
539 payChanClaim[sfChannel.jsonName] = payChanIndex;
540 payChanClaim[sfPublicKey.jsonName] =
strHex(alice.pk().slice());
571 params[jss::account] = alice.human();
572 params[jss::ledger_index_min] = -1;
573 params[jss::ledger_index_max] = -1;
578 BEAST_EXPECT(result[jss::result][jss::status] ==
"success");
579 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
581 Json::Value const& txs{result[jss::result][jss::transactions]};
588 {0, jss::DepositPreauth, {jss::DepositPreauth}, {jss::Ticket}, {jss::AccountRoot, jss::DirectoryNode}},
589 {1, jss::TicketCreate, {jss::Ticket}, {}, {jss::AccountRoot, jss::DirectoryNode}},
590 {2, jss::CheckCancel, {}, {jss::Check}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
591 {3, jss::CheckCash, {}, {jss::Check}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
592 {4, jss::CheckCreate, {jss::Check}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
593 {5, jss::CheckCreate, {jss::Check}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
594 {6, jss::PaymentChannelClaim, {}, {jss::PayChannel}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
595 {7, jss::PaymentChannelFund, {}, {}, {jss::AccountRoot, jss::PayChannel}},
596 {8, jss::PaymentChannelCreate, {jss::PayChannel}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
597 {9, jss::EscrowCancel, {}, {jss::Escrow}, {jss::AccountRoot, jss::DirectoryNode}},
598 {10, jss::EscrowFinish, {}, {jss::Escrow}, {jss::AccountRoot, jss::DirectoryNode}},
599 {11, jss::EscrowCreate, {jss::Escrow}, {}, {jss::AccountRoot, jss::DirectoryNode}},
600 {12, jss::EscrowCreate, {jss::Escrow}, {}, {jss::AccountRoot, jss::DirectoryNode}},
601 {13, jss::SignerListSet, {jss::SignerList}, {}, {jss::AccountRoot, jss::DirectoryNode}},
602 {14, jss::OfferCancel, {}, {jss::Offer, jss::DirectoryNode}, {jss::AccountRoot, jss::DirectoryNode}},
603 {15, jss::OfferCreate, {jss::Offer, jss::DirectoryNode}, {}, {jss::AccountRoot, jss::DirectoryNode}},
604 {16, jss::TrustSet, {jss::RippleState, jss::DirectoryNode, jss::DirectoryNode}, {}, {jss::AccountRoot, jss::AccountRoot}},
605 {17, jss::SetRegularKey, {}, {}, {jss::AccountRoot}},
606 {18, jss::Payment, {}, {}, {jss::AccountRoot, jss::AccountRoot}},
607 {19, jss::AccountSet, {}, {}, {jss::AccountRoot}},
608 {20, jss::AccountSet, {}, {}, {jss::AccountRoot}},
609 {21, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}},
614 std::size(sanity) == result[jss::result][jss::transactions].size());
616 for (
unsigned int index{0}; index <
std::size(sanity); ++index)
630 using namespace test::jtx;
631 using namespace std::chrono_literals;
637 env.
fund(
XRP(10000), alice, becky);
642 BEAST_EXPECT(env.
closed()->exists(beckyAcctKey));
654 auto const beckyPreDelBalance{env.
balance(becky)};
656 auto const acctDelFee{
drops(env.
current()->fees().increment)};
661 BEAST_EXPECT(!env.
closed()->exists(beckyAcctKey));
673 { 0, jss::Payment, {}, {}, {jss::AccountRoot, jss::AccountRoot}},
674 { 1, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}},
675 { 2, jss::AccountDelete, {}, {jss::AccountRoot}, {jss::AccountRoot}},
676 { 3, jss::AccountSet, {}, {}, {jss::AccountRoot}},
677 { 4, jss::AccountSet, {}, {}, {jss::AccountRoot}},
678 { 5, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}}
686 params[jss::account] = becky.human();
687 params[jss::ledger_index_min] = -1;
688 params[jss::ledger_index_max] = -1;
693 BEAST_EXPECT(result[jss::result][jss::status] ==
"success");
694 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
697 constexpr unsigned int beckyDeletedOffest = 2;
700 result[jss::result][jss::transactions].size() +
703 Json::Value const& txs{result[jss::result][jss::transactions]};
705 for (
unsigned int index = beckyDeletedOffest;
709 checkSanity(txs[index - beckyDeletedOffest], sanity[index]);
722 env(
pay(alice, becky,
XRP(45)));
726 BEAST_EXPECT(env.
closed()->exists(beckyAcctKey));
730 env(
pay(becky, alice,
XRP(20)));
736 params[jss::account] = becky.human();
737 params[jss::ledger_index_min] = -1;
738 params[jss::ledger_index_max] = -1;
743 BEAST_EXPECT(result[jss::result][jss::status] ==
"success");
744 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
747 std::size(sanity) == result[jss::result][jss::transactions].size());
749 Json::Value const& txs{result[jss::result][jss::transactions]};
751 for (
unsigned int index = 0; index <
std::size(sanity); ++index)
Lightweight wrapper to tag static string.
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.
void run() override
Runs the suite.
void checkSanity(Json::Value const &txNode, NodeSanity const &sane)
void testParameters(unsigned int apiVersion)
Immutable cryptographic account descriptor.
A transaction testing environment.
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
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.
NetClock::time_point now()
Returns the current network time.
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)
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an 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.
Set the expected result code for a JTx The test will fail if the code doesn't match.
Set a ticket sequence on a JTx.
@ arrayValue
array value (ordered list)
@ objectValue
object value (collection of name/value pairs).
ErrorInfo const & get_error_info(error_code_i code)
Returns an ErrorInfo that reflects the error code.
Keylet account(AccountID const &id) noexcept
AccountID root.
Keylet check(AccountID const &id, std::uint32_t seq) noexcept
A Check.
Keylet payChan(AccountID const &src, AccountID const &dst, std::uint32_t seq) noexcept
A PaymentChannel.
Json::Value create(A const &account, A const &dest, STAmount const &sendMax)
Create a check.
Json::Value cancel(jtx::Account const &dest, uint256 const &checkId)
Cancel a check.
Json::Value cash(jtx::Account const &dest, uint256 const &checkId, STAmount const &amount)
Cash a check requiring that a specific amount be delivered.
Json::Value auth(Account const &account, Account const &auth)
Preauthorize for deposit.
Json::Value create(Account const &account, std::uint32_t count)
Create one of more tickets.
Json::Value regkey(Account const &account, disabled_t)
Disable the regular key.
Json::Value escrow(AccountID const &account, AccountID const &to, STAmount const &amount)
Json::Value signers(Account const &account, std::uint32_t quorum, std::vector< signer > const &v)
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Json::Value noop(Account const &account)
The null transaction.
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Json::Value acctdelete(Account const &account, Account const &dest)
Delete account.
XRP_t const XRP
Converts to XRP Issue or STAmount.
Json::Value offer_cancel(Account const &account, std::uint32_t offerSeq)
Cancel an offer.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
std::string strHex(FwdIt begin, FwdIt end)
void forAllApiVersions(Fn const &fn, Args &&... args)
constexpr std::uint32_t tfUniversal
std::string to_string(base_uint< Bits, Tag > const &a)
constexpr std::uint32_t tfClose
@ txNode
transaction plus metadata
A pair of SHAMap key and LedgerEntryType.
NodeSanity(int idx, Json::StaticString const &t, std::initializer_list< char const * > c, std::initializer_list< char const * > d, std::initializer_list< char const * > m)
boost::container::flat_set< std::string > deleted
Json::StaticString const & txType
boost::container::flat_set< std::string > modified
boost::container::flat_set< std::string > created