rippled
Loading...
Searching...
No Matches
AccountTx_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 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/envconfig.h>
22
23#include <xrpl/beast/unit_test.h>
24#include <xrpl/beast/unit_test/suite.h>
25#include <xrpl/protocol/ErrorCodes.h>
26#include <xrpl/protocol/TxFlags.h>
27#include <xrpl/protocol/jss.h>
28
29#include <boost/container/flat_set.hpp>
30
31namespace ripple {
32
33namespace test {
34
36{
37 // A data structure used to describe the basic structure of a
38 // transactions array node as returned by the account_tx RPC command.
40 {
41 int const index;
43 boost::container::flat_set<std::string> created;
44 boost::container::flat_set<std::string> deleted;
45 boost::container::flat_set<std::string> modified;
46
48 int idx,
49 Json::StaticString const& t,
53 : index(idx), txType(t)
54 {
55 auto buildSet = [](auto&& init) {
56 boost::container::flat_set<std::string> r;
57 r.reserve(init.size());
58 for (auto&& s : init)
59 r.insert(s);
60 return r;
61 };
62
63 created = buildSet(c);
64 deleted = buildSet(d);
65 modified = buildSet(m);
66 }
67 };
68
69 // A helper method tests can use to validate returned JSON vs NodeSanity.
70 void
72 {
73 BEAST_EXPECT(txNode[jss::validated].asBool() == true);
74 BEAST_EXPECT(
75 txNode[jss::tx][sfTransactionType.jsonName].asString() ==
76 sane.txType);
77
78 // Make sure all of the expected node types are present.
79 boost::container::flat_set<std::string> createdNodes;
80 boost::container::flat_set<std::string> deletedNodes;
81 boost::container::flat_set<std::string> modifiedNodes;
82
83 for (Json::Value const& metaNode :
84 txNode[jss::meta][sfAffectedNodes.jsonName])
85 {
86 if (metaNode.isMember(sfCreatedNode.jsonName))
87 createdNodes.insert(
88 metaNode[sfCreatedNode.jsonName][sfLedgerEntryType.jsonName]
89 .asString());
90
91 else if (metaNode.isMember(sfDeletedNode.jsonName))
92 deletedNodes.insert(
93 metaNode[sfDeletedNode.jsonName][sfLedgerEntryType.jsonName]
94 .asString());
95
96 else if (metaNode.isMember(sfModifiedNode.jsonName))
97 modifiedNodes.insert(metaNode[sfModifiedNode.jsonName]
98 [sfLedgerEntryType.jsonName]
99 .asString());
100
101 else
102 fail(
103 "Unexpected or unlabeled node type in metadata.",
104 __FILE__,
105 __LINE__);
106 }
107
108 BEAST_EXPECT(createdNodes == sane.created);
109 BEAST_EXPECT(deletedNodes == sane.deleted);
110 BEAST_EXPECT(modifiedNodes == sane.modified);
111 };
112
113 void
114 testParameters(unsigned int apiVersion)
115 {
116 testcase("Parameters APIv" + std::to_string(apiVersion));
117 using namespace test::jtx;
118
119 Env env(*this, envconfig([](std::unique_ptr<Config> cfg) {
120 cfg->FEES.reference_fee = 10;
121 return cfg;
122 }));
123 Account A1{"A1"};
124 env.fund(XRP(10000), A1);
125 env.close();
126
127 // Ledger 3 has the two txs associated with funding the account
128 // All other ledgers have no txs
129
130 auto hasTxs = [apiVersion](Json::Value const& j) {
131 switch (apiVersion)
132 {
133 case 1:
134 return j.isMember(jss::result) &&
135 (j[jss::result][jss::status] == "success") &&
136 (j[jss::result][jss::transactions].size() == 2) &&
137 (j[jss::result][jss::transactions][0u][jss::tx]
138 [jss::TransactionType] == jss::AccountSet) &&
139 (j[jss::result][jss::transactions][1u][jss::tx]
140 [jss::TransactionType] == jss::Payment) &&
141 (j[jss::result][jss::transactions][1u][jss::tx]
142 [jss::DeliverMax] == "10000000010") &&
143 (j[jss::result][jss::transactions][1u][jss::tx]
144 [jss::Amount] ==
145 j[jss::result][jss::transactions][1u][jss::tx]
146 [jss::DeliverMax]);
147 case 2:
148 case 3:
149 if (j.isMember(jss::result) &&
150 (j[jss::result][jss::status] == "success") &&
151 (j[jss::result][jss::transactions].size() == 2) &&
152 (j[jss::result][jss::transactions][0u][jss::tx_json]
153 [jss::TransactionType] == jss::AccountSet))
154 {
155 auto const& payment =
156 j[jss::result][jss::transactions][1u];
157
158 return (payment.isMember(jss::tx_json)) &&
159 (payment[jss::tx_json][jss::TransactionType] ==
160 jss::Payment) &&
161 (payment[jss::tx_json][jss::DeliverMax] ==
162 "10000000010") &&
163 (!payment[jss::tx_json].isMember(jss::Amount)) &&
164 (!payment[jss::tx_json].isMember(jss::hash)) &&
165 (payment[jss::hash] ==
166 "9F3085D85F472D1CC29627F260DF68EDE59D42D1D0C33E345"
167 "ECF0D4CE981D0A8") &&
168 (payment[jss::validated] == true) &&
169 (payment[jss::ledger_index] == 3) &&
170 (payment[jss::ledger_hash] ==
171 "5476DCD816EA04CBBA57D47BBF1FC58A5217CC93A5ADD79CB"
172 "580A5AFDD727E33") &&
173 (payment[jss::close_time_iso] ==
174 "2000-01-01T00:00:10Z");
175 }
176 else
177 return false;
178
179 default:
180 return false;
181 }
182 };
183
184 auto noTxs = [](Json::Value const& j) {
185 return j.isMember(jss::result) &&
186 (j[jss::result][jss::status] == "success") &&
187 (j[jss::result][jss::transactions].size() == 0);
188 };
189
190 auto isErr = [](Json::Value const& j, error_code_i code) {
191 return j.isMember(jss::result) &&
192 j[jss::result].isMember(jss::error) &&
193 j[jss::result][jss::error] == RPC::get_error_info(code).token;
194 };
195
196 Json::Value jParms;
197 jParms[jss::api_version] = apiVersion;
198
199 BEAST_EXPECT(isErr(
200 env.rpc("json", "account_tx", to_string(jParms)),
202
203 jParms[jss::account] = "0xDEADBEEF";
204
205 BEAST_EXPECT(isErr(
206 env.rpc("json", "account_tx", to_string(jParms)),
208
209 jParms[jss::account] = A1.human();
210 BEAST_EXPECT(hasTxs(
211 env.rpc(apiVersion, "json", "account_tx", to_string(jParms))));
212
213 // Ledger min/max index
214 {
215 Json::Value p{jParms};
216 p[jss::ledger_index_min] = -1;
217 p[jss::ledger_index_max] = -1;
218 BEAST_EXPECT(hasTxs(
219 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
220
221 p[jss::ledger_index_min] = 0;
222 p[jss::ledger_index_max] = 100;
223 if (apiVersion < 2u)
224 BEAST_EXPECT(hasTxs(
225 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
226 else
227 BEAST_EXPECT(isErr(
228 env.rpc("json", "account_tx", to_string(p)),
230
231 p[jss::ledger_index_min] = 1;
232 p[jss::ledger_index_max] = 2;
233 if (apiVersion < 2u)
234 BEAST_EXPECT(
235 noTxs(env.rpc("json", "account_tx", to_string(p))));
236 else
237 BEAST_EXPECT(isErr(
238 env.rpc("json", "account_tx", to_string(p)),
240
241 p[jss::ledger_index_min] = 2;
242 p[jss::ledger_index_max] = 1;
243 BEAST_EXPECT(isErr(
244 env.rpc("json", "account_tx", to_string(p)),
245 (apiVersion == 1 ? rpcLGR_IDXS_INVALID
247 }
248 // Ledger index min only
249 {
250 Json::Value p{jParms};
251 p[jss::ledger_index_min] = -1;
252 BEAST_EXPECT(hasTxs(
253 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
254
255 p[jss::ledger_index_min] = 1;
256 if (apiVersion < 2u)
257 BEAST_EXPECT(hasTxs(
258 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
259 else
260 BEAST_EXPECT(isErr(
261 env.rpc("json", "account_tx", to_string(p)),
263
264 p[jss::ledger_index_min] = env.current()->info().seq;
265 BEAST_EXPECT(isErr(
266 env.rpc("json", "account_tx", to_string(p)),
267 (apiVersion == 1 ? rpcLGR_IDXS_INVALID
269 }
270
271 // Ledger index max only
272 {
273 Json::Value p{jParms};
274 p[jss::ledger_index_max] = -1;
275 BEAST_EXPECT(hasTxs(
276 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
277
278 p[jss::ledger_index_max] = env.current()->info().seq;
279 if (apiVersion < 2u)
280 BEAST_EXPECT(hasTxs(
281 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
282 else
283 BEAST_EXPECT(isErr(
284 env.rpc("json", "account_tx", to_string(p)),
286
287 p[jss::ledger_index_max] = 3;
288 BEAST_EXPECT(hasTxs(
289 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
290
291 p[jss::ledger_index_max] = env.closed()->info().seq;
292 BEAST_EXPECT(hasTxs(
293 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
294
295 p[jss::ledger_index_max] = env.closed()->info().seq - 1;
296 BEAST_EXPECT(noTxs(env.rpc("json", "account_tx", to_string(p))));
297 }
298
299 // Ledger Sequence
300 {
301 Json::Value p{jParms};
302
303 p[jss::ledger_index] = env.closed()->info().seq;
304 BEAST_EXPECT(hasTxs(
305 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
306
307 p[jss::ledger_index] = env.closed()->info().seq - 1;
308 BEAST_EXPECT(noTxs(env.rpc("json", "account_tx", to_string(p))));
309
310 p[jss::ledger_index] = env.current()->info().seq;
311 BEAST_EXPECT(isErr(
312 env.rpc("json", "account_tx", to_string(p)),
314
315 p[jss::ledger_index] = env.current()->info().seq + 1;
316 BEAST_EXPECT(isErr(
317 env.rpc("json", "account_tx", to_string(p)), rpcLGR_NOT_FOUND));
318 }
319
320 // Ledger Hash
321 {
322 Json::Value p{jParms};
323
324 p[jss::ledger_hash] = to_string(env.closed()->info().hash);
325 BEAST_EXPECT(hasTxs(
326 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
327
328 p[jss::ledger_hash] = to_string(env.closed()->info().parentHash);
329 BEAST_EXPECT(noTxs(env.rpc("json", "account_tx", to_string(p))));
330 }
331
332 // Ledger index max/min/index all specified
333 // ERRORS out with invalid Parenthesis
334 {
335 jParms[jss::account] = "0xDEADBEEF";
336 jParms[jss::account] = A1.human();
337 Json::Value p{jParms};
338
339 p[jss::ledger_index_max] = -1;
340 p[jss::ledger_index_min] = -1;
341 p[jss::ledger_index] = -1;
342
343 if (apiVersion < 2u)
344 BEAST_EXPECT(hasTxs(
345 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
346 else
347 BEAST_EXPECT(isErr(
348 env.rpc("json", "account_tx", to_string(p)),
350 }
351
352 // Ledger index max only
353 {
354 Json::Value p{jParms};
355 p[jss::ledger_index_max] = env.current()->info().seq;
356 if (apiVersion < 2u)
357 BEAST_EXPECT(hasTxs(
358 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
359 else
360 BEAST_EXPECT(isErr(
361 env.rpc("json", "account_tx", to_string(p)),
363 }
364 // test account non-string
365 {
366 auto testInvalidAccountParam = [&](auto const& param) {
367 Json::Value params;
368 params[jss::account] = param;
369 auto jrr = env.rpc(
370 "json", "account_tx", to_string(params))[jss::result];
371 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
372 BEAST_EXPECT(
373 jrr[jss::error_message] == "Invalid field 'account'.");
374 };
375
376 testInvalidAccountParam(1);
377 testInvalidAccountParam(1.1);
378 testInvalidAccountParam(true);
379 testInvalidAccountParam(Json::Value(Json::nullValue));
380 testInvalidAccountParam(Json::Value(Json::objectValue));
381 testInvalidAccountParam(Json::Value(Json::arrayValue));
382 }
383 // test binary and forward for bool/non bool values
384 {
385 Json::Value p{jParms};
386 p[jss::binary] = "asdf";
387 if (apiVersion < 2u)
388 {
389 Json::Value result{env.rpc("json", "account_tx", to_string(p))};
390 BEAST_EXPECT(result[jss::result][jss::status] == "success");
391 }
392 else
393 BEAST_EXPECT(isErr(
394 env.rpc("json", "account_tx", to_string(p)),
396
397 p[jss::binary] = true;
398 Json::Value result{env.rpc("json", "account_tx", to_string(p))};
399 BEAST_EXPECT(result[jss::result][jss::status] == "success");
400
401 p[jss::forward] = "true";
402 if (apiVersion < 2u)
403 BEAST_EXPECT(result[jss::result][jss::status] == "success");
404 else
405 BEAST_EXPECT(isErr(
406 env.rpc("json", "account_tx", to_string(p)),
408
409 p[jss::forward] = false;
410 result = env.rpc("json", "account_tx", to_string(p));
411 BEAST_EXPECT(result[jss::result][jss::status] == "success");
412 }
413 }
414
415 void
417 {
418 testcase("Contents");
419
420 // Get results for all transaction types that can be associated
421 // with an account. Start by generating all transaction types.
422 using namespace test::jtx;
423 using namespace std::chrono_literals;
424
425 Env env(*this);
426 Account const alice{"alice"};
427 Account const alie{"alie"};
428 Account const gw{"gw"};
429 auto const USD{gw["USD"]};
430
431 env.fund(XRP(1000000), alice, gw);
432 env.close();
433
434 // AccountSet
435 env(noop(alice));
436
437 // Payment
438 env(pay(alice, gw, XRP(100)));
439
440 // Regular key set
441 env(regkey(alice, alie));
442 env.close();
443
444 // Trust and Offers
445 env(trust(alice, USD(200)), sig(alie));
446 std::uint32_t const offerSeq{env.seq(alice)};
447 env(offer(alice, USD(50), XRP(150)), sig(alie));
448 env.close();
449
450 env(offer_cancel(alice, offerSeq), sig(alie));
451 env.close();
452
453 // SignerListSet
454 env(signers(alice, 1, {{"bogie", 1}, {"demon", 1}}), sig(alie));
455
456 // Escrow
457 {
458 // Create an escrow. Requires either a CancelAfter or FinishAfter.
459 auto escrow = [](Account const& account,
460 Account const& to,
461 STAmount const& amount) {
462 Json::Value escro;
463 escro[jss::TransactionType] = jss::EscrowCreate;
464 escro[jss::Account] = account.human();
465 escro[jss::Destination] = to.human();
466 escro[jss::Amount] = amount.getJson(JsonOptions::none);
467 return escro;
468 };
469
470 NetClock::time_point const nextTime{env.now() + 2s};
471
472 Json::Value escrowWithFinish{escrow(alice, alice, XRP(500))};
473 escrowWithFinish[sfFinishAfter.jsonName] =
474 nextTime.time_since_epoch().count();
475
476 std::uint32_t const escrowFinishSeq{env.seq(alice)};
477 env(escrowWithFinish, sig(alie));
478
479 Json::Value escrowWithCancel{escrow(alice, alice, XRP(500))};
480 escrowWithCancel[sfFinishAfter.jsonName] =
481 nextTime.time_since_epoch().count();
482 escrowWithCancel[sfCancelAfter.jsonName] =
483 nextTime.time_since_epoch().count() + 1;
484
485 std::uint32_t const escrowCancelSeq{env.seq(alice)};
486 env(escrowWithCancel, sig(alie));
487 env.close();
488
489 {
490 Json::Value escrowFinish;
491 escrowFinish[jss::TransactionType] = jss::EscrowFinish;
492 escrowFinish[jss::Account] = alice.human();
493 escrowFinish[sfOwner.jsonName] = alice.human();
494 escrowFinish[sfOfferSequence.jsonName] = escrowFinishSeq;
495 env(escrowFinish, sig(alie));
496 }
497 {
498 Json::Value escrowCancel;
499 escrowCancel[jss::TransactionType] = jss::EscrowCancel;
500 escrowCancel[jss::Account] = alice.human();
501 escrowCancel[sfOwner.jsonName] = alice.human();
502 escrowCancel[sfOfferSequence.jsonName] = escrowCancelSeq;
503 env(escrowCancel, sig(alie));
504 }
505 env.close();
506 }
507
508 // PayChan
509 {
510 std::uint32_t payChanSeq{env.seq(alice)};
511 Json::Value payChanCreate;
512 payChanCreate[jss::TransactionType] = jss::PaymentChannelCreate;
513 payChanCreate[jss::Account] = alice.human();
514 payChanCreate[jss::Destination] = gw.human();
515 payChanCreate[jss::Amount] =
516 XRP(500).value().getJson(JsonOptions::none);
517 payChanCreate[sfSettleDelay.jsonName] =
519 payChanCreate[sfPublicKey.jsonName] = strHex(alice.pk().slice());
520 env(payChanCreate, sig(alie));
521 env.close();
522
523 std::string const payChanIndex{
524 strHex(keylet::payChan(alice, gw, payChanSeq).key)};
525
526 {
527 Json::Value payChanFund;
528 payChanFund[jss::TransactionType] = jss::PaymentChannelFund;
529 payChanFund[jss::Account] = alice.human();
530 payChanFund[sfChannel.jsonName] = payChanIndex;
531 payChanFund[jss::Amount] =
532 XRP(200).value().getJson(JsonOptions::none);
533 env(payChanFund, sig(alie));
534 env.close();
535 }
536 {
537 Json::Value payChanClaim;
538 payChanClaim[jss::TransactionType] = jss::PaymentChannelClaim;
539 payChanClaim[jss::Flags] = tfClose;
540 payChanClaim[jss::Account] = gw.human();
541 payChanClaim[sfChannel.jsonName] = payChanIndex;
542 payChanClaim[sfPublicKey.jsonName] = strHex(alice.pk().slice());
543 env(payChanClaim);
544 env.close();
545 }
546 }
547
548 // Check
549 {
550 auto const aliceCheckId = keylet::check(alice, env.seq(alice)).key;
551 env(check::create(alice, gw, XRP(300)), sig(alie));
552
553 auto const gwCheckId = keylet::check(gw, env.seq(gw)).key;
554 env(check::create(gw, alice, XRP(200)));
555 env.close();
556
557 env(check::cash(alice, gwCheckId, XRP(200)), sig(alie));
558 env(check::cancel(alice, aliceCheckId), sig(alie));
559 env.close();
560 }
561 {
562 // Deposit preauthorization with a Ticket.
563 std::uint32_t const tktSeq{env.seq(alice) + 1};
564 env(ticket::create(alice, 1), sig(alie));
565 env.close();
566
567 env(deposit::auth(alice, gw), ticket::use(tktSeq), sig(alie));
568 env.close();
569 }
570
571 // Setup is done. Look at the transactions returned by account_tx.
572 Json::Value params;
573 params[jss::account] = alice.human();
574 params[jss::ledger_index_min] = -1;
575 params[jss::ledger_index_max] = -1;
576
577 Json::Value const result{
578 env.rpc("json", "account_tx", to_string(params))};
579
580 BEAST_EXPECT(result[jss::result][jss::status] == "success");
581 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
582
583 Json::Value const& txs{result[jss::result][jss::transactions]};
584
585 // clang-format off
586 // Do a sanity check on each returned transaction. They should
587 // be returned in the reverse order of application to the ledger.
588 static const NodeSanity sanity[]{
589 // txType, created, deleted, modified
590 {0, jss::DepositPreauth, {jss::DepositPreauth}, {jss::Ticket}, {jss::AccountRoot, jss::DirectoryNode}},
591 {1, jss::TicketCreate, {jss::Ticket}, {}, {jss::AccountRoot, jss::DirectoryNode}},
592 {2, jss::CheckCancel, {}, {jss::Check}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
593 {3, jss::CheckCash, {}, {jss::Check}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
594 {4, jss::CheckCreate, {jss::Check}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
595 {5, jss::CheckCreate, {jss::Check}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
596 {6, jss::PaymentChannelClaim, {}, {jss::PayChannel}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
597 {7, jss::PaymentChannelFund, {}, {}, {jss::AccountRoot, jss::PayChannel}},
598 {8, jss::PaymentChannelCreate, {jss::PayChannel}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
599 {9, jss::EscrowCancel, {}, {jss::Escrow}, {jss::AccountRoot, jss::DirectoryNode}},
600 {10, jss::EscrowFinish, {}, {jss::Escrow}, {jss::AccountRoot, jss::DirectoryNode}},
601 {11, jss::EscrowCreate, {jss::Escrow}, {}, {jss::AccountRoot, jss::DirectoryNode}},
602 {12, jss::EscrowCreate, {jss::Escrow}, {}, {jss::AccountRoot, jss::DirectoryNode}},
603 {13, jss::SignerListSet, {jss::SignerList}, {}, {jss::AccountRoot, jss::DirectoryNode}},
604 {14, jss::OfferCancel, {}, {jss::Offer, jss::DirectoryNode}, {jss::AccountRoot, jss::DirectoryNode}},
605 {15, jss::OfferCreate, {jss::Offer, jss::DirectoryNode}, {}, {jss::AccountRoot, jss::DirectoryNode}},
606 {16, jss::TrustSet, {jss::RippleState, jss::DirectoryNode, jss::DirectoryNode}, {}, {jss::AccountRoot, jss::AccountRoot}},
607 {17, jss::SetRegularKey, {}, {}, {jss::AccountRoot}},
608 {18, jss::Payment, {}, {}, {jss::AccountRoot, jss::AccountRoot}},
609 {19, jss::AccountSet, {}, {}, {jss::AccountRoot}},
610 {20, jss::AccountSet, {}, {}, {jss::AccountRoot}},
611 {21, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}},
612 };
613 // clang-format on
614
615 BEAST_EXPECT(
616 std::size(sanity) == result[jss::result][jss::transactions].size());
617
618 for (unsigned int index{0}; index < std::size(sanity); ++index)
619 {
620 checkSanity(txs[index], sanity[index]);
621 }
622 }
623
624 void
626 {
627 testcase("AccountDelete");
628
629 // Verify that if an account is resurrected then the account_tx RPC
630 // command still recovers all transactions on that account before
631 // and after resurrection.
632 using namespace test::jtx;
633 using namespace std::chrono_literals;
634
635 Env env(*this);
636 Account const alice{"alice"};
637 Account const becky{"becky"};
638
639 env.fund(XRP(10000), alice, becky);
640 env.close();
641
642 // Verify that becky's account root is present.
643 Keylet const beckyAcctKey{keylet::account(becky.id())};
644 BEAST_EXPECT(env.closed()->exists(beckyAcctKey));
645
646 // becky does an AccountSet .
647 env(noop(becky));
648
649 // Close enough ledgers to be able to delete becky's account.
650 std::uint32_t const ledgerCount{
651 env.current()->seq() + 257 - env.seq(becky)};
652
653 for (std::uint32_t i = 0; i < ledgerCount; ++i)
654 env.close();
655
656 auto const beckyPreDelBalance{env.balance(becky)};
657
658 auto const acctDelFee{drops(env.current()->fees().increment)};
659 env(acctdelete(becky, alice), fee(acctDelFee));
660 env.close();
661
662 // Verify that becky's account root is gone.
663 BEAST_EXPECT(!env.closed()->exists(beckyAcctKey));
664 env.close();
665
666 // clang-format off
667 // Do a sanity check on each returned transaction. They should
668 // be returned in the reverse order of application to the ledger.
669 //
670 // Note that the first two transactions in sanity have not occurred
671 // yet. We'll see those after becky's account is resurrected.
672 static const NodeSanity sanity[]
673 {
674 // txType, created, deleted, modified
675/* becky pays alice */ { 0, jss::Payment, {}, {}, {jss::AccountRoot, jss::AccountRoot}},
676/* alice resurrects becky's acct */ { 1, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}},
677/* becky deletes her account */ { 2, jss::AccountDelete, {}, {jss::AccountRoot}, {jss::AccountRoot}},
678/* becky's noop */ { 3, jss::AccountSet, {}, {}, {jss::AccountRoot}},
679/* "fund" sets flags */ { 4, jss::AccountSet, {}, {}, {jss::AccountRoot}},
680/* "fund" creates becky's acct */ { 5, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}}
681 };
682 // clang-format on
683
684 // Verify that we can recover becky's account_tx information even
685 // after the account is deleted.
686 {
687 Json::Value params;
688 params[jss::account] = becky.human();
689 params[jss::ledger_index_min] = -1;
690 params[jss::ledger_index_max] = -1;
691
692 Json::Value const result{
693 env.rpc("json", "account_tx", to_string(params))};
694
695 BEAST_EXPECT(result[jss::result][jss::status] == "success");
696 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
697
698 // The first two transactions listed in sanity haven't happened yet.
699 constexpr unsigned int beckyDeletedOffest = 2;
700 BEAST_EXPECT(
701 std::size(sanity) ==
702 result[jss::result][jss::transactions].size() +
703 beckyDeletedOffest);
704
705 Json::Value const& txs{result[jss::result][jss::transactions]};
706
707 for (unsigned int index = beckyDeletedOffest;
708 index < std::size(sanity);
709 ++index)
710 {
711 checkSanity(txs[index - beckyDeletedOffest], sanity[index]);
712 }
713 }
714
715 // All it takes is a large enough XRP payment to resurrect
716 // becky's account. Try too small a payment.
717 env(pay(alice,
718 becky,
719 drops(env.current()->fees().accountReserve(0)) - XRP(1)),
721 env.close();
722
723 // Actually resurrect becky's account.
724 env(pay(alice, becky, XRP(45)));
725 env.close();
726
727 // becky's account root should be back.
728 BEAST_EXPECT(env.closed()->exists(beckyAcctKey));
729 BEAST_EXPECT(env.balance(becky) == XRP(45));
730
731 // becky pays alice.
732 env(pay(becky, alice, XRP(20)));
733 env.close();
734
735 // Setup is done. Look at the transactions returned by account_tx.
736 // Verify that account_tx locates all of becky's transactions.
737 Json::Value params;
738 params[jss::account] = becky.human();
739 params[jss::ledger_index_min] = -1;
740 params[jss::ledger_index_max] = -1;
741
742 Json::Value const result{
743 env.rpc("json", "account_tx", to_string(params))};
744
745 BEAST_EXPECT(result[jss::result][jss::status] == "success");
746 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
747
748 BEAST_EXPECT(
749 std::size(sanity) == result[jss::result][jss::transactions].size());
750
751 Json::Value const& txs{result[jss::result][jss::transactions]};
752
753 for (unsigned int index = 0; index < std::size(sanity); ++index)
754 {
755 checkSanity(txs[index], sanity[index]);
756 }
757 }
758
759 void
761 {
762 testcase("MPT");
763
764 using namespace test::jtx;
765 using namespace std::chrono_literals;
766
767 auto cfg = makeConfig();
768 cfg->FEES.reference_fee = 10;
769 Env env(*this, std::move(cfg));
770
771 Account const alice{"alice"};
772 Account const bob{"bob"};
773 Account const carol{"carol"};
774
775 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
776
777 // check the latest mpt-related txn is in alice's account history
778 auto const checkAliceAcctTx = [&](size_t size,
779 Json::StaticString txType) {
780 Json::Value params;
781 params[jss::account] = alice.human();
782 params[jss::limit] = 100;
783 auto const jv =
784 env.rpc("json", "account_tx", to_string(params))[jss::result];
785
786 BEAST_EXPECT(jv[jss::transactions].size() == size);
787 auto const& tx0(jv[jss::transactions][0u][jss::tx]);
788 BEAST_EXPECT(tx0[jss::TransactionType] == txType);
789
790 std::string const txHash{
791 env.tx()->getJson(JsonOptions::none)[jss::hash].asString()};
792 BEAST_EXPECT(tx0[jss::hash] == txHash);
793 };
794
795 // alice creates issuance
796 mptAlice.create(
797 {.ownerCount = 1,
798 .holderCount = 0,
800
801 checkAliceAcctTx(3, jss::MPTokenIssuanceCreate);
802
803 // bob creates a MPToken;
804 mptAlice.authorize({.account = bob});
805 checkAliceAcctTx(4, jss::MPTokenAuthorize);
806 env.close();
807
808 // TODO: windows pipeline fails validation for the hardcoded ledger hash
809 // due to having different test config, it can be uncommented after
810 // figuring out what happened
811 //
812 // ledger hash should be fixed regardless any change to account history
813 // BEAST_EXPECT(
814 // to_string(env.closed()->info().hash) ==
815 // "0BD507BB87D3C0E73B462485E6E381798A8C82FC49BF17FE39C60E08A1AF035D");
816
817 // alice authorizes bob
818 mptAlice.authorize({.account = alice, .holder = bob});
819 checkAliceAcctTx(5, jss::MPTokenAuthorize);
820
821 // carol creates a MPToken;
822 mptAlice.authorize({.account = carol});
823 checkAliceAcctTx(6, jss::MPTokenAuthorize);
824
825 // alice authorizes carol
826 mptAlice.authorize({.account = alice, .holder = carol});
827 checkAliceAcctTx(7, jss::MPTokenAuthorize);
828
829 // alice pays bob 100 tokens
830 mptAlice.pay(alice, bob, 100);
831 checkAliceAcctTx(8, jss::Payment);
832
833 // bob pays carol 10 tokens
834 mptAlice.pay(bob, carol, 10);
835 checkAliceAcctTx(9, jss::Payment);
836 }
837
838public:
839 void
840 run() override
841 {
844 testContents();
846 testMPT();
847 }
848};
849BEAST_DEFINE_TESTSUITE(AccountTx, rpc, ripple);
850
851} // namespace test
852} // namespace ripple
T bind_front(T... args)
Lightweight wrapper to tag static string.
Definition json_value.h:63
Represents a JSON value.
Definition json_value.h:149
bool isMember(char const *key) const
Return true if the object has a member named key.
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
void run() override
Runs the suite.
void checkSanity(Json::Value const &txNode, NodeSanity const &sane)
void testParameters(unsigned int apiVersion)
Immutable cryptographic account descriptor.
Definition Account.h:39
A transaction testing environment.
Definition Env.h:121
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
Definition Env.cpp:115
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:258
std::shared_ptr< STTx const > tx() const
Return the tx data for the last JTx.
Definition Env.cpp:515
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:121
NetClock::time_point now()
Returns the current network time.
Definition Env.h:284
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:279
PrettyAmount balance(Account const &account) const
Returns the XRP balance on an account.
Definition Env.cpp:183
Set the fee on a JTx.
Definition fee.h:37
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
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:35
Set a ticket sequence on a JTx.
Definition ticket.h:48
@ nullValue
'null' value
Definition json_value.h:38
@ arrayValue
array value (ordered list)
Definition json_value.h:44
@ objectValue
object value (collection of name/value pairs).
Definition json_value.h:45
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.
Definition Indexes.cpp:184
Keylet check(AccountID const &id, std::uint32_t seq) noexcept
A Check.
Definition Indexes.cpp:336
Keylet payChan(AccountID const &src, AccountID const &dst, std::uint32_t seq) noexcept
A PaymentChannel.
Definition Indexes.cpp:395
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.
Definition check.cpp:60
Json::Value cash(jtx::Account const &dest, uint256 const &checkId, STAmount const &amount)
Cash a check requiring that a specific amount be delivered.
Definition check.cpp:33
Json::Value auth(Account const &account, Account const &auth)
Preauthorize for deposit.
Definition deposit.cpp:32
Json::Value create(Account const &account, std::uint32_t count)
Create one of more tickets.
Definition ticket.cpp:31
std::unique_ptr< Config > makeConfig(std::map< std::string, std::string > extraTxQ={}, std::map< std::string, std::string > extraVoting={})
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
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.
Definition trust.cpp:32
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
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:29
Json::Value acctdelete(Account const &account, Account const &dest)
Delete account.
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:105
Json::Value offer_cancel(Account const &account, std::uint32_t offerSeq)
Cancel an offer.
Definition offer.cpp:46
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
constexpr std::uint32_t const tfMPTCanTransfer
Definition TxFlags.h:149
@ rpcINVALID_LGR_RANGE
Definition ErrorCodes.h:136
@ rpcLGR_NOT_VALIDATED
Definition ErrorCodes.h:73
@ rpcACT_MALFORMED
Definition ErrorCodes.h:90
@ rpcINVALID_PARAMS
Definition ErrorCodes.h:84
@ rpcLGR_NOT_FOUND
Definition ErrorCodes.h:72
@ rpcLGR_IDXS_INVALID
Definition ErrorCodes.h:112
@ rpcLGR_IDX_MALFORMED
Definition ErrorCodes.h:113
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:30
void forAllApiVersions(Fn const &fn, Args &&... args)
Definition ApiVersion.h:101
@ tecNO_DST_INSUF_XRP
Definition TER.h:291
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:630
constexpr std::uint32_t tfClose
Definition TxFlags.h:133
@ txNode
transaction plus metadata
constexpr std::uint32_t const tfMPTRequireAuth
Definition TxFlags.h:146
constexpr std::uint32_t const tfMPTCanClawback
Definition TxFlags.h:150
T size(T... args)
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:39
uint256 key
Definition Keylet.h:40
Json::StaticString token
Definition ErrorCodes.h:215
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
boost::container::flat_set< std::string > modified
boost::container::flat_set< std::string > created
T to_string(T... args)