22#include <xrpld/app/misc/Transaction.h>
24#include <xrpl/protocol/Feature.h>
25#include <xrpl/protocol/jss.h>
37 using namespace std::string_literals;
42 tx[sfTransactionType.jsonName].asString();
45 txType == jss::TicketCreate,
46 "Unexpected TransactionType: "s + txType))
50 std::uint32_t const count = {tx[sfTicketCount.jsonName].asUInt()};
56 std::uint32_t const txSeq = {tx[sfSequence.jsonName].asUInt()};
57 std::string const account = tx[sfAccount.jsonName].asString();
61 metadata.
isMember(sfTransactionResult.jsonName) &&
62 metadata[sfTransactionResult.jsonName].
asString() ==
64 "Not metadata for successful TicketCreate."))
67 BEAST_EXPECT(metadata.
isMember(sfAffectedNodes.jsonName));
68 BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].
isArray());
70 bool directoryChanged =
false;
74 for (
Json::Value const& node : metadata[sfAffectedNodes.jsonName])
76 if (node.isMember(sfModifiedNode.jsonName))
78 Json::Value const& modified = node[sfModifiedNode.jsonName];
80 modified[sfLedgerEntryType.jsonName].
asString();
81 if (entryType == jss::AccountRoot)
83 auto const& previousFields =
84 modified[sfPreviousFields.jsonName];
85 auto const& finalFields = modified[sfFinalFields.jsonName];
89 previousFields[sfSequence.jsonName].asUInt();
92 finalFields[sfSequence.jsonName].asUInt();
97 BEAST_EXPECT(acctRootFinalSeq == prevSeq + count);
102 BEAST_EXPECT(prevSeq == txSeq);
104 acctRootFinalSeq == prevSeq + count + 1);
109 txSeq == 0u ? 1u : 0u};
118 bool const unreportedPrevTicketCount = {
119 count == 1 && txSeq == 0};
122 if (unreportedPrevTicketCount)
127 !previousFields.isMember(sfOwnerCount.jsonName));
133 previousFields[sfOwnerCount.jsonName].asUInt()};
136 finalFields[sfOwnerCount.jsonName].asUInt()};
139 prevCount + count - consumedTickets == finalCount);
143 BEAST_EXPECT(finalFields.isMember(sfTicketCount.jsonName));
145 if (unreportedPrevTicketCount)
150 !previousFields.isMember(sfTicketCount.jsonName));
157 previousFields.isMember(sfTicketCount.jsonName)
158 ? previousFields[sfTicketCount.jsonName]
164 previousFields.isMember(sfTicketCount.jsonName));
167 startCount + count - consumedTickets ==
168 finalFields[sfTicketCount.jsonName]);
171 else if (entryType == jss::DirectoryNode)
173 directoryChanged =
true;
178 "Unexpected modified node: "s + entryType,
183 else if (node.isMember(sfCreatedNode.jsonName))
185 Json::Value const& created = node[sfCreatedNode.jsonName];
187 created[sfLedgerEntryType.jsonName].
asString();
188 if (entryType == jss::Ticket)
190 auto const& newFields = created[sfNewFields.jsonName];
193 newFields[sfAccount.jsonName].asString() == account);
195 newFields[sfTicketSequence.jsonName].asUInt());
197 else if (entryType == jss::DirectoryNode)
199 directoryChanged =
true;
204 "Unexpected created node: "s + entryType,
209 else if (node.isMember(sfDeletedNode.jsonName))
211 Json::Value const& deleted = node[sfDeletedNode.jsonName];
213 deleted[sfLedgerEntryType.jsonName].asString();
215 if (entryType == jss::Ticket)
218 BEAST_EXPECT(txSeq == 0);
221 auto const& finalFields = deleted[sfFinalFields.jsonName];
223 finalFields[sfAccount.jsonName].asString() == account);
227 finalFields[sfTicketSequence.jsonName].asUInt() ==
228 tx[sfTicketSequence.jsonName].asUInt());
234 "Unexpected node type in TicketCreate metadata.",
239 BEAST_EXPECT(directoryChanged);
242 BEAST_EXPECT(ticketSeqs.
size() == count);
247 BEAST_EXPECT(*ticketSeqs.
rbegin() == acctRootFinalSeq - 1);
282 BEAST_EXPECT(tx[sfSequence.jsonName].asUInt() == 0);
283 std::string const account{tx[sfAccount.jsonName].asString()};
285 tx.isMember(sfTicketSequence.jsonName),
286 "Not metadata for a ticket consuming transaction."))
289 std::uint32_t const ticketSeq{tx[sfTicketSequence.jsonName].asUInt()};
293 metadata.isMember(sfTransactionResult.jsonName),
294 "Metadata is missing TransactionResult."))
299 metadata[sfTransactionResult.jsonName].asString()};
301 transactionResult ==
"tesSUCCESS" ||
302 transactionResult.compare(0, 3,
"tec") == 0,
303 transactionResult +
" neither tesSUCCESS nor tec"))
307 BEAST_EXPECT(metadata.isMember(sfAffectedNodes.jsonName));
308 BEAST_EXPECT(metadata[sfAffectedNodes.jsonName].isArray());
310 bool acctRootFound{
false};
312 int ticketsRemoved{0};
313 for (
Json::Value const& node : metadata[sfAffectedNodes.jsonName])
315 if (node.isMember(sfModifiedNode.jsonName))
317 Json::Value const& modified{node[sfModifiedNode.jsonName]};
319 modified[sfLedgerEntryType.jsonName].asString();
320 if (entryType ==
"AccountRoot" &&
321 modified[sfFinalFields.jsonName][sfAccount.jsonName]
322 .asString() == account)
324 acctRootFound =
true;
326 auto const& previousFields =
327 modified[sfPreviousFields.jsonName];
328 auto const& finalFields = modified[sfFinalFields.jsonName];
330 acctRootSeq = finalFields[sfSequence.jsonName].asUInt();
336 previousFields.isMember(sfTicketCount.jsonName),
337 "AccountRoot previous is missing TicketCount"))
341 previousFields[sfTicketCount.jsonName].asUInt();
343 BEAST_EXPECT(prevTicketCount > 0);
344 if (prevTicketCount == 1)
346 !finalFields.isMember(sfTicketCount.jsonName));
349 finalFields.isMember(sfTicketCount.jsonName) &&
350 finalFields[sfTicketCount.jsonName].asUInt() ==
351 prevTicketCount - 1);
354 else if (node.isMember(sfDeletedNode.jsonName))
356 Json::Value const& deleted{node[sfDeletedNode.jsonName]};
358 deleted[sfLedgerEntryType.jsonName].asString()};
360 if (entryType == jss::Ticket)
364 deleted[sfFinalFields.jsonName][sfAccount.jsonName]
365 .asString() == account);
369 deleted[sfFinalFields.jsonName]
370 [sfTicketSequence.jsonName]
371 .asUInt() == ticketSeq);
377 BEAST_EXPECT(acctRootFound);
378 BEAST_EXPECT(ticketsRemoved == 1);
379 BEAST_EXPECT(ticketSeq < acctRootSeq);
387 using namespace test::jtx;
395 env(
noop(env.master),
397 seq(env.seq(env.master)),
402 for (
int i = 0; i < 8; ++i)
405 env.enableFeature(featureTicketBatch);
431 testcase(
"Create Tickets that fail Preflight");
433 using namespace test::jtx;
478 testcase(
"Create Tickets that fail Preclaim");
480 using namespace test::jtx;
488 json(jss::Sequence, 1),
497 env.fund(
XRP(100000), alice);
536 env.fund(
XRP(100000), alice);
570 testcase(
"Create Ticket Insufficient Reserve");
572 using namespace test::jtx;
577 env.fund(env.current()->fees().accountReserve(1) -
drops(1), alice);
588 env.current()->fees().accountReserve(1) - env.balance(alice)));
601 env.current()->fees().accountReserve(250) -
drops(1) -
602 env.balance(alice)));
616 env.current()->fees().accountReserve(250) - env.balance(alice)));
624 BEAST_EXPECT(ticketSeq + 249 == env.seq(alice));
632 using namespace test::jtx;
636 env.fund(
XRP(10000), alice);
645 BEAST_EXPECT(ticketSeq_AB + 2 == env.seq(alice));
653 BEAST_EXPECT(ticketSeq_C + 1 == env.seq(alice));
661 BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
668 BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
674 BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
680 BEAST_EXPECT(ticketSeq_DE + 2 == env.seq(alice));
696 BEAST_EXPECT(ticketSeq_F + 1 == env.seq(alice));
707 json(R
"({"AccountTxnID": "0"})"),
726 testcase(
"Transaction Database With Tickets");
728 using namespace test::jtx;
732 env.fund(
XRP(10000), alice);
736 auto getTxID = [&env,
this]() ->
uint256 {
738 if (!BEAST_EXPECTS(tx,
"Transaction not found"))
739 Throw<std::invalid_argument>(
"Invalid transaction ID");
741 return tx->getTransactionID();
756 uint256 const txHash_1{getTxID()};
759 ticketSeq += ticketCount;
761 uint256 const txHash_2{getTxID()};
764 uint256 const txHash_3{getTxID()};
767 uint256 const txHash_4{getTxID()};
774 uint256 const txHash_5{getTxID()};
777 uint256 const txHash_6{getTxID()};
780 uint256 const txHash_7{getTxID()};
783 uint256 const txHash_8{getTxID()};
793 auto checkTxFromDB = [&env,
this](
807 if (
auto txPtr = std::get_if<TxPair>(&maybeTx))
810 BEAST_EXPECT(tx->getLedger() == ledgerSeq);
812 BEAST_EXPECT((*sttx)[sfSequence] == txSeq);
814 BEAST_EXPECT((*sttx)[sfTicketSequence] == *ticketSeq);
815 BEAST_EXPECT((*sttx)[sfTransactionType] == txType);
819 fail(
"Expected transaction was not found");
824 checkTxFromDB(txHash_1, 4, 4, {}, ttTICKET_CREATE);
825 checkTxFromDB(txHash_2, 4, 0, 13, ttACCOUNT_SET);
826 checkTxFromDB(txHash_3, 4, 0, 12, ttPAYMENT);
827 checkTxFromDB(txHash_4, 4, 0, 11, ttDEPOSIT_PREAUTH);
829 checkTxFromDB(txHash_5, 5, 0, 10, ttPAYMENT);
830 checkTxFromDB(txHash_6, 5, 0, 9, ttPAYMENT);
831 checkTxFromDB(txHash_7, 5, 0, 8, ttDEPOSIT_PREAUTH);
832 checkTxFromDB(txHash_8, 5, 0, 7, ttACCOUNT_SET);
842 testcase(
"Sign with TicketSequence");
844 using namespace test::jtx;
848 env.fund(
XRP(10000), alice);
857 BEAST_EXPECT(ticketSeq + 2 == env.seq(alice));
866 tx[jss::tx_json] =
noop(alice);
867 tx[jss::tx_json][sfTicketSequence.jsonName] = ticketSeq;
871 BEAST_EXPECT(!tx[jss::tx_json].isMember(sfSequence.jsonName));
878 if (BEAST_EXPECT(jr[jss::result][jss::tx_json].isMember(
879 sfSequence.jsonName)))
882 jr[jss::result][jss::tx_json][sfSequence.jsonName] == 0);
890 env.rpc(
"submit", jr[jss::result][jss::tx_blob].asString());
901 tx[jss::tx_json] =
noop(alice);
902 tx[jss::tx_json][sfTicketSequence.jsonName] = ticketSeq + 1;
906 BEAST_EXPECT(!tx[jss::tx_json].isMember(sfSequence.jsonName));
913 if (BEAST_EXPECT(jr[jss::result][jss::tx_json].isMember(
914 sfSequence.jsonName)))
917 jr[jss::result][jss::tx_json][sfSequence.jsonName] == 0);
931 testcase(
"Fix both Seq and Ticket");
934 using namespace test::jtx;
939 env.fund(
XRP(10000), alice);
947 BEAST_EXPECT(ticketSeq == env.seq(alice) + 1);
963 env.fund(
XRP(10000), alice);
971 BEAST_EXPECT(ticketSeq + 1 == env.seq(alice));
984 BEAST_EXPECT(ticketSeq + 1 == env.seq(alice));
T adjacent_find(T... args)
std::string asString() const
Returns the unquoted string value.
bool isMember(char const *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 testFixBothSeqAndTicket()
void testTicketInsufficientReserve()
void testTransactionDatabaseWithTickets()
void testSignWithTicketSequence()
void checkTicketCreateMeta(test::jtx::Env &env)
Validate metadata for a successful CreateTicket transaction.
void testTicketCreatePreclaimFail()
void testTicketCreatePreflightFail()
void testTicketNotEnabled()
void checkTicketConsumeMeta(test::jtx::Env &env)
Validate metadata for a ticket using transaction.
void run() override
Runs the suite.
static std::variant< std::pair< std::shared_ptr< Transaction >, std::shared_ptr< TxMeta > >, TxSearched > load(uint256 const &id, Application &app, error_code_i &ec)
Immutable cryptographic account descriptor.
static Account const master
The master account.
A transaction testing environment.
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
std::shared_ptr< STObject const > meta()
Return metadata for the last JTx.
Match the number of items in the account's owner directory.
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.
@ objectValue
object value (collection of name/value pairs).
Json::Value unauth(Account const &account, Account const &unauth)
Remove preauthorization for deposit.
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.
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 fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
owner_count< ltTICKET > tickets
Match the number of tickets on the account.
XRP_t const XRP
Converts to XRP Issue or STAmount.
FeatureBitset supported_amendments()
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
TxType
Transaction type identifiers.
constexpr std::uint32_t asfDisableMaster
@ tecINSUFFICIENT_RESERVE
std::string to_string(base_uint< Bits, Tag > const &a)
constexpr std::uint32_t tfFullyCanonicalSig
Transaction flags.
constexpr std::uint32_t tfSell
Seed generateSeed(std::string const &passPhrase)
Generate a seed deterministically.
Set the sequence number on a JTx.