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/delegate.h>
9#include <test/jtx/deposit.h>
10#include <test/jtx/envconfig.h>
11#include <test/jtx/fee.h>
12#include <test/jtx/mpt.h>
13#include <test/jtx/multisign.h>
14#include <test/jtx/noop.h>
15#include <test/jtx/offer.h>
16#include <test/jtx/pay.h>
17#include <test/jtx/regkey.h>
18#include <test/jtx/sig.h>
19#include <test/jtx/sponsor.h>
20#include <test/jtx/ter.h>
21#include <test/jtx/ticket.h>
22#include <test/jtx/trust.h>
23
24#include <xrpld/core/Config.h>
25
26#include <xrpl/basics/base_uint.h>
27#include <xrpl/basics/chrono.h>
28#include <xrpl/basics/strHex.h>
29#include <xrpl/beast/unit_test/suite.h>
30#include <xrpl/json/json_value.h>
31#include <xrpl/json/to_string.h>
32#include <xrpl/protocol/AccountID.h>
33#include <xrpl/protocol/ApiVersion.h>
34#include <xrpl/protocol/ErrorCodes.h>
35#include <xrpl/protocol/Indexes.h>
36#include <xrpl/protocol/SField.h>
37#include <xrpl/protocol/STAmount.h>
38#include <xrpl/protocol/SeqProxy.h>
39#include <xrpl/protocol/TER.h>
40#include <xrpl/protocol/TxFlags.h>
41#include <xrpl/protocol/jss.h>
42
43#include <boost/container/flat_set.hpp>
44
45#include <chrono>
46#include <cstddef>
47#include <cstdint>
48#include <initializer_list>
49#include <iterator>
50#include <memory>
51#include <optional>
52#include <string>
53#include <utility>
54
55namespace xrpl::test {
56
58{
59 // A data structure used to describe the basic structure of a
60 // transactions array node as returned by the account_tx RPC command.
62 {
63 int const index;
65 boost::container::flat_set<std::string> created;
66 boost::container::flat_set<std::string> deleted;
67 boost::container::flat_set<std::string> modified;
68
70 int idx,
71 json::StaticString const& t,
75 : index(idx), txType(t)
76 {
77 auto buildSet = [](auto&& init) {
78 boost::container::flat_set<std::string> r;
79 r.reserve(init.size());
80 for (auto&& s : init)
81 r.insert(s);
82 return r;
83 };
84
85 created = buildSet(c);
86 deleted = buildSet(d);
87 modified = buildSet(m);
88 }
89 };
90
91 // A helper method tests can use to validate returned JSON vs NodeSanity.
92 void
93 checkSanity(json::Value const& txNode, NodeSanity const& sane)
94 {
95 BEAST_EXPECT(txNode[jss::validated].asBool() == true);
96 BEAST_EXPECT(txNode[jss::tx][sfTransactionType.jsonName].asString() == sane.txType);
97
98 // Make sure all of the expected node types are present.
99 boost::container::flat_set<std::string> createdNodes;
100 boost::container::flat_set<std::string> deletedNodes;
101 boost::container::flat_set<std::string> modifiedNodes;
102
103 for (json::Value const& metaNode : txNode[jss::meta][sfAffectedNodes.jsonName])
104 {
105 if (metaNode.isMember(sfCreatedNode.jsonName))
106 {
107 createdNodes.insert(
108 metaNode[sfCreatedNode.jsonName][sfLedgerEntryType.jsonName].asString());
109 }
110 else if (metaNode.isMember(sfDeletedNode.jsonName))
111 {
112 deletedNodes.insert(
113 metaNode[sfDeletedNode.jsonName][sfLedgerEntryType.jsonName].asString());
114 }
115 else if (metaNode.isMember(sfModifiedNode.jsonName))
116 {
117 modifiedNodes.insert(
118 metaNode[sfModifiedNode.jsonName][sfLedgerEntryType.jsonName].asString());
119 }
120 else
121 {
122 fail("Unexpected or unlabeled node type in metadata.", __FILE__, __LINE__);
123 }
124 }
125
126 BEAST_EXPECT(createdNodes == sane.created);
127 BEAST_EXPECT(deletedNodes == sane.deleted);
128 BEAST_EXPECT(modifiedNodes == sane.modified);
129 };
130
131 void
132 testParameters(unsigned int apiVersion)
133 {
134 testcase("Parameters APIv" + std::to_string(apiVersion));
135 using namespace test::jtx;
136
137 Env env(*this, envconfig([](std::unique_ptr<Config> cfg) {
138 cfg->fees.referenceFee = 10;
139 return cfg;
140 }));
141 Account const a1{"A1"};
142 env.fund(XRP(10000), a1);
143 env.close();
144
145 // Ledger 3 has the two txs associated with funding the account
146 // All other ledgers have no txs
147
148 auto hasTxs = [apiVersion](json::Value const& j) {
149 switch (apiVersion)
150 {
151 case 1:
152 return j.isMember(jss::result) && (j[jss::result][jss::status] == "success") &&
153 (j[jss::result][jss::transactions].size() == 2) &&
154 (j[jss::result][jss::transactions][0u][jss::tx][jss::TransactionType] ==
155 jss::AccountSet) &&
156 (j[jss::result][jss::transactions][1u][jss::tx][jss::TransactionType] ==
157 jss::Payment) &&
158 (j[jss::result][jss::transactions][1u][jss::tx][jss::DeliverMax] ==
159 "10000000010") &&
160 (j[jss::result][jss::transactions][1u][jss::tx][jss::Amount] ==
161 j[jss::result][jss::transactions][1u][jss::tx][jss::DeliverMax]);
162 case 2:
163 case 3:
164 if (j.isMember(jss::result) && (j[jss::result][jss::status] == "success") &&
165 (j[jss::result][jss::transactions].size() == 2) &&
166 (j[jss::result][jss::transactions][0u][jss::tx_json]
167 [jss::TransactionType] == jss::AccountSet))
168 {
169 auto const& payment = j[jss::result][jss::transactions][1u];
170
171 return (payment.isMember(jss::tx_json)) &&
172 (payment[jss::tx_json][jss::TransactionType] == jss::Payment) &&
173 (payment[jss::tx_json][jss::DeliverMax] == "10000000010") &&
174 (!payment[jss::tx_json].isMember(jss::Amount)) &&
175 (!payment[jss::tx_json].isMember(jss::hash)) &&
176 (payment[jss::hash] ==
177 "9F3085D85F472D1CC29627F260DF68EDE59D42D1D0C33E345"
178 "ECF0D4CE981D0A8") &&
179 (payment[jss::validated] == true) &&
180 (payment[jss::ledger_index] == 3) &&
181 (payment[jss::ledger_hash] ==
182 "5476DCD816EA04CBBA57D47BBF1FC58A5217CC93A5ADD79CB"
183 "580A5AFDD727E33") &&
184 (payment[jss::close_time_iso] == "2000-01-01T00:00:10Z");
185 }
186 else
187 {
188 return false;
189 }
190
191 default:
192 return false;
193 }
194 };
195
196 auto noTxs = [](json::Value const& j) {
197 return j.isMember(jss::result) && (j[jss::result][jss::status] == "success") &&
198 (j[jss::result][jss::transactions].size() == 0);
199 };
200
201 auto isErr = [](json::Value const& j, ErrorCodeI code) {
202 return j.isMember(jss::result) && j[jss::result].isMember(jss::error) &&
203 j[jss::result][jss::error] == rpc::getErrorInfo(code).token;
204 };
205
206 json::Value jParams;
207 jParams[jss::api_version] = apiVersion;
208
209 BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(jParams)), RpcInvalidParams));
210
211 jParams[jss::account] = "0xDEADBEEF";
212
213 BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(jParams)), RpcActMalformed));
214
215 jParams[jss::account] = a1.human();
216 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(jParams))));
217
218 // Ledger min/max index
219 {
220 json::Value p{jParams};
221 p[jss::ledger_index_min] = -1;
222 p[jss::ledger_index_max] = -1;
223 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
224
225 p[jss::ledger_index_min] = 0;
226 p[jss::ledger_index_max] = 100;
227 if (apiVersion < 2u)
228 {
229 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
230 }
231 else
232 {
233 BEAST_EXPECT(
234 isErr(env.rpc("json", "account_tx", to_string(p)), RpcLgrIdxMalformed));
235 }
236
237 p[jss::ledger_index_min] = 1;
238 p[jss::ledger_index_max] = 2;
239 if (apiVersion < 2u)
240 {
241 BEAST_EXPECT(noTxs(env.rpc("json", "account_tx", to_string(p))));
242 }
243 else
244 {
245 BEAST_EXPECT(
246 isErr(env.rpc("json", "account_tx", to_string(p)), RpcLgrIdxMalformed));
247 }
248
249 p[jss::ledger_index_min] = 2;
250 p[jss::ledger_index_max] = 1;
251 BEAST_EXPECT(isErr(
252 env.rpc("json", "account_tx", to_string(p)),
253 (apiVersion == 1 ? RpcLgrIdxsInvalid : RpcInvalidLgrRange)));
254 }
255 // Ledger index min only
256 {
257 json::Value p{jParams};
258 p[jss::ledger_index_min] = -1;
259 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
260
261 p[jss::ledger_index_min] = 1;
262 if (apiVersion < 2u)
263 {
264 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
265 }
266 else
267 {
268 BEAST_EXPECT(
269 isErr(env.rpc("json", "account_tx", to_string(p)), RpcLgrIdxMalformed));
270 }
271
272 p[jss::ledger_index_min] = env.current()->header().seq;
273 BEAST_EXPECT(isErr(
274 env.rpc("json", "account_tx", to_string(p)),
275 (apiVersion == 1 ? RpcLgrIdxsInvalid : RpcInvalidLgrRange)));
276 }
277
278 // Ledger index max only
279 {
280 json::Value p{jParams};
281 p[jss::ledger_index_max] = -1;
282 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
283
284 p[jss::ledger_index_max] = env.current()->header().seq;
285 if (apiVersion < 2u)
286 {
287 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
288 }
289 else
290 {
291 BEAST_EXPECT(
292 isErr(env.rpc("json", "account_tx", to_string(p)), RpcLgrIdxMalformed));
293 }
294
295 p[jss::ledger_index_max] = 3;
296 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
297
298 p[jss::ledger_index_max] = env.closed()->header().seq;
299 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
300
301 p[jss::ledger_index_max] = env.closed()->header().seq - 1;
302 BEAST_EXPECT(noTxs(env.rpc("json", "account_tx", to_string(p))));
303 }
304
305 // Ledger Sequence
306 {
307 json::Value p{jParams};
308
309 p[jss::ledger_index] = env.closed()->header().seq;
310 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
311
312 p[jss::ledger_index] = env.closed()->header().seq - 1;
313 BEAST_EXPECT(noTxs(env.rpc("json", "account_tx", to_string(p))));
314
315 p[jss::ledger_index] = env.current()->header().seq;
316 BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), RpcLgrNotValidated));
317
318 p[jss::ledger_index] = env.current()->header().seq + 1;
319 BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), RpcLgrNotFound));
320 }
321
322 // Ledger Hash
323 {
324 json::Value p{jParams};
325
326 p[jss::ledger_hash] = to_string(env.closed()->header().hash);
327 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
328
329 p[jss::ledger_hash] = to_string(env.closed()->header().parentHash);
330 BEAST_EXPECT(noTxs(env.rpc("json", "account_tx", to_string(p))));
331 }
332
333 // Ledger index max/min/index all specified
334 // ERRORS out with invalid Parenthesis
335 {
336 jParams[jss::account] = "0xDEADBEEF";
337 jParams[jss::account] = a1.human();
338 json::Value p{jParams};
339
340 p[jss::ledger_index_max] = -1;
341 p[jss::ledger_index_min] = -1;
342 p[jss::ledger_index] = -1;
343
344 if (apiVersion < 2u)
345 {
346 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
347 }
348 else
349 {
350 BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), RpcInvalidParams));
351 }
352 }
353
354 // Ledger index max only
355 {
356 json::Value p{jParams};
357 p[jss::ledger_index_max] = env.current()->header().seq;
358 if (apiVersion < 2u)
359 {
360 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
361 }
362 else
363 {
364 BEAST_EXPECT(
365 isErr(env.rpc("json", "account_tx", to_string(p)), RpcLgrIdxMalformed));
366 }
367 }
368 // test account non-string
369 {
370 auto testInvalidAccountParam = [&](auto const& param) {
371 json::Value params;
372 params[jss::account] = param;
373 auto jrr = env.rpc("json", "account_tx", to_string(params))[jss::result];
374 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
375 BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'account'.");
376 };
377
378 testInvalidAccountParam(1);
379 testInvalidAccountParam(1.1);
380 testInvalidAccountParam(true);
381 testInvalidAccountParam(json::Value(json::ValueType::Null));
382 testInvalidAccountParam(json::Value(json::ValueType::Object));
383 testInvalidAccountParam(json::Value(json::ValueType::Array));
384 }
385 // test binary and forward for bool/non bool values
386 {
387 json::Value p{jParams};
388 p[jss::binary] = "asdf";
389 if (apiVersion < 2u)
390 {
391 json::Value result{env.rpc("json", "account_tx", to_string(p))};
392 BEAST_EXPECT(result[jss::result][jss::status] == "success");
393 }
394 else
395 {
396 BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), RpcInvalidParams));
397 }
398
399 p[jss::binary] = true;
400 json::Value result{env.rpc("json", "account_tx", to_string(p))};
401 BEAST_EXPECT(result[jss::result][jss::status] == "success");
402
403 p[jss::forward] = "true";
404 if (apiVersion < 2u)
405 {
406 BEAST_EXPECT(result[jss::result][jss::status] == "success");
407 }
408 else
409 {
410 BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), RpcInvalidParams));
411 }
412
413 p[jss::forward] = false;
414 result = env.rpc("json", "account_tx", to_string(p));
415 BEAST_EXPECT(result[jss::result][jss::status] == "success");
416 }
417 // test limit with malformed values
418 {
419 json::Value p{jParams};
420
421 // Test case: limit = 0 should fail (below minimum)
422 p[jss::limit] = 0;
423 BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), RpcInvalidParams));
424
425 // Test case: limit = 1.2 should fail (not an integer)
426 p[jss::limit] = 1.2;
427 BEAST_EXPECT(
428 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
429 rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
430
431 // Test case: limit = "10" should fail (string instead of integer)
432 p[jss::limit] = "10";
433 BEAST_EXPECT(
434 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
435 rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
436
437 // Test case: limit = true should fail (boolean instead of integer)
438 p[jss::limit] = true;
439 BEAST_EXPECT(
440 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
441 rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
442
443 // Test case: limit = false should fail (boolean instead of integer)
444 p[jss::limit] = false;
445 BEAST_EXPECT(
446 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
447 rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
448
449 // Test case: limit = -1 should fail (negative number)
450 p[jss::limit] = -1;
451 BEAST_EXPECT(
452 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
453 rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
454
455 // Test case: limit = [] should fail (array instead of integer)
456 p[jss::limit] = json::Value(json::ValueType::Array);
457 BEAST_EXPECT(
458 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
459 rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
460
461 // Test case: limit = {} should fail (object instead of integer)
462 p[jss::limit] = json::Value(json::ValueType::Object);
463 BEAST_EXPECT(
464 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
465 rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
466
467 // Test case: limit = "malformed" should fail (malformed string)
468 p[jss::limit] = "malformed";
469 BEAST_EXPECT(
470 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
471 rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
472
473 // Test case: limit = ["limit"] should fail (array with string)
474 p[jss::limit] = json::Value(json::ValueType::Array);
475 p[jss::limit].append("limit");
476 BEAST_EXPECT(
477 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
478 rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
479
480 // Test case: limit = {"limit": 10} should fail (object with
481 // property)
482 p[jss::limit] = json::Value(json::ValueType::Object);
483 p[jss::limit][jss::limit] = 10;
484 BEAST_EXPECT(
485 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
486 rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
487
488 // Test case: limit = 10 should succeed (valid integer)
489 p[jss::limit] = 10;
490 BEAST_EXPECT(
491 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::status] == "success");
492 }
493 }
494
495 void
497 {
498 testcase("Contents");
499
500 // Get results for all transaction types that can be associated
501 // with an account. Start by generating all transaction types.
502 using namespace test::jtx;
503 using namespace std::chrono_literals;
504
505 Env env(*this);
506 Account const alice{"alice"};
507 Account const alie{"alie"};
508 Account const gw{"gw"};
509 auto const usd{gw["USD"]};
510
511 env.fund(XRP(1000000), alice, gw);
512 env.close();
513
514 // AccountSet
515 env(noop(alice));
516
517 // Payment
518 env(pay(alice, gw, XRP(100)));
519
520 // Regular key set
521 env(regkey(alice, alie));
522 env.close();
523
524 // Trust and Offers
525 env(trust(alice, usd(200)), Sig(alie));
526 std::uint32_t const offerSeq{env.seq(alice)};
527 env(offer(alice, usd(50), XRP(150)), Sig(alie));
528 env.close();
529
530 env(offerCancel(alice, offerSeq), Sig(alie));
531 env.close();
532
533 // SignerListSet
534 env(signers(alice, 1, {{"bogie", 1}, {"demon", 1}}), Sig(alie));
535
536 // Escrow
537 {
538 // Create an escrow. Requires either a CancelAfter or FinishAfter.
539 auto escrow = [](Account const& account, Account const& to, STAmount const& amount) {
541 escrow[jss::TransactionType] = jss::EscrowCreate;
542 escrow[jss::Account] = account.human();
543 escrow[jss::Destination] = to.human();
544 escrow[jss::Amount] = amount.getJson(JsonOptions::Values::None);
545 return escrow;
546 };
547
548 NetClock::time_point const nextTime{env.now() + 2s};
549
550 json::Value escrowWithFinish{escrow(alice, alice, XRP(500))};
551 escrowWithFinish[sfFinishAfter.jsonName] = nextTime.time_since_epoch().count();
552
553 std::uint32_t const escrowFinishSeq{env.seq(alice)};
554 env(escrowWithFinish, Sig(alie));
555
556 json::Value escrowWithCancel{escrow(alice, alice, XRP(500))};
557 escrowWithCancel[sfFinishAfter.jsonName] = nextTime.time_since_epoch().count();
558 escrowWithCancel[sfCancelAfter.jsonName] = nextTime.time_since_epoch().count() + 1;
559
560 std::uint32_t const escrowCancelSeq{env.seq(alice)};
561 env(escrowWithCancel, Sig(alie));
562 env.close();
563
564 {
565 json::Value escrowFinish;
566 escrowFinish[jss::TransactionType] = jss::EscrowFinish;
567 escrowFinish[jss::Account] = alice.human();
568 escrowFinish[sfOwner.jsonName] = alice.human();
569 escrowFinish[sfOfferSequence.jsonName] = escrowFinishSeq;
570 env(escrowFinish, Sig(alie));
571 }
572 {
573 json::Value escrowCancel;
574 escrowCancel[jss::TransactionType] = jss::EscrowCancel;
575 escrowCancel[jss::Account] = alice.human();
576 escrowCancel[sfOwner.jsonName] = alice.human();
577 escrowCancel[sfOfferSequence.jsonName] = escrowCancelSeq;
578 env(escrowCancel, Sig(alie));
579 }
580 env.close();
581 }
582
583 // PayChan
584 {
585 std::uint32_t const payChanSeq{env.seq(alice)};
586 json::Value payChanCreate;
587 payChanCreate[jss::TransactionType] = jss::PaymentChannelCreate;
588 payChanCreate[jss::Account] = alice.human();
589 payChanCreate[jss::Destination] = gw.human();
590 payChanCreate[jss::Amount] = XRP(500).value().getJson(JsonOptions::Values::None);
591 payChanCreate[sfSettleDelay.jsonName] = NetClock::duration{100s}.count();
592 payChanCreate[sfPublicKey.jsonName] = strHex(alice.pk().slice());
593 env(payChanCreate, Sig(alie));
594 env.close();
595
596 std::string const payChanIndex{
597 strHex(keylet::payChannel(alice, gw, SeqProxy::rawSequence(payChanSeq)).key)};
598
599 {
600 json::Value payChanFund;
601 payChanFund[jss::TransactionType] = jss::PaymentChannelFund;
602 payChanFund[jss::Account] = alice.human();
603 payChanFund[sfChannel.jsonName] = payChanIndex;
604 payChanFund[jss::Amount] = XRP(200).value().getJson(JsonOptions::Values::None);
605 env(payChanFund, Sig(alie));
606 env.close();
607 }
608 {
609 json::Value payChanClaim;
610 payChanClaim[jss::TransactionType] = jss::PaymentChannelClaim;
611 payChanClaim[jss::Flags] = tfClose;
612 payChanClaim[jss::Account] = gw.human();
613 payChanClaim[sfChannel.jsonName] = payChanIndex;
614 payChanClaim[sfPublicKey.jsonName] = strHex(alice.pk().slice());
615 env(payChanClaim);
616 env.close();
617 }
618 }
619
620 // Check
621 {
622 auto const aliceCheckId =
623 keylet::check(alice, SeqProxy::rawSequence(env.seq(alice))).key;
624 env(check::create(alice, gw, XRP(300)), Sig(alie));
625
626 auto const gwCheckId = keylet::check(gw, SeqProxy::rawSequence(env.seq(gw))).key;
627 env(check::create(gw, alice, XRP(200)));
628 env.close();
629
630 env(check::cash(alice, gwCheckId, XRP(200)), Sig(alie));
631 env(check::cancel(alice, aliceCheckId), Sig(alie));
632 env.close();
633 }
634 {
635 // Deposit pre-authorization with a Ticket.
636 std::uint32_t const tktSeq{env.seq(alice) + 1};
637 env(ticket::create(alice, 1), Sig(alie));
638 env.close();
639
640 env(deposit::auth(alice, gw), ticket::Use(tktSeq), Sig(alie));
641 env.close();
642 }
643
644 // Setup is done. Look at the transactions returned by account_tx.
645 json::Value params;
646 params[jss::account] = alice.human();
647 params[jss::ledger_index_min] = -1;
648 params[jss::ledger_index_max] = -1;
649
650 json::Value const result{env.rpc("json", "account_tx", to_string(params))};
651
652 BEAST_EXPECT(result[jss::result][jss::status] == "success");
653 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
654
655 json::Value const& txs{result[jss::result][jss::transactions]};
656
657 // clang-format off
658 // Do a sanity check on each returned transaction. They should
659 // be returned in the reverse order of application to the ledger.
660 static const NodeSanity kSanity[]{
661 // txType, created, deleted, modified
662 {0, jss::DepositPreauth, {jss::DepositPreauth}, {jss::Ticket}, {jss::AccountRoot, jss::DirectoryNode}},
663 {1, jss::TicketCreate, {jss::Ticket}, {}, {jss::AccountRoot, jss::DirectoryNode}},
664 {2, jss::CheckCancel, {}, {jss::Check}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
665 {3, jss::CheckCash, {}, {jss::Check}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
666 {4, jss::CheckCreate, {jss::Check}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
667 {5, jss::CheckCreate, {jss::Check}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
668 {6, jss::PaymentChannelClaim, {}, {jss::PayChannel}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
669 {7, jss::PaymentChannelFund, {}, {}, {jss::AccountRoot, jss::PayChannel}},
670 {8, jss::PaymentChannelCreate, {jss::PayChannel}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
671 {9, jss::EscrowCancel, {}, {jss::Escrow}, {jss::AccountRoot, jss::DirectoryNode}},
672 {10, jss::EscrowFinish, {}, {jss::Escrow}, {jss::AccountRoot, jss::DirectoryNode}},
673 {11, jss::EscrowCreate, {jss::Escrow}, {}, {jss::AccountRoot, jss::DirectoryNode}},
674 {12, jss::EscrowCreate, {jss::Escrow}, {}, {jss::AccountRoot, jss::DirectoryNode}},
675 {13, jss::SignerListSet, {jss::SignerList}, {}, {jss::AccountRoot, jss::DirectoryNode}},
676 {14, jss::OfferCancel, {}, {jss::Offer, jss::DirectoryNode}, {jss::AccountRoot, jss::DirectoryNode}},
677 {15, jss::OfferCreate, {jss::Offer, jss::DirectoryNode}, {}, {jss::AccountRoot, jss::DirectoryNode}},
678 {16, jss::TrustSet, {jss::RippleState, jss::DirectoryNode, jss::DirectoryNode}, {}, {jss::AccountRoot, jss::AccountRoot}},
679 {17, jss::SetRegularKey, {}, {}, {jss::AccountRoot}},
680 {18, jss::Payment, {}, {}, {jss::AccountRoot, jss::AccountRoot}},
681 {19, jss::AccountSet, {}, {}, {jss::AccountRoot}},
682 {20, jss::AccountSet, {}, {}, {jss::AccountRoot}},
683 {21, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}},
684 };
685 // clang-format on
686
687 BEAST_EXPECT(std::size(kSanity) == result[jss::result][jss::transactions].size());
688
689 for (unsigned int index{0}; index < std::size(kSanity); ++index)
690 {
691 checkSanity(txs[index], kSanity[index]);
692 }
693 }
694
695 void
697 {
698 testcase("AccountDelete");
699
700 // Verify that if an account is resurrected then the account_tx RPC
701 // command still recovers all transactions on that account before
702 // and after resurrection.
703 using namespace test::jtx;
704 using namespace std::chrono_literals;
705
706 Env env(*this);
707 Account const alice{"alice"};
708 Account const becky{"becky"};
709
710 env.fund(XRP(10000), alice, becky);
711 env.close();
712
713 // Verify that becky's account root is present.
714 Keylet const beckyAcctKey{keylet::account(becky.id())};
715 BEAST_EXPECT(env.closed()->exists(beckyAcctKey));
716
717 // becky does an AccountSet .
718 env(noop(becky));
719
720 // Close enough ledgers to be able to delete becky's account.
721 std::uint32_t const ledgerCount{env.current()->seq() + 257 - env.seq(becky)};
722
723 for (std::uint32_t i = 0; i < ledgerCount; ++i)
724 env.close();
725
726 auto const beckyPreDelBalance{env.balance(becky)};
727
728 auto const acctDelFee{drops(env.current()->fees().increment)};
729 env(acctdelete(becky, alice), Fee(acctDelFee));
730 env.close();
731
732 // Verify that becky's account root is gone.
733 BEAST_EXPECT(!env.closed()->exists(beckyAcctKey));
734 env.close();
735
736 // clang-format off
737 // Do a sanity check on each returned transaction. They should
738 // be returned in the reverse order of application to the ledger.
739 //
740 // Note that the first two transactions in sanity have not occurred
741 // yet. We'll see those after becky's account is resurrected.
742 static const NodeSanity kSanity[]
743 {
744 // txType, created, deleted, modified
745/* becky pays alice */ { 0, jss::Payment, {}, {}, {jss::AccountRoot, jss::AccountRoot}},
746/* alice resurrects becky's acct */ { 1, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}},
747/* becky deletes her account */ { 2, jss::AccountDelete, {}, {jss::AccountRoot}, {jss::AccountRoot}},
748/* becky's noop */ { 3, jss::AccountSet, {}, {}, {jss::AccountRoot}},
749/* "fund" sets flags */ { 4, jss::AccountSet, {}, {}, {jss::AccountRoot}},
750/* "fund" creates becky's acct */ { 5, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}}
751 };
752 // clang-format on
753
754 // Verify that we can recover becky's account_tx information even
755 // after the account is deleted.
756 {
757 json::Value params;
758 params[jss::account] = becky.human();
759 params[jss::ledger_index_min] = -1;
760 params[jss::ledger_index_max] = -1;
761
762 json::Value const result{env.rpc("json", "account_tx", to_string(params))};
763
764 BEAST_EXPECT(result[jss::result][jss::status] == "success");
765 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
766
767 // The first two transactions listed in sanity haven't happened yet.
768 static constexpr unsigned int kBeckyDeletedOffset = 2;
769 BEAST_EXPECT(
770 std::size(kSanity) ==
771 result[jss::result][jss::transactions].size() + kBeckyDeletedOffset);
772
773 json::Value const& txs{result[jss::result][jss::transactions]};
774
775 for (unsigned int index = kBeckyDeletedOffset; index < std::size(kSanity); ++index)
776 {
777 checkSanity(txs[index - kBeckyDeletedOffset], kSanity[index]);
778 }
779 }
780
781 // All it takes is a large enough XRP payment to resurrect
782 // becky's account. Try too small a payment.
783 env(pay(alice, becky, drops(env.current()->fees().accountReserve(0, 1)) - drops(1)),
785 env.close();
786
787 // Actually resurrect becky's account.
788 env(pay(alice, becky, XRP(45)));
789 env.close();
790
791 // becky's account root should be back.
792 BEAST_EXPECT(env.closed()->exists(beckyAcctKey));
793 BEAST_EXPECT(env.balance(becky) == XRP(45));
794
795 // becky pays alice.
796 env(pay(becky, alice, XRP(20)));
797 env.close();
798
799 // Setup is done. Look at the transactions returned by account_tx.
800 // Verify that account_tx locates all of becky's transactions.
801 json::Value params;
802 params[jss::account] = becky.human();
803 params[jss::ledger_index_min] = -1;
804 params[jss::ledger_index_max] = -1;
805
806 json::Value const result{env.rpc("json", "account_tx", to_string(params))};
807
808 BEAST_EXPECT(result[jss::result][jss::status] == "success");
809 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
810
811 BEAST_EXPECT(std::size(kSanity) == result[jss::result][jss::transactions].size());
812
813 json::Value const& txs{result[jss::result][jss::transactions]};
814
815 for (unsigned int index = 0; index < std::size(kSanity); ++index)
816 {
817 checkSanity(txs[index], kSanity[index]);
818 }
819 }
820
821 void
823 {
824 testcase("MPT");
825
826 using namespace test::jtx;
827 using namespace std::chrono_literals;
828
829 auto cfg = makeConfig();
830 cfg->fees.referenceFee = 10;
831 Env env(*this, std::move(cfg));
832
833 Account const alice{"alice"};
834 Account const bob{"bob"};
835 Account const carol{"carol"};
836
837 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
838
839 // check the latest mpt-related txn is in alice's account history
840 auto const checkAliceAcctTx = [&](size_t size, json::StaticString txType) {
841 json::Value params;
842 params[jss::account] = alice.human();
843 params[jss::limit] = 100;
844 auto const jv = env.rpc("json", "account_tx", to_string(params))[jss::result];
845
846 BEAST_EXPECT(jv[jss::transactions].size() == size);
847 auto const& tx0(jv[jss::transactions][0u][jss::tx]);
848 BEAST_EXPECT(tx0[jss::TransactionType] == txType);
849
850 std::string const txHash{
851 env.tx()->getJson(JsonOptions::Values::None)[jss::hash].asString()};
852 BEAST_EXPECT(tx0[jss::hash] == txHash);
853 };
854
855 // alice creates issuance
856 mptAlice.create(
857 {.ownerCount = 1,
858 .holderCount = 0,
859 .flags = tfMPTCanClawback | tfMPTRequireAuth | tfMPTCanTransfer});
860
861 checkAliceAcctTx(3, jss::MPTokenIssuanceCreate);
862
863 // bob creates a MPToken;
864 mptAlice.authorize({.account = bob});
865 checkAliceAcctTx(4, jss::MPTokenAuthorize);
866 env.close();
867
868 // TODO: windows pipeline fails validation for the hardcoded ledger hash
869 // due to having different test config, it can be uncommented after
870 // figuring out what happened
871 //
872 // ledger hash should be fixed regardless any change to account history
873 // BEAST_EXPECT(
874 // to_string(env.closed()->header().hash) ==
875 // "0BD507BB87D3C0E73B462485E6E381798A8C82FC49BF17FE39C60E08A1AF035D");
876
877 // alice authorizes bob
878 mptAlice.authorize({.account = alice, .holder = bob});
879 checkAliceAcctTx(5, jss::MPTokenAuthorize);
880
881 // carol creates a MPToken;
882 mptAlice.authorize({.account = carol});
883 checkAliceAcctTx(6, jss::MPTokenAuthorize);
884
885 // alice authorizes carol
886 mptAlice.authorize({.account = alice, .holder = carol});
887 checkAliceAcctTx(7, jss::MPTokenAuthorize);
888
889 // alice pays bob 100 tokens
890 mptAlice.pay(alice, bob, 100);
891 checkAliceAcctTx(8, jss::Payment);
892
893 // bob pays carol 10 tokens
894 mptAlice.pay(bob, carol, 10);
895 checkAliceAcctTx(9, jss::Payment);
896 }
897
898 void
900 {
901 testcase("Delegation Filtering");
902
903 using namespace test::jtx;
904
905 Env env(*this);
906 Account const alice{"alice"};
907 Account const bob{"bob"};
908 Account const carol{"carol"};
909
910 env.fund(XRP(10000), alice, bob, carol);
911 env.close();
912
913 // Normal TX: Alice pays Carol (Signed by Alice's Master Key)
914 env(pay(alice, carol, XRP(10)));
915 env.close();
916
917 // Setup Delegation: Alice allows Bob to sign Payments for her
918 env(delegate::set(alice, bob, {"Payment"}));
919 env.close();
920
921 // Delegated TX: Alice pays Bob (Signed by Bob using Delegation)
922 env(pay(alice, bob, XRP(20)), delegate::As(bob));
923 env.close();
924
925 // Normal TX: Bob pays Carol (Signed by Bob for himself)
926 env(pay(bob, carol, XRP(30)));
927 env.close();
928
929 auto const countTxs = [&](AccountID const& account,
930 json::Value const& delegateParams,
931 std::optional<std::uint32_t> const limit = std::nullopt) -> int {
932 int count = 0;
933 json::Value marker;
934 bool haveMarker = false;
935 int pages = 0;
936
937 while (true)
938 {
939 json::Value params;
940 params[jss::account] = toBase58(account);
941 params[jss::ledger_index_min] = -1;
942 params[jss::ledger_index_max] = -1;
943
944 if (!delegateParams.isNull())
945 params[jss::delegate] = delegateParams;
946 if (limit)
947 params[jss::limit] = *limit;
948 if (haveMarker)
949 params[jss::marker] = marker;
950
951 auto const res = env.rpc("json", "account_tx", to_string(params));
952 auto const& result = res[jss::result];
953
954 if (result.isMember(jss::transactions))
955 count += result[jss::transactions].size();
956
957 if (!limit || !result.isMember(jss::marker))
958 break;
959
960 marker = result[jss::marker];
961 haveMarker = true;
962 ++pages;
963 if (!BEAST_EXPECT(pages < 20))
964 break;
965 }
966
967 return count;
968 };
969
970 auto const checkError = [&](json::Value const& delegateParams,
971 std::string const& errToken) {
972 json::Value params;
973 params[jss::account] = alice.human();
974 params[jss::delegate] = delegateParams;
975 auto res = env.rpc("json", "account_tx", to_string(params));
976 BEAST_EXPECT(res[jss::result][jss::error] == errToken);
977 };
978
979 // Filter: Delegatee. Expects TX #2 (Signed by Bob)
980 {
981 json::Value p;
982 p[jss::delegate_filter] = "actor";
983 BEAST_EXPECT(countTxs(alice.id(), p) == 1);
984 }
985
986 // Filter: Delegatee + Counterparty Bob. Expects TX #2.
987 {
988 json::Value p;
989 p[jss::delegate_filter] = "actor";
990 p[jss::counter_party] = bob.human();
991 BEAST_EXPECT(countTxs(alice.id(), p) == 1);
992 }
993
994 // Filter: Delegatee + Counterparty Carol. Expects 0.
995 {
996 json::Value p;
997 p[jss::delegate_filter] = "actor";
998 p[jss::counter_party] = carol.human();
999 BEAST_EXPECT(countTxs(alice.id(), p) == 0);
1000 }
1001
1002 // Filter: Delegator. Expects TX #2.
1003 // (Bob signed it, but Alice is the owner).
1004 {
1005 json::Value p;
1006 p[jss::delegate_filter] = "authorizer";
1007 BEAST_EXPECT(countTxs(bob.id(), p) == 1);
1008 }
1009
1010 // Filter: Delegator + Counterparty Alice. Expects TX #2.
1011 {
1012 json::Value p;
1013 p[jss::delegate_filter] = "authorizer";
1014 p[jss::counter_party] = alice.human();
1015 BEAST_EXPECT(countTxs(bob.id(), p) == 1);
1016 }
1017
1018 // Filter: Authorizer. Expect: None.
1019 // TX #2 has sfDelegate present, but Alice is the delegator/owner, not
1020 // the delegate signer
1021 {
1022 json::Value p;
1023 p[jss::delegate_filter] = "authorizer";
1024 BEAST_EXPECT(countTxs(alice.id(), p) == 0);
1025 }
1026
1027 // Query Bob (Signer), Filter: Delegator, Counterparty: Carol
1028 // Expect: None (Alice is Owner, not Carol)
1029 {
1030 json::Value p;
1031 p[jss::delegate_filter] = "authorizer";
1032 p[jss::counter_party] = carol.human();
1033 BEAST_EXPECT(countTxs(bob.id(), p) == 0);
1034 }
1035
1036 // Query Bob (Signer), Filter: Delegatee
1037 // Expect: None. Bob did not employ a delegatee for his own TXs (TX C).
1038 {
1039 json::Value p;
1040 p[jss::delegate_filter] = "actor";
1041 BEAST_EXPECT(countTxs(bob.id(), p) == 0);
1042 }
1043
1044 // "delegate" is not an object (e.g., string)
1045 {
1046 json::Value const p = "not_an_object";
1047 checkError(p, "invalidParams");
1048 }
1049
1050 // Missing "delegate_filter" inside object
1051 {
1053 checkError(p, "invalidParams");
1054 }
1055
1056 // "delegate_filter" is not a string (e.g., int)
1057 {
1058 json::Value p;
1059 p[jss::delegate_filter] = 123;
1060 checkError(p, "invalidParams");
1061 }
1062
1063 // "delegate_filter" has invalid value
1064 {
1065 json::Value p;
1066 p[jss::delegate_filter] = "random_string";
1067 checkError(p, "invalidParams");
1068 }
1069
1070 // "counterparty" is not a string
1071 {
1072 json::Value p;
1073 p[jss::delegate_filter] = "actor";
1074 p[jss::counter_party] = 123;
1075 checkError(p, "invalidParams");
1076 }
1077
1078 // "counterparty" is malformed base58
1079 {
1080 json::Value p;
1081 p[jss::delegate_filter] = "actor";
1082 p[jss::counter_party] = "not_an_account";
1083 checkError(p, "actMalformed");
1084 }
1085
1086 // Multi-signed non-delegated TX: Alice pays Carol via multi-sig.
1087 // sfDelegate is absent and sfSigningPubKey is empty — the filter
1088 // must skip it without crashing.
1089 {
1090 Account const daria{"daria"};
1091 Account const edward{"edward"};
1092 env.fund(XRP(1000), daria, edward);
1093 env.close();
1094 env(signers(alice, 2, {{daria, 1}, {edward, 1}}));
1095 env.close();
1096 env(pay(alice, carol, XRP(1)),
1097 Fee(drops(env.current()->fees().increment * 2)),
1098 Msig(daria, edward));
1099 env.close();
1100
1101 // Alice's actor filter should still see only the 1 delegated tx,
1102 // not the multi-signed one.
1103 json::Value p;
1104 p[jss::delegate_filter] = "actor";
1105 BEAST_EXPECT(countTxs(alice.id(), p) == 1);
1106 }
1107
1108 // Regular-key-signed non-delegated TX: Alice pays Bob, signed by Bob
1109 // as Alice's regular key. This must not be treated as delegation.
1110 {
1111 env(regkey(alice, bob));
1112 env.close();
1113 env(pay(alice, bob, XRP(1)));
1114 env.close();
1115
1116 json::Value actorFilter;
1117 actorFilter[jss::delegate_filter] = "actor";
1118 BEAST_EXPECT(countTxs(alice.id(), actorFilter) == 1);
1119 // limit: 1 forces pagination past newer non-delegated rows.
1120 BEAST_EXPECT(countTxs(alice.id(), actorFilter, 1) == 1);
1121
1122 actorFilter[jss::counter_party] = bob.human();
1123 BEAST_EXPECT(countTxs(alice.id(), actorFilter) == 1);
1124 BEAST_EXPECT(countTxs(alice.id(), actorFilter, 1) == 1);
1125
1126 json::Value authorizerFilter;
1127 authorizerFilter[jss::delegate_filter] = "authorizer";
1128 BEAST_EXPECT(countTxs(bob.id(), authorizerFilter) == 1);
1129 BEAST_EXPECT(countTxs(bob.id(), authorizerFilter, 1) == 1);
1130
1131 authorizerFilter[jss::counter_party] = alice.human();
1132 BEAST_EXPECT(countTxs(bob.id(), authorizerFilter) == 1);
1133 BEAST_EXPECT(countTxs(bob.id(), authorizerFilter, 1) == 1);
1134 }
1135
1136 // Pagination marker/delegate-filter consistency. A marker returned by a
1137 // delegate-filtered query carries a `delegate` flag and is only valid
1138 // for a follow-up request that repeats the filter; mixing the two
1139 // marker conventions must be rejected with invalidParams.
1140 {
1141 json::Value actorFilter;
1142 actorFilter[jss::delegate_filter] = "actor";
1143
1144 // Obtain a delegate-filtered marker (limit 1 forces pagination).
1145 json::Value dp;
1146 dp[jss::account] = alice.human();
1147 dp[jss::ledger_index_min] = -1;
1148 dp[jss::ledger_index_max] = -1;
1149 dp[jss::delegate] = actorFilter;
1150 dp[jss::limit] = 1;
1151 auto const dRes = env.rpc("json", "account_tx", to_string(dp));
1152 BEAST_EXPECT(dRes[jss::result].isMember(jss::marker));
1153 json::Value const delegateMarker = dRes[jss::result][jss::marker];
1154 BEAST_EXPECT(
1155 delegateMarker.isMember(jss::delegate) && delegateMarker[jss::delegate].asBool());
1156
1157 // Reusing a delegate marker without the delegate filter is rejected.
1158 {
1159 json::Value p;
1160 p[jss::account] = alice.human();
1161 p[jss::ledger_index_min] = -1;
1162 p[jss::ledger_index_max] = -1;
1163 p[jss::limit] = 1;
1164 p[jss::marker] = delegateMarker;
1165 auto const r = env.rpc("json", "account_tx", to_string(p));
1166 BEAST_EXPECT(r[jss::result][jss::error] == "invalidParams");
1167 }
1168
1169 // Obtain a non-delegate marker; it must not carry the flag.
1170 json::Value np;
1171 np[jss::account] = alice.human();
1172 np[jss::ledger_index_min] = -1;
1173 np[jss::ledger_index_max] = -1;
1174 np[jss::limit] = 1;
1175 auto const nRes = env.rpc("json", "account_tx", to_string(np));
1176 BEAST_EXPECT(nRes[jss::result].isMember(jss::marker));
1177 json::Value const normalMarker = nRes[jss::result][jss::marker];
1178 BEAST_EXPECT(!normalMarker.isMember(jss::delegate));
1179
1180 // Reusing a non-delegate marker with a delegate filter is rejected.
1181 {
1182 json::Value p;
1183 p[jss::account] = alice.human();
1184 p[jss::ledger_index_min] = -1;
1185 p[jss::ledger_index_max] = -1;
1186 p[jss::limit] = 1;
1187 p[jss::delegate] = actorFilter;
1188 p[jss::marker] = normalMarker;
1189 auto const r = env.rpc("json", "account_tx", to_string(p));
1190 BEAST_EXPECT(r[jss::result][jss::error] == "invalidParams");
1191 }
1192 }
1193 }
1194
1195 void
1197 {
1198 testcase("Delegation filter with multi-signed delegatee");
1199 using namespace test::jtx;
1200
1201 Env env(*this);
1202 Account const alice{"alice"};
1203 Account const bob{"bob"};
1204 Account const carol{"carol"};
1205 Account const daria{"daria"};
1206 Account const edward{"edward"};
1207
1208 env.fund(XRP(10000), alice, bob, carol, daria, edward);
1209 env.close();
1210
1211 // Bob's identity is established via multi-sig (daria + edward)
1212 env(signers(bob, 2, {{daria, 1}, {edward, 1}}));
1213 env.close();
1214
1215 env(delegate::set(alice, bob, {"Payment"}));
1216 env.close();
1217
1218 // Delegated tx: Alice pays Carol, Bob signs via multi-sig
1219 env(pay(alice, carol, XRP(10)), Fee(XRP(1)), delegate::As(bob), Msig(daria, edward));
1220 env.close();
1221
1222 auto const countTxs = [&](AccountID const& account,
1223 json::Value const& delegateParams) -> int {
1224 json::Value params;
1225 params[jss::account] = toBase58(account);
1226 params[jss::ledger_index_min] = -1;
1227 params[jss::ledger_index_max] = -1;
1228 params[jss::delegate] = delegateParams;
1229
1230 auto const res = env.rpc("json", "account_tx", to_string(params));
1231
1232 if (res[jss::result].isMember(jss::transactions))
1233 return res[jss::result][jss::transactions].size();
1234 return 0;
1235 };
1236
1237 // Alice (owner) finds the tx with actor filter
1238 {
1239 json::Value p;
1240 p[jss::delegate_filter] = "actor";
1241 BEAST_EXPECT(countTxs(alice.id(), p) == 1);
1242 }
1243
1244 // Alice (owner) + counterparty Bob finds the tx
1245 {
1246 json::Value p;
1247 p[jss::delegate_filter] = "actor";
1248 p[jss::counter_party] = bob.human();
1249 BEAST_EXPECT(countTxs(alice.id(), p) == 1);
1250 }
1251
1252 // Bob (delegatee) finds the tx with authorizer filter
1253 {
1254 json::Value p;
1255 p[jss::delegate_filter] = "authorizer";
1256 BEAST_EXPECT(countTxs(bob.id(), p) == 1);
1257 }
1258
1259 // Bob (delegatee) + counterparty Alice finds the tx
1260 {
1261 json::Value p;
1262 p[jss::delegate_filter] = "authorizer";
1263 p[jss::counter_party] = alice.human();
1264 BEAST_EXPECT(countTxs(bob.id(), p) == 1);
1265 }
1266 }
1267
1268 void
1270 {
1271 testcase("Delegation filter marker within a single query page");
1272
1273 using namespace test::jtx;
1274
1275 Env env(*this);
1276 Account const alice{"alice"};
1277 Account const bob{"bob"};
1278 Account const carol{"carol"};
1279
1280 env.fund(XRP(10000), alice, bob, carol);
1281 env.close();
1282
1283 env(delegate::set(alice, bob, {"Payment"}));
1284 env.close();
1285
1286 auto const startLedger = env.closed()->header().seq + 1;
1287
1288 env(pay(alice, carol, XRP(1)), delegate::As(bob));
1289 env.close();
1290 env(pay(alice, carol, XRP(1)), delegate::As(bob));
1291 env.close();
1292
1293 json::Value p;
1294 p[jss::delegate_filter] = "actor";
1295
1296 json::Value params;
1297 params[jss::account] = alice.human();
1298 params[jss::ledger_index_min] = startLedger;
1299 params[jss::ledger_index_max] = -1;
1300 params[jss::delegate] = p;
1301 params[jss::limit] = 1;
1302
1303 auto const res = env.rpc("json", "account_tx", to_string(params));
1304 auto const& result = res[jss::result];
1305
1306 // The first page emits only the first delegated payment. (as page limit is set to 1)
1307 BEAST_EXPECT(result[jss::transactions].size() == 1);
1308 BEAST_EXPECT(result.isMember(jss::marker));
1309
1310 // Following the marker resumes right after the first payment and
1311 // returns the second one.
1312 json::Value page2 = params;
1313 page2[jss::marker] = result[jss::marker];
1314 auto const res2 = env.rpc("json", "account_tx", to_string(page2));
1315 BEAST_EXPECT(res2[jss::result][jss::transactions].size() == 1);
1316 }
1317
1318 void
1320 {
1321 // test all sponsored transactions are in sponsor and sponsee's account
1322 // tx list
1323 testcase("Sponsorship");
1324
1325 using namespace test::jtx;
1326 Env env(*this);
1327 Account const alice("alice");
1328 Account const sponsor("sponsor");
1329 Account const sponsor2("sponsor2");
1330 env.fund(XRP(10000), alice, sponsor, sponsor2);
1331 env.close();
1332
1333 // check the latest sponsorship-related txn is in account tx list
1334 auto const checkTx = [&](Account const& account, json::StaticString txType) {
1335 json::Value params;
1336 params[jss::account] = account.human();
1337 params[jss::limit] = 100;
1338 auto const jv = env.rpc("json", "account_tx", to_string(params))[jss::result];
1339
1340 auto const& tx0(jv[jss::transactions][0u][jss::tx]);
1341 BEAST_EXPECT(tx0[jss::TransactionType] == txType);
1342
1343 std::string const txHash{
1344 env.tx()->getJson(JsonOptions::Values::None)[jss::hash].asString()};
1345 BEAST_EXPECT(tx0[jss::hash] == txHash);
1346 };
1347
1348 // fee sponsorship
1349 env(noop(alice), sponsor::As(sponsor, spfSponsorFee), Sig(sfSponsorSignature, sponsor));
1350 env.close();
1351 checkTx(alice, jss::AccountSet);
1352 checkTx(sponsor, jss::AccountSet);
1353
1354 // set sponsor
1355 env(sponsor::set(sponsor, 0, 100, XRP(100)), sponsor::SponseeAcc(alice), Ter(tesSUCCESS));
1356 env.close();
1357 checkTx(alice, jss::SponsorshipSet);
1358 checkTx(sponsor, jss::SponsorshipSet);
1359
1360 // create an object with sponsor
1361 auto const checkId = keylet::check(alice, SeqProxy::rawSequence(env.seq(alice))).key;
1363 env.close();
1364 checkTx(alice, jss::CheckCreate);
1365 checkTx(sponsor, jss::CheckCreate);
1366
1367 // transfer object sponsorship
1368 env(sponsor::transfer(alice, tfSponsorshipReassign, checkId),
1369 sponsor::As(sponsor2, spfSponsorReserve),
1370 Sig(sfSponsorSignature, sponsor2));
1371 env.close();
1372 checkTx(alice, jss::SponsorshipTransfer);
1373 checkTx(sponsor, jss::SponsorshipTransfer);
1374 checkTx(sponsor2, jss::SponsorshipTransfer);
1375
1376 // delete the sponsored object
1377 env(check::cancel(alice, checkId),
1379 Sig(sfSponsorSignature, sponsor));
1380 env.close();
1381 checkTx(alice, jss::CheckCancel);
1382 checkTx(sponsor, jss::CheckCancel);
1383 checkTx(sponsor2, jss::CheckCancel);
1384
1385 // account sponsorship
1386 env(sponsor::transfer(alice, tfSponsorshipCreate),
1388 Sig(sfSponsorSignature, sponsor));
1389 env.close();
1390 checkTx(alice, jss::SponsorshipTransfer);
1391 checkTx(sponsor, jss::SponsorshipTransfer);
1392 }
1393
1394public:
1395 void
1396 run() override
1397 {
1398 forAllApiVersions([this](unsigned apiVersion) { testParameters(apiVersion); });
1399 testContents();
1401 testMPT();
1406 }
1407};
1409
1410} // namespace xrpl::test
A testsuite class.
Definition suite.h:52
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:554
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:155
Lightweight wrapper to tag static string.
Definition json_value.h:48
Represents a JSON value.
Definition json_value.h:117
bool asBool() const
Value & append(Value const &value)
Append value to array at the end.
UInt size() const
Number of values in array or object.
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:48
std::chrono::duration< rep, period > duration
Definition chrono.h:47
Slice slice() const noexcept
Definition PublicKey.h:115
static constexpr SeqProxy rawSequence(std::uint32_t v)
Factory function to return a sequence-based SeqProxy.
Definition SeqProxy.h:62
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:21
std::string const & human() const
Returns the human readable public key.
PublicKey const & pk() const
Return the public key.
Definition jtx/Account.h:84
AccountID id() const
Returns the Account ID.
A transaction testing environment.
Definition Env.h:161
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:323
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:302
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:1056
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:560
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:377
NetClock::time_point now()
Returns the current network time.
Definition Env.h:326
Set the fee on a JTx.
Definition fee.h:20
Test helper for creating, mutating, and asserting MPT and confidential MPT ledger state.
Definition mpt.h:447
void create(MPTCreate const &arg=MPTCreate{})
Definition mpt.cpp:241
Set a multisignature on a JTx.
Definition multisign.h:53
Set the regular signature on a JTx.
Definition sig.h:19
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:18
Set a ticket sequence on a JTx.
Definition ticket.h:36
@ Array
array value (ordered list)
Definition json_value.h:28
@ Object
object value (collection of name/value pairs).
Definition json_value.h:29
@ Null
'null' value
Definition json_value.h:22
Keylet payChannel(AccountID const &src, AccountID const &dst, SeqProxy const &seq) noexcept
A PaymentChannel.
Definition Indexes.cpp:394
Keylet check(AccountID const &id, SeqProxy const &seq) noexcept
A Check.
Definition Indexes.cpp:338
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:198
API version numbers used in later API versions.
Definition ApiVersion.h:36
std::string expectedFieldMessage(std::string const &name, std::string const &type)
Definition ErrorCodes.h:297
ErrorInfo const & getErrorInfo(ErrorCodeI code)
Returns an ErrorInfo that reflects the error code.
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 set(jtx::Account const &account, jtx::Account const &authorize, std::vector< std::string > const &permissions)
Definition delegate.cpp:17
json::Value auth(Account const &account, Account const &auth)
Preauthorize for deposit.
Definition deposit.cpp:16
Escrow operations.
Definition escrow.h:19
json::Value transfer(jtx::Account const &account, uint32_t flags, std::optional< uint256 > const &index)
Definition sponsor.cpp:52
json::Value set(jtx::Account const &account, uint32_t flags, std::optional< int32_t > const reserveCountDelta, std::optional< STAmount > const feeAmountDelta, std::optional< STAmount > const maxFee)
Definition sponsor.cpp:22
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:14
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:37
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
constexpr FlagValue spfSponsorFee
Definition TxFlags.h:459
ErrorCodeI
Definition ErrorCodes.h:23
@ RpcLgrNotFound
Definition ErrorCodes.h:55
@ RpcActMalformed
Definition ErrorCodes.h:73
@ RpcLgrIdxsInvalid
Definition ErrorCodes.h:95
@ RpcInvalidParams
Definition ErrorCodes.h:67
@ RpcLgrNotValidated
Definition ErrorCodes.h:56
@ RpcInvalidLgrRange
Definition ErrorCodes.h:119
@ RpcLgrIdxMalformed
Definition ErrorCodes.h:96
void forAllApiVersions(Fn const &fn, Args &&... args)
Definition ApiVersion.h:159
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:13
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:651
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:34
@ tecNO_DST_INSUF_XRP
Definition TER.h:294
constexpr FlagValue spfSponsorReserve
Definition TxFlags.h:460
@ tesSUCCESS
Definition TER.h:245
T size(T... args)
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:20
uint256 key
Definition Keylet.h:21
json::StaticString token
Definition ErrorCodes.h:194
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)