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 <xrpl/beast/unit_test.h>
22#include <xrpl/protocol/ErrorCodes.h>
23#include <xrpl/protocol/jss.h>
24
25#include <boost/container/flat_set.hpp>
26
27namespace ripple {
28
29namespace test {
30
32{
33 // A data structure used to describe the basic structure of a
34 // transactions array node as returned by the account_tx RPC command.
36 {
37 int const index;
39 boost::container::flat_set<std::string> created;
40 boost::container::flat_set<std::string> deleted;
41 boost::container::flat_set<std::string> modified;
42
44 int idx,
45 Json::StaticString const& t,
49 : index(idx), txType(t)
50 {
51 auto buildSet = [](auto&& init) {
52 boost::container::flat_set<std::string> r;
53 r.reserve(init.size());
54 for (auto&& s : init)
55 r.insert(s);
56 return r;
57 };
58
59 created = buildSet(c);
60 deleted = buildSet(d);
61 modified = buildSet(m);
62 }
63 };
64
65 // A helper method tests can use to validate returned JSON vs NodeSanity.
66 void
68 {
69 BEAST_EXPECT(txNode[jss::validated].asBool() == true);
70 BEAST_EXPECT(
71 txNode[jss::tx][sfTransactionType.jsonName].asString() ==
72 sane.txType);
73
74 // Make sure all of the expected node types are present.
75 boost::container::flat_set<std::string> createdNodes;
76 boost::container::flat_set<std::string> deletedNodes;
77 boost::container::flat_set<std::string> modifiedNodes;
78
79 for (Json::Value const& metaNode :
80 txNode[jss::meta][sfAffectedNodes.jsonName])
81 {
82 if (metaNode.isMember(sfCreatedNode.jsonName))
83 createdNodes.insert(
84 metaNode[sfCreatedNode.jsonName][sfLedgerEntryType.jsonName]
85 .asString());
86
87 else if (metaNode.isMember(sfDeletedNode.jsonName))
88 deletedNodes.insert(
89 metaNode[sfDeletedNode.jsonName][sfLedgerEntryType.jsonName]
90 .asString());
91
92 else if (metaNode.isMember(sfModifiedNode.jsonName))
93 modifiedNodes.insert(metaNode[sfModifiedNode.jsonName]
94 [sfLedgerEntryType.jsonName]
95 .asString());
96
97 else
98 fail(
99 "Unexpected or unlabeled node type in metadata.",
100 __FILE__,
101 __LINE__);
102 }
103
104 BEAST_EXPECT(createdNodes == sane.created);
105 BEAST_EXPECT(deletedNodes == sane.deleted);
106 BEAST_EXPECT(modifiedNodes == sane.modified);
107 };
108
109 void
110 testParameters(unsigned int apiVersion)
111 {
112 testcase("Parameters APIv" + std::to_string(apiVersion));
113 using namespace test::jtx;
114
115 Env env(*this);
116 Account A1{"A1"};
117 env.fund(XRP(10000), A1);
118 env.close();
119
120 // Ledger 3 has the two txs associated with funding the account
121 // All other ledgers have no txs
122
123 auto hasTxs = [apiVersion](Json::Value const& j) {
124 switch (apiVersion)
125 {
126 case 1:
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]
137 [jss::Amount] ==
138 j[jss::result][jss::transactions][1u][jss::tx]
139 [jss::DeliverMax]);
140 case 2:
141 case 3:
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))
147 {
148 auto const& payment =
149 j[jss::result][jss::transactions][1u];
150
151 return (payment.isMember(jss::tx_json)) &&
152 (payment[jss::tx_json][jss::TransactionType] ==
153 jss::Payment) &&
154 (payment[jss::tx_json][jss::DeliverMax] ==
155 "10000000010") &&
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");
168 }
169 else
170 return false;
171
172 default:
173 return false;
174 }
175 };
176
177 auto noTxs = [](Json::Value const& j) {
178 return j.isMember(jss::result) &&
179 (j[jss::result][jss::status] == "success") &&
180 (j[jss::result][jss::transactions].size() == 0);
181 };
182
183 auto isErr = [](Json::Value const& j, error_code_i code) {
184 return j.isMember(jss::result) &&
185 j[jss::result].isMember(jss::error) &&
186 j[jss::result][jss::error] == RPC::get_error_info(code).token;
187 };
188
189 Json::Value jParms;
190 jParms[jss::api_version] = apiVersion;
191
192 BEAST_EXPECT(isErr(
193 env.rpc("json", "account_tx", to_string(jParms)),
195
196 jParms[jss::account] = "0xDEADBEEF";
197
198 BEAST_EXPECT(isErr(
199 env.rpc("json", "account_tx", to_string(jParms)),
201
202 jParms[jss::account] = A1.human();
203 BEAST_EXPECT(hasTxs(
204 env.rpc(apiVersion, "json", "account_tx", to_string(jParms))));
205
206 // Ledger min/max index
207 {
208 Json::Value p{jParms};
209 p[jss::ledger_index_min] = -1;
210 p[jss::ledger_index_max] = -1;
211 BEAST_EXPECT(hasTxs(
212 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
213
214 p[jss::ledger_index_min] = 0;
215 p[jss::ledger_index_max] = 100;
216 if (apiVersion < 2u)
217 BEAST_EXPECT(hasTxs(
218 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
219 else
220 BEAST_EXPECT(isErr(
221 env.rpc("json", "account_tx", to_string(p)),
223
224 p[jss::ledger_index_min] = 1;
225 p[jss::ledger_index_max] = 2;
226 if (apiVersion < 2u)
227 BEAST_EXPECT(
228 noTxs(env.rpc("json", "account_tx", to_string(p))));
229 else
230 BEAST_EXPECT(isErr(
231 env.rpc("json", "account_tx", to_string(p)),
233
234 p[jss::ledger_index_min] = 2;
235 p[jss::ledger_index_max] = 1;
236 BEAST_EXPECT(isErr(
237 env.rpc("json", "account_tx", to_string(p)),
238 (apiVersion == 1 ? rpcLGR_IDXS_INVALID
240 }
241 // Ledger index min only
242 {
243 Json::Value p{jParms};
244 p[jss::ledger_index_min] = -1;
245 BEAST_EXPECT(hasTxs(
246 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
247
248 p[jss::ledger_index_min] = 1;
249 if (apiVersion < 2u)
250 BEAST_EXPECT(hasTxs(
251 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
252 else
253 BEAST_EXPECT(isErr(
254 env.rpc("json", "account_tx", to_string(p)),
256
257 p[jss::ledger_index_min] = env.current()->info().seq;
258 BEAST_EXPECT(isErr(
259 env.rpc("json", "account_tx", to_string(p)),
260 (apiVersion == 1 ? rpcLGR_IDXS_INVALID
262 }
263
264 // Ledger index max only
265 {
266 Json::Value p{jParms};
267 p[jss::ledger_index_max] = -1;
268 BEAST_EXPECT(hasTxs(
269 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
270
271 p[jss::ledger_index_max] = env.current()->info().seq;
272 if (apiVersion < 2u)
273 BEAST_EXPECT(hasTxs(
274 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
275 else
276 BEAST_EXPECT(isErr(
277 env.rpc("json", "account_tx", to_string(p)),
279
280 p[jss::ledger_index_max] = 3;
281 BEAST_EXPECT(hasTxs(
282 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
283
284 p[jss::ledger_index_max] = env.closed()->info().seq;
285 BEAST_EXPECT(hasTxs(
286 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
287
288 p[jss::ledger_index_max] = env.closed()->info().seq - 1;
289 BEAST_EXPECT(noTxs(env.rpc("json", "account_tx", to_string(p))));
290 }
291
292 // Ledger Sequence
293 {
294 Json::Value p{jParms};
295
296 p[jss::ledger_index] = env.closed()->info().seq;
297 BEAST_EXPECT(hasTxs(
298 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
299
300 p[jss::ledger_index] = env.closed()->info().seq - 1;
301 BEAST_EXPECT(noTxs(env.rpc("json", "account_tx", to_string(p))));
302
303 p[jss::ledger_index] = env.current()->info().seq;
304 BEAST_EXPECT(isErr(
305 env.rpc("json", "account_tx", to_string(p)),
307
308 p[jss::ledger_index] = env.current()->info().seq + 1;
309 BEAST_EXPECT(isErr(
310 env.rpc("json", "account_tx", to_string(p)), rpcLGR_NOT_FOUND));
311 }
312
313 // Ledger Hash
314 {
315 Json::Value p{jParms};
316
317 p[jss::ledger_hash] = to_string(env.closed()->info().hash);
318 BEAST_EXPECT(hasTxs(
319 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
320
321 p[jss::ledger_hash] = to_string(env.closed()->info().parentHash);
322 BEAST_EXPECT(noTxs(env.rpc("json", "account_tx", to_string(p))));
323 }
324
325 // Ledger index max/min/index all specified
326 // ERRORS out with invalid Parenthesis
327 {
328 jParms[jss::account] = "0xDEADBEEF";
329 jParms[jss::account] = A1.human();
330 Json::Value p{jParms};
331
332 p[jss::ledger_index_max] = -1;
333 p[jss::ledger_index_min] = -1;
334 p[jss::ledger_index] = -1;
335
336 if (apiVersion < 2u)
337 BEAST_EXPECT(hasTxs(
338 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
339 else
340 BEAST_EXPECT(isErr(
341 env.rpc("json", "account_tx", to_string(p)),
343 }
344
345 // Ledger index max only
346 {
347 Json::Value p{jParms};
348 p[jss::ledger_index_max] = env.current()->info().seq;
349 if (apiVersion < 2u)
350 BEAST_EXPECT(hasTxs(
351 env.rpc(apiVersion, "json", "account_tx", to_string(p))));
352 else
353 BEAST_EXPECT(isErr(
354 env.rpc("json", "account_tx", to_string(p)),
356 }
357 // test account non-string
358 {
359 auto testInvalidAccountParam = [&](auto const& param) {
360 Json::Value params;
361 params[jss::account] = param;
362 auto jrr = env.rpc(
363 "json", "account_tx", to_string(params))[jss::result];
364 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
365 BEAST_EXPECT(
366 jrr[jss::error_message] == "Invalid field 'account'.");
367 };
368
369 testInvalidAccountParam(1);
370 testInvalidAccountParam(1.1);
371 testInvalidAccountParam(true);
372 testInvalidAccountParam(Json::Value(Json::nullValue));
373 testInvalidAccountParam(Json::Value(Json::objectValue));
374 testInvalidAccountParam(Json::Value(Json::arrayValue));
375 }
376 // test binary and forward for bool/non bool values
377 {
378 Json::Value p{jParms};
379 p[jss::binary] = "asdf";
380 if (apiVersion < 2u)
381 {
382 Json::Value result{env.rpc("json", "account_tx", to_string(p))};
383 BEAST_EXPECT(result[jss::result][jss::status] == "success");
384 }
385 else
386 BEAST_EXPECT(isErr(
387 env.rpc("json", "account_tx", to_string(p)),
389
390 p[jss::binary] = true;
391 Json::Value result{env.rpc("json", "account_tx", to_string(p))};
392 BEAST_EXPECT(result[jss::result][jss::status] == "success");
393
394 p[jss::forward] = "true";
395 if (apiVersion < 2u)
396 BEAST_EXPECT(result[jss::result][jss::status] == "success");
397 else
398 BEAST_EXPECT(isErr(
399 env.rpc("json", "account_tx", to_string(p)),
401
402 p[jss::forward] = false;
403 result = env.rpc("json", "account_tx", to_string(p));
404 BEAST_EXPECT(result[jss::result][jss::status] == "success");
405 }
406 }
407
408 void
410 {
411 testcase("Contents");
412
413 // Get results for all transaction types that can be associated
414 // with an account. Start by generating all transaction types.
415 using namespace test::jtx;
416 using namespace std::chrono_literals;
417
418 Env env(*this);
419 Account const alice{"alice"};
420 Account const alie{"alie"};
421 Account const gw{"gw"};
422 auto const USD{gw["USD"]};
423
424 env.fund(XRP(1000000), alice, gw);
425 env.close();
426
427 // AccountSet
428 env(noop(alice));
429
430 // Payment
431 env(pay(alice, gw, XRP(100)));
432
433 // Regular key set
434 env(regkey(alice, alie));
435 env.close();
436
437 // Trust and Offers
438 env(trust(alice, USD(200)), sig(alie));
439 std::uint32_t const offerSeq{env.seq(alice)};
440 env(offer(alice, USD(50), XRP(150)), sig(alie));
441 env.close();
442
443 env(offer_cancel(alice, offerSeq), sig(alie));
444 env.close();
445
446 // SignerListSet
447 env(signers(alice, 1, {{"bogie", 1}, {"demon", 1}}), sig(alie));
448
449 // Escrow
450 {
451 // Create an escrow. Requires either a CancelAfter or FinishAfter.
452 auto escrow = [](Account const& account,
453 Account const& to,
454 STAmount const& amount) {
455 Json::Value escro;
456 escro[jss::TransactionType] = jss::EscrowCreate;
457 escro[jss::Flags] = tfUniversal;
458 escro[jss::Account] = account.human();
459 escro[jss::Destination] = to.human();
460 escro[jss::Amount] = amount.getJson(JsonOptions::none);
461 return escro;
462 };
463
464 NetClock::time_point const nextTime{env.now() + 2s};
465
466 Json::Value escrowWithFinish{escrow(alice, alice, XRP(500))};
467 escrowWithFinish[sfFinishAfter.jsonName] =
468 nextTime.time_since_epoch().count();
469
470 std::uint32_t const escrowFinishSeq{env.seq(alice)};
471 env(escrowWithFinish, sig(alie));
472
473 Json::Value escrowWithCancel{escrow(alice, alice, XRP(500))};
474 escrowWithCancel[sfFinishAfter.jsonName] =
475 nextTime.time_since_epoch().count();
476 escrowWithCancel[sfCancelAfter.jsonName] =
477 nextTime.time_since_epoch().count() + 1;
478
479 std::uint32_t const escrowCancelSeq{env.seq(alice)};
480 env(escrowWithCancel, sig(alie));
481 env.close();
482
483 {
484 Json::Value escrowFinish;
485 escrowFinish[jss::TransactionType] = jss::EscrowFinish;
486 escrowFinish[jss::Flags] = tfUniversal;
487 escrowFinish[jss::Account] = alice.human();
488 escrowFinish[sfOwner.jsonName] = alice.human();
489 escrowFinish[sfOfferSequence.jsonName] = escrowFinishSeq;
490 env(escrowFinish, sig(alie));
491 }
492 {
493 Json::Value escrowCancel;
494 escrowCancel[jss::TransactionType] = jss::EscrowCancel;
495 escrowCancel[jss::Flags] = tfUniversal;
496 escrowCancel[jss::Account] = alice.human();
497 escrowCancel[sfOwner.jsonName] = alice.human();
498 escrowCancel[sfOfferSequence.jsonName] = escrowCancelSeq;
499 env(escrowCancel, sig(alie));
500 }
501 env.close();
502 }
503
504 // PayChan
505 {
506 std::uint32_t payChanSeq{env.seq(alice)};
507 Json::Value payChanCreate;
508 payChanCreate[jss::TransactionType] = jss::PaymentChannelCreate;
509 payChanCreate[jss::Flags] = tfUniversal;
510 payChanCreate[jss::Account] = alice.human();
511 payChanCreate[jss::Destination] = gw.human();
512 payChanCreate[jss::Amount] =
513 XRP(500).value().getJson(JsonOptions::none);
514 payChanCreate[sfSettleDelay.jsonName] =
516 payChanCreate[sfPublicKey.jsonName] = strHex(alice.pk().slice());
517 env(payChanCreate, sig(alie));
518 env.close();
519
520 std::string const payChanIndex{
521 strHex(keylet::payChan(alice, gw, payChanSeq).key)};
522
523 {
524 Json::Value payChanFund;
525 payChanFund[jss::TransactionType] = jss::PaymentChannelFund;
526 payChanFund[jss::Flags] = tfUniversal;
527 payChanFund[jss::Account] = alice.human();
528 payChanFund[sfChannel.jsonName] = payChanIndex;
529 payChanFund[jss::Amount] =
530 XRP(200).value().getJson(JsonOptions::none);
531 env(payChanFund, sig(alie));
532 env.close();
533 }
534 {
535 Json::Value payChanClaim;
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());
541 env(payChanClaim);
542 env.close();
543 }
544 }
545
546 // Check
547 {
548 auto const aliceCheckId = keylet::check(alice, env.seq(alice)).key;
549 env(check::create(alice, gw, XRP(300)), sig(alie));
550
551 auto const gwCheckId = keylet::check(gw, env.seq(gw)).key;
552 env(check::create(gw, alice, XRP(200)));
553 env.close();
554
555 env(check::cash(alice, gwCheckId, XRP(200)), sig(alie));
556 env(check::cancel(alice, aliceCheckId), sig(alie));
557 env.close();
558 }
559 {
560 // Deposit preauthorization with a Ticket.
561 std::uint32_t const tktSeq{env.seq(alice) + 1};
562 env(ticket::create(alice, 1), sig(alie));
563 env.close();
564
565 env(deposit::auth(alice, gw), ticket::use(tktSeq), sig(alie));
566 env.close();
567 }
568
569 // Setup is done. Look at the transactions returned by account_tx.
570 Json::Value params;
571 params[jss::account] = alice.human();
572 params[jss::ledger_index_min] = -1;
573 params[jss::ledger_index_max] = -1;
574
575 Json::Value const result{
576 env.rpc("json", "account_tx", to_string(params))};
577
578 BEAST_EXPECT(result[jss::result][jss::status] == "success");
579 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
580
581 Json::Value const& txs{result[jss::result][jss::transactions]};
582
583 // clang-format off
584 // Do a sanity check on each returned transaction. They should
585 // be returned in the reverse order of application to the ledger.
586 static const NodeSanity sanity[]{
587 // txType, created, deleted, modified
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}},
610 };
611 // clang-format on
612
613 BEAST_EXPECT(
614 std::size(sanity) == result[jss::result][jss::transactions].size());
615
616 for (unsigned int index{0}; index < std::size(sanity); ++index)
617 {
618 checkSanity(txs[index], sanity[index]);
619 }
620 }
621
622 void
624 {
625 testcase("AccountDelete");
626
627 // Verify that if an account is resurrected then the account_tx RPC
628 // command still recovers all transactions on that account before
629 // and after resurrection.
630 using namespace test::jtx;
631 using namespace std::chrono_literals;
632
633 Env env(*this);
634 Account const alice{"alice"};
635 Account const becky{"becky"};
636
637 env.fund(XRP(10000), alice, becky);
638 env.close();
639
640 // Verify that becky's account root is present.
641 Keylet const beckyAcctKey{keylet::account(becky.id())};
642 BEAST_EXPECT(env.closed()->exists(beckyAcctKey));
643
644 // becky does an AccountSet .
645 env(noop(becky));
646
647 // Close enough ledgers to be able to delete becky's account.
648 std::uint32_t const ledgerCount{
649 env.current()->seq() + 257 - env.seq(becky)};
650
651 for (std::uint32_t i = 0; i < ledgerCount; ++i)
652 env.close();
653
654 auto const beckyPreDelBalance{env.balance(becky)};
655
656 auto const acctDelFee{drops(env.current()->fees().increment)};
657 env(acctdelete(becky, alice), fee(acctDelFee));
658 env.close();
659
660 // Verify that becky's account root is gone.
661 BEAST_EXPECT(!env.closed()->exists(beckyAcctKey));
662 env.close();
663
664 // clang-format off
665 // Do a sanity check on each returned transaction. They should
666 // be returned in the reverse order of application to the ledger.
667 //
668 // Note that the first two transactions in sanity have not occurred
669 // yet. We'll see those after becky's account is resurrected.
670 static const NodeSanity sanity[]
671 {
672 // txType, created, deleted, modified
673/* becky pays alice */ { 0, jss::Payment, {}, {}, {jss::AccountRoot, jss::AccountRoot}},
674/* alice resurrects becky's acct */ { 1, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}},
675/* becky deletes her account */ { 2, jss::AccountDelete, {}, {jss::AccountRoot}, {jss::AccountRoot}},
676/* becky's noop */ { 3, jss::AccountSet, {}, {}, {jss::AccountRoot}},
677/* "fund" sets flags */ { 4, jss::AccountSet, {}, {}, {jss::AccountRoot}},
678/* "fund" creates becky's acct */ { 5, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}}
679 };
680 // clang-format on
681
682 // Verify that we can recover becky's account_tx information even
683 // after the account is deleted.
684 {
685 Json::Value params;
686 params[jss::account] = becky.human();
687 params[jss::ledger_index_min] = -1;
688 params[jss::ledger_index_max] = -1;
689
690 Json::Value const result{
691 env.rpc("json", "account_tx", to_string(params))};
692
693 BEAST_EXPECT(result[jss::result][jss::status] == "success");
694 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
695
696 // The first two transactions listed in sanity haven't happened yet.
697 constexpr unsigned int beckyDeletedOffest = 2;
698 BEAST_EXPECT(
699 std::size(sanity) ==
700 result[jss::result][jss::transactions].size() +
701 beckyDeletedOffest);
702
703 Json::Value const& txs{result[jss::result][jss::transactions]};
704
705 for (unsigned int index = beckyDeletedOffest;
706 index < std::size(sanity);
707 ++index)
708 {
709 checkSanity(txs[index - beckyDeletedOffest], sanity[index]);
710 }
711 }
712
713 // All it takes is a large enough XRP payment to resurrect
714 // becky's account. Try too small a payment.
715 env(pay(alice,
716 becky,
717 drops(env.current()->fees().accountReserve(0)) - XRP(1)),
719 env.close();
720
721 // Actually resurrect becky's account.
722 env(pay(alice, becky, XRP(45)));
723 env.close();
724
725 // becky's account root should be back.
726 BEAST_EXPECT(env.closed()->exists(beckyAcctKey));
727 BEAST_EXPECT(env.balance(becky) == XRP(45));
728
729 // becky pays alice.
730 env(pay(becky, alice, XRP(20)));
731 env.close();
732
733 // Setup is done. Look at the transactions returned by account_tx.
734 // Verify that account_tx locates all of becky's transactions.
735 Json::Value params;
736 params[jss::account] = becky.human();
737 params[jss::ledger_index_min] = -1;
738 params[jss::ledger_index_max] = -1;
739
740 Json::Value const result{
741 env.rpc("json", "account_tx", to_string(params))};
742
743 BEAST_EXPECT(result[jss::result][jss::status] == "success");
744 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
745
746 BEAST_EXPECT(
747 std::size(sanity) == result[jss::result][jss::transactions].size());
748
749 Json::Value const& txs{result[jss::result][jss::transactions]};
750
751 for (unsigned int index = 0; index < std::size(sanity); ++index)
752 {
753 checkSanity(txs[index], sanity[index]);
754 }
755 }
756
757public:
758 void
759 run() override
760 {
763 testContents();
765 }
766};
767BEAST_DEFINE_TESTSUITE(AccountTx, rpc, ripple);
768
769} // namespace test
770} // namespace ripple
T bind_front(T... args)
Lightweight wrapper to tag static string.
Definition: json_value.h:61
Represents a JSON value.
Definition: json_value.h:147
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
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:38
A transaction testing environment.
Definition: Env.h:117
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: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
NetClock::time_point now()
Returns the current network time.
Definition: Env.h:278
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
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:36
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
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition: ter.h:34
Set a ticket sequence on a JTx.
Definition: ticket.h:48
@ nullValue
'null' value
Definition: json_value.h:36
@ arrayValue
array value (ordered list)
Definition: json_value.h:42
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:43
ErrorInfo const & get_error_info(error_code_i code)
Returns an ErrorInfo that reflects the error code.
Definition: ErrorCodes.cpp:173
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition: Indexes.cpp:160
Keylet check(AccountID const &id, std::uint32_t seq) noexcept
A Check.
Definition: Indexes.cpp:312
Keylet payChan(AccountID const &src, AccountID const &dst, std::uint32_t seq) noexcept
A PaymentChannel.
Definition: Indexes.cpp:371
Json::Value create(A const &account, A const &dest, STAmount const &sendMax)
Create a check.
Definition: TestHelpers.h:439
Json::Value cancel(jtx::Account const &dest, uint256 const &checkId)
Cancel a check.
Definition: check.cpp:61
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:32
Json::Value auth(Account const &account, Account const &auth)
Preauthorize for deposit.
Definition: deposit.cpp:31
Json::Value create(Account const &account, std::uint32_t count)
Create one of more tickets.
Definition: ticket.cpp:30
Json::Value regkey(Account const &account, disabled_t)
Disable the regular key.
Definition: regkey.cpp:28
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)
Definition: multisign.cpp:35
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:30
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition: pay.cpp:29
Json::Value noop(Account const &account)
The null transaction.
Definition: noop.h:31
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition: offer.cpp:28
Json::Value acctdelete(Account const &account, Account const &dest)
Delete account.
Definition: acctdelete.cpp:29
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:104
Json::Value offer_cancel(Account const &account, std::uint32_t offerSeq)
Cancel an offer.
Definition: offer.cpp:45
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: algorithm.h:26
error_code_i
Definition: ErrorCodes.h:40
@ 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:102
@ tecNO_DST_INSUF_XRP
Definition: TER.h:278
constexpr std::uint32_t tfUniversal
Definition: TxFlags.h:61
std::string to_string(base_uint< Bits, Tag > const &a)
Definition: base_uint.h:629
constexpr std::uint32_t tfClose
Definition: TxFlags.h:127
@ txNode
transaction plus metadata
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:210
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)