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