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