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 {
323 json::Value p{jParams};
324
325 p[jss::ledger_index] = "validated";
326 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
327
328 p[jss::ledger_index] = "closed";
329 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
330
331 p[jss::ledger_index] = "current";
332 BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), RpcLgrNotValidated));
333
334 p[jss::ledger_index] = "";
335 BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), RpcLgrNotValidated));
336 }
337
338 // Ledger Hash
339 {
340 json::Value p{jParams};
341
342 p[jss::ledger_hash] = to_string(env.closed()->header().hash);
343 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
344
345 p[jss::ledger_hash] = to_string(env.closed()->header().parentHash);
346 BEAST_EXPECT(noTxs(env.rpc("json", "account_tx", to_string(p))));
347 }
348
349 // Ledger index max/min/index all specified
350 // ERRORS out with invalid Parenthesis
351 {
352 jParams[jss::account] = "0xDEADBEEF";
353 jParams[jss::account] = a1.human();
354 json::Value p{jParams};
355
356 p[jss::ledger_index_max] = -1;
357 p[jss::ledger_index_min] = -1;
358 p[jss::ledger_index] = -1;
359
360 if (apiVersion < 2u)
361 {
362 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
363 }
364 else
365 {
366 BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), RpcInvalidParams));
367 }
368 }
369
370 // Ledger index max only
371 {
372 json::Value p{jParams};
373 p[jss::ledger_index_max] = env.current()->header().seq;
374 if (apiVersion < 2u)
375 {
376 BEAST_EXPECT(hasTxs(env.rpc(apiVersion, "json", "account_tx", to_string(p))));
377 }
378 else
379 {
380 BEAST_EXPECT(
381 isErr(env.rpc("json", "account_tx", to_string(p)), RpcLgrIdxMalformed));
382 }
383 }
384 // test account non-string
385 {
386 auto testInvalidAccountParam = [&](auto const& param) {
387 json::Value params;
388 params[jss::account] = param;
389 auto jrr = env.rpc("json", "account_tx", to_string(params))[jss::result];
390 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
391 BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'account'.");
392 };
393
394 testInvalidAccountParam(1);
395 testInvalidAccountParam(1.1);
396 testInvalidAccountParam(true);
397 testInvalidAccountParam(json::Value(json::ValueType::Null));
398 testInvalidAccountParam(json::Value(json::ValueType::Object));
399 testInvalidAccountParam(json::Value(json::ValueType::Array));
400 }
401 // test binary and forward for bool/non bool values
402 {
403 json::Value p{jParams};
404 p[jss::binary] = "asdf";
405 if (apiVersion < 2u)
406 {
407 json::Value result{env.rpc("json", "account_tx", to_string(p))};
408 BEAST_EXPECT(result[jss::result][jss::status] == "success");
409 }
410 else
411 {
412 BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), RpcInvalidParams));
413 }
414
415 p[jss::binary] = true;
416 json::Value result{env.rpc("json", "account_tx", to_string(p))};
417 BEAST_EXPECT(result[jss::result][jss::status] == "success");
418
419 p[jss::forward] = "true";
420 if (apiVersion < 2u)
421 {
422 BEAST_EXPECT(result[jss::result][jss::status] == "success");
423 }
424 else
425 {
426 BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), RpcInvalidParams));
427 }
428
429 p[jss::forward] = false;
430 result = env.rpc("json", "account_tx", to_string(p));
431 BEAST_EXPECT(result[jss::result][jss::status] == "success");
432 }
433 // test limit with malformed values
434 {
435 json::Value p{jParams};
436
437 // Test case: limit = 0 should fail (below minimum)
438 p[jss::limit] = 0;
439 BEAST_EXPECT(isErr(env.rpc("json", "account_tx", to_string(p)), RpcInvalidParams));
440
441 // Test case: limit = 1.2 should fail (not an integer)
442 p[jss::limit] = 1.2;
443 BEAST_EXPECT(
444 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
445 rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
446
447 // Test case: limit = "10" should fail (string instead of integer)
448 p[jss::limit] = "10";
449 BEAST_EXPECT(
450 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
451 rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
452
453 // Test case: limit = true should fail (boolean instead of integer)
454 p[jss::limit] = true;
455 BEAST_EXPECT(
456 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
457 rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
458
459 // Test case: limit = false should fail (boolean instead of integer)
460 p[jss::limit] = false;
461 BEAST_EXPECT(
462 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
463 rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
464
465 // Test case: limit = -1 should fail (negative number)
466 p[jss::limit] = -1;
467 BEAST_EXPECT(
468 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
469 rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
470
471 // Test case: limit = [] should fail (array instead of integer)
472 p[jss::limit] = json::Value(json::ValueType::Array);
473 BEAST_EXPECT(
474 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
475 rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
476
477 // Test case: limit = {} should fail (object instead of integer)
478 p[jss::limit] = json::Value(json::ValueType::Object);
479 BEAST_EXPECT(
480 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
481 rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
482
483 // Test case: limit = "malformed" should fail (malformed string)
484 p[jss::limit] = "malformed";
485 BEAST_EXPECT(
486 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
487 rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
488
489 // Test case: limit = ["limit"] should fail (array with string)
490 p[jss::limit] = json::Value(json::ValueType::Array);
491 p[jss::limit].append("limit");
492 BEAST_EXPECT(
493 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
494 rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
495
496 // Test case: limit = {"limit": 10} should fail (object with
497 // property)
498 p[jss::limit] = json::Value(json::ValueType::Object);
499 p[jss::limit][jss::limit] = 10;
500 BEAST_EXPECT(
501 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::error_message] ==
502 rpc::expectedFieldMessage(jss::limit, "unsigned integer"));
503
504 // Test case: limit = 10 should succeed (valid integer)
505 p[jss::limit] = 10;
506 BEAST_EXPECT(
507 env.rpc("json", "account_tx", to_string(p))[jss::result][jss::status] == "success");
508 }
509 }
510
511 void
513 {
514 testcase("Contents");
515
516 // Get results for all transaction types that can be associated
517 // with an account. Start by generating all transaction types.
518 using namespace test::jtx;
519 using namespace std::chrono_literals;
520
521 Env env(*this);
522 Account const alice{"alice"};
523 Account const alie{"alie"};
524 Account const gw{"gw"};
525 auto const usd{gw["USD"]};
526
527 env.fund(XRP(1000000), alice, gw);
528 env.close();
529
530 // AccountSet
531 env(noop(alice));
532
533 // Payment
534 env(pay(alice, gw, XRP(100)));
535
536 // Regular key set
537 env(regkey(alice, alie));
538 env.close();
539
540 // Trust and Offers
541 env(trust(alice, usd(200)), Sig(alie));
542 std::uint32_t const offerSeq{env.seq(alice)};
543 env(offer(alice, usd(50), XRP(150)), Sig(alie));
544 env.close();
545
546 env(offerCancel(alice, offerSeq), Sig(alie));
547 env.close();
548
549 // SignerListSet
550 env(signers(alice, 1, {{"bogie", 1}, {"demon", 1}}), Sig(alie));
551
552 // Escrow
553 {
554 // Create an escrow. Requires either a CancelAfter or FinishAfter.
555 auto escrow = [](Account const& account, Account const& to, STAmount const& amount) {
557 escrow[jss::TransactionType] = jss::EscrowCreate;
558 escrow[jss::Account] = account.human();
559 escrow[jss::Destination] = to.human();
560 escrow[jss::Amount] = amount.getJson(JsonOptions::Values::None);
561 return escrow;
562 };
563
564 NetClock::time_point const nextTime{env.now() + 2s};
565
566 json::Value escrowWithFinish{escrow(alice, alice, XRP(500))};
567 escrowWithFinish[sfFinishAfter.jsonName] = nextTime.time_since_epoch().count();
568
569 std::uint32_t const escrowFinishSeq{env.seq(alice)};
570 env(escrowWithFinish, Sig(alie));
571
572 json::Value escrowWithCancel{escrow(alice, alice, XRP(500))};
573 escrowWithCancel[sfFinishAfter.jsonName] = nextTime.time_since_epoch().count();
574 escrowWithCancel[sfCancelAfter.jsonName] = nextTime.time_since_epoch().count() + 1;
575
576 std::uint32_t const escrowCancelSeq{env.seq(alice)};
577 env(escrowWithCancel, Sig(alie));
578 env.close();
579
580 {
581 json::Value escrowFinish;
582 escrowFinish[jss::TransactionType] = jss::EscrowFinish;
583 escrowFinish[jss::Account] = alice.human();
584 escrowFinish[sfOwner.jsonName] = alice.human();
585 escrowFinish[sfOfferSequence.jsonName] = escrowFinishSeq;
586 env(escrowFinish, Sig(alie));
587 }
588 {
589 json::Value escrowCancel;
590 escrowCancel[jss::TransactionType] = jss::EscrowCancel;
591 escrowCancel[jss::Account] = alice.human();
592 escrowCancel[sfOwner.jsonName] = alice.human();
593 escrowCancel[sfOfferSequence.jsonName] = escrowCancelSeq;
594 env(escrowCancel, Sig(alie));
595 }
596 env.close();
597 }
598
599 // PayChan
600 {
601 std::uint32_t const payChanSeq{env.seq(alice)};
602 json::Value payChanCreate;
603 payChanCreate[jss::TransactionType] = jss::PaymentChannelCreate;
604 payChanCreate[jss::Account] = alice.human();
605 payChanCreate[jss::Destination] = gw.human();
606 payChanCreate[jss::Amount] = XRP(500).value().getJson(JsonOptions::Values::None);
607 payChanCreate[sfSettleDelay.jsonName] = NetClock::duration{100s}.count();
608 payChanCreate[sfPublicKey.jsonName] = strHex(alice.pk().slice());
609 env(payChanCreate, Sig(alie));
610 env.close();
611
612 std::string const payChanIndex{
613 strHex(keylet::payChannel(alice, gw, SeqProxy::rawSequence(payChanSeq)).key)};
614
615 {
616 json::Value payChanFund;
617 payChanFund[jss::TransactionType] = jss::PaymentChannelFund;
618 payChanFund[jss::Account] = alice.human();
619 payChanFund[sfChannel.jsonName] = payChanIndex;
620 payChanFund[jss::Amount] = XRP(200).value().getJson(JsonOptions::Values::None);
621 env(payChanFund, Sig(alie));
622 env.close();
623 }
624 {
625 json::Value payChanClaim;
626 payChanClaim[jss::TransactionType] = jss::PaymentChannelClaim;
627 payChanClaim[jss::Flags] = tfClose;
628 payChanClaim[jss::Account] = gw.human();
629 payChanClaim[sfChannel.jsonName] = payChanIndex;
630 payChanClaim[sfPublicKey.jsonName] = strHex(alice.pk().slice());
631 env(payChanClaim);
632 env.close();
633 }
634 }
635
636 // Check
637 {
638 auto const aliceCheckId =
639 keylet::check(alice, SeqProxy::rawSequence(env.seq(alice))).key;
640 env(check::create(alice, gw, XRP(300)), Sig(alie));
641
642 auto const gwCheckId = keylet::check(gw, SeqProxy::rawSequence(env.seq(gw))).key;
643 env(check::create(gw, alice, XRP(200)));
644 env.close();
645
646 env(check::cash(alice, gwCheckId, XRP(200)), Sig(alie));
647 env(check::cancel(alice, aliceCheckId), Sig(alie));
648 env.close();
649 }
650 {
651 // Deposit pre-authorization with a Ticket.
652 std::uint32_t const tktSeq{env.seq(alice) + 1};
653 env(ticket::create(alice, 1), Sig(alie));
654 env.close();
655
656 env(deposit::auth(alice, gw), ticket::Use(tktSeq), Sig(alie));
657 env.close();
658 }
659
660 // Setup is done. Look at the transactions returned by account_tx.
661 json::Value params;
662 params[jss::account] = alice.human();
663 params[jss::ledger_index_min] = -1;
664 params[jss::ledger_index_max] = -1;
665
666 json::Value const result{env.rpc("json", "account_tx", to_string(params))};
667
668 BEAST_EXPECT(result[jss::result][jss::status] == "success");
669 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
670
671 json::Value const& txs{result[jss::result][jss::transactions]};
672
673 // clang-format off
674 // Do a sanity check on each returned transaction. They should
675 // be returned in the reverse order of application to the ledger.
676 static const NodeSanity kSanity[]{
677 // txType, created, deleted, modified
678 {0, jss::DepositPreauth, {jss::DepositPreauth}, {jss::Ticket}, {jss::AccountRoot, jss::DirectoryNode}},
679 {1, jss::TicketCreate, {jss::Ticket}, {}, {jss::AccountRoot, jss::DirectoryNode}},
680 {2, jss::CheckCancel, {}, {jss::Check}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
681 {3, jss::CheckCash, {}, {jss::Check}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
682 {4, jss::CheckCreate, {jss::Check}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
683 {5, jss::CheckCreate, {jss::Check}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
684 {6, jss::PaymentChannelClaim, {}, {jss::PayChannel}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
685 {7, jss::PaymentChannelFund, {}, {}, {jss::AccountRoot, jss::PayChannel}},
686 {8, jss::PaymentChannelCreate, {jss::PayChannel}, {}, {jss::AccountRoot, jss::AccountRoot, jss::DirectoryNode, jss::DirectoryNode}},
687 {9, jss::EscrowCancel, {}, {jss::Escrow}, {jss::AccountRoot, jss::DirectoryNode}},
688 {10, jss::EscrowFinish, {}, {jss::Escrow}, {jss::AccountRoot, jss::DirectoryNode}},
689 {11, jss::EscrowCreate, {jss::Escrow}, {}, {jss::AccountRoot, jss::DirectoryNode}},
690 {12, jss::EscrowCreate, {jss::Escrow}, {}, {jss::AccountRoot, jss::DirectoryNode}},
691 {13, jss::SignerListSet, {jss::SignerList}, {}, {jss::AccountRoot, jss::DirectoryNode}},
692 {14, jss::OfferCancel, {}, {jss::Offer, jss::DirectoryNode}, {jss::AccountRoot, jss::DirectoryNode}},
693 {15, jss::OfferCreate, {jss::Offer, jss::DirectoryNode}, {}, {jss::AccountRoot, jss::DirectoryNode}},
694 {16, jss::TrustSet, {jss::RippleState, jss::DirectoryNode, jss::DirectoryNode}, {}, {jss::AccountRoot, jss::AccountRoot}},
695 {17, jss::SetRegularKey, {}, {}, {jss::AccountRoot}},
696 {18, jss::Payment, {}, {}, {jss::AccountRoot, jss::AccountRoot}},
697 {19, jss::AccountSet, {}, {}, {jss::AccountRoot}},
698 {20, jss::AccountSet, {}, {}, {jss::AccountRoot}},
699 {21, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}},
700 };
701 // clang-format on
702
703 BEAST_EXPECT(std::size(kSanity) == result[jss::result][jss::transactions].size());
704
705 for (unsigned int index{0}; index < std::size(kSanity); ++index)
706 {
707 checkSanity(txs[index], kSanity[index]);
708 }
709 }
710
711 void
713 {
714 testcase("AccountDelete");
715
716 // Verify that if an account is resurrected then the account_tx RPC
717 // command still recovers all transactions on that account before
718 // and after resurrection.
719 using namespace test::jtx;
720 using namespace std::chrono_literals;
721
722 Env env(*this);
723 Account const alice{"alice"};
724 Account const becky{"becky"};
725
726 env.fund(XRP(10000), alice, becky);
727 env.close();
728
729 // Verify that becky's account root is present.
730 Keylet const beckyAcctKey{keylet::account(becky.id())};
731 BEAST_EXPECT(env.closed()->exists(beckyAcctKey));
732
733 // becky does an AccountSet .
734 env(noop(becky));
735
736 // Close enough ledgers to be able to delete becky's account.
737 std::uint32_t const ledgerCount{env.current()->seq() + 257 - env.seq(becky)};
738
739 for (std::uint32_t i = 0; i < ledgerCount; ++i)
740 env.close();
741
742 auto const beckyPreDelBalance{env.balance(becky)};
743
744 auto const acctDelFee{drops(env.current()->fees().increment)};
745 env(acctdelete(becky, alice), Fee(acctDelFee));
746 env.close();
747
748 // Verify that becky's account root is gone.
749 BEAST_EXPECT(!env.closed()->exists(beckyAcctKey));
750 env.close();
751
752 // clang-format off
753 // Do a sanity check on each returned transaction. They should
754 // be returned in the reverse order of application to the ledger.
755 //
756 // Note that the first two transactions in sanity have not occurred
757 // yet. We'll see those after becky's account is resurrected.
758 static const NodeSanity kSanity[]
759 {
760 // txType, created, deleted, modified
761/* becky pays alice */ { 0, jss::Payment, {}, {}, {jss::AccountRoot, jss::AccountRoot}},
762/* alice resurrects becky's acct */ { 1, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}},
763/* becky deletes her account */ { 2, jss::AccountDelete, {}, {jss::AccountRoot}, {jss::AccountRoot}},
764/* becky's noop */ { 3, jss::AccountSet, {}, {}, {jss::AccountRoot}},
765/* "fund" sets flags */ { 4, jss::AccountSet, {}, {}, {jss::AccountRoot}},
766/* "fund" creates becky's acct */ { 5, jss::Payment, {jss::AccountRoot}, {}, {jss::AccountRoot}}
767 };
768 // clang-format on
769
770 // Verify that we can recover becky's account_tx information even
771 // after the account is deleted.
772 {
773 json::Value params;
774 params[jss::account] = becky.human();
775 params[jss::ledger_index_min] = -1;
776 params[jss::ledger_index_max] = -1;
777
778 json::Value const result{env.rpc("json", "account_tx", to_string(params))};
779
780 BEAST_EXPECT(result[jss::result][jss::status] == "success");
781 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
782
783 // The first two transactions listed in sanity haven't happened yet.
784 static constexpr unsigned int kBeckyDeletedOffset = 2;
785 BEAST_EXPECT(
786 std::size(kSanity) ==
787 result[jss::result][jss::transactions].size() + kBeckyDeletedOffset);
788
789 json::Value const& txs{result[jss::result][jss::transactions]};
790
791 for (unsigned int index = kBeckyDeletedOffset; index < std::size(kSanity); ++index)
792 {
793 checkSanity(txs[index - kBeckyDeletedOffset], kSanity[index]);
794 }
795 }
796
797 // All it takes is a large enough XRP payment to resurrect
798 // becky's account. Try too small a payment.
799 env(pay(alice, becky, drops(env.current()->fees().accountReserve(0, 1)) - drops(1)),
801 env.close();
802
803 // Actually resurrect becky's account.
804 env(pay(alice, becky, XRP(45)));
805 env.close();
806
807 // becky's account root should be back.
808 BEAST_EXPECT(env.closed()->exists(beckyAcctKey));
809 BEAST_EXPECT(env.balance(becky) == XRP(45));
810
811 // becky pays alice.
812 env(pay(becky, alice, XRP(20)));
813 env.close();
814
815 // Setup is done. Look at the transactions returned by account_tx.
816 // Verify that account_tx locates all of becky's transactions.
817 json::Value params;
818 params[jss::account] = becky.human();
819 params[jss::ledger_index_min] = -1;
820 params[jss::ledger_index_max] = -1;
821
822 json::Value const result{env.rpc("json", "account_tx", to_string(params))};
823
824 BEAST_EXPECT(result[jss::result][jss::status] == "success");
825 BEAST_EXPECT(result[jss::result][jss::transactions].isArray());
826
827 BEAST_EXPECT(std::size(kSanity) == result[jss::result][jss::transactions].size());
828
829 json::Value const& txs{result[jss::result][jss::transactions]};
830
831 for (unsigned int index = 0; index < std::size(kSanity); ++index)
832 {
833 checkSanity(txs[index], kSanity[index]);
834 }
835 }
836
837 void
839 {
840 testcase("MPT");
841
842 using namespace test::jtx;
843 using namespace std::chrono_literals;
844
845 auto cfg = makeConfig();
846 cfg->fees.referenceFee = 10;
847 Env env(*this, std::move(cfg));
848
849 Account const alice{"alice"};
850 Account const bob{"bob"};
851 Account const carol{"carol"};
852
853 MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
854
855 // check the latest mpt-related txn is in alice's account history
856 auto const checkAliceAcctTx = [&](size_t size, json::StaticString txType) {
857 json::Value params;
858 params[jss::account] = alice.human();
859 params[jss::limit] = 100;
860 auto const jv = env.rpc("json", "account_tx", to_string(params))[jss::result];
861
862 BEAST_EXPECT(jv[jss::transactions].size() == size);
863 auto const& tx0(jv[jss::transactions][0u][jss::tx]);
864 BEAST_EXPECT(tx0[jss::TransactionType] == txType);
865
866 std::string const txHash{
867 env.tx()->getJson(JsonOptions::Values::None)[jss::hash].asString()};
868 BEAST_EXPECT(tx0[jss::hash] == txHash);
869 };
870
871 // alice creates issuance
872 mptAlice.create(
873 {.ownerCount = 1,
874 .holderCount = 0,
875 .flags = tfMPTCanClawback | tfMPTRequireAuth | tfMPTCanTransfer});
876
877 checkAliceAcctTx(3, jss::MPTokenIssuanceCreate);
878
879 // bob creates a MPToken;
880 mptAlice.authorize({.account = bob});
881 checkAliceAcctTx(4, jss::MPTokenAuthorize);
882 env.close();
883
884 // TODO: windows pipeline fails validation for the hardcoded ledger hash
885 // due to having different test config, it can be uncommented after
886 // figuring out what happened
887 //
888 // ledger hash should be fixed regardless any change to account history
889 // BEAST_EXPECT(
890 // to_string(env.closed()->header().hash) ==
891 // "0BD507BB87D3C0E73B462485E6E381798A8C82FC49BF17FE39C60E08A1AF035D");
892
893 // alice authorizes bob
894 mptAlice.authorize({.account = alice, .holder = bob});
895 checkAliceAcctTx(5, jss::MPTokenAuthorize);
896
897 // carol creates a MPToken;
898 mptAlice.authorize({.account = carol});
899 checkAliceAcctTx(6, jss::MPTokenAuthorize);
900
901 // alice authorizes carol
902 mptAlice.authorize({.account = alice, .holder = carol});
903 checkAliceAcctTx(7, jss::MPTokenAuthorize);
904
905 // alice pays bob 100 tokens
906 mptAlice.pay(alice, bob, 100);
907 checkAliceAcctTx(8, jss::Payment);
908
909 // bob pays carol 10 tokens
910 mptAlice.pay(bob, carol, 10);
911 checkAliceAcctTx(9, jss::Payment);
912 }
913
914 void
916 {
917 testcase("Delegation Filtering");
918
919 using namespace test::jtx;
920
921 Env env(*this);
922 Account const alice{"alice"};
923 Account const bob{"bob"};
924 Account const carol{"carol"};
925
926 env.fund(XRP(10000), alice, bob, carol);
927 env.close();
928
929 // Normal TX: Alice pays Carol (Signed by Alice's Master Key)
930 env(pay(alice, carol, XRP(10)));
931 env.close();
932
933 // Setup Delegation: Alice allows Bob to sign Payments for her
934 env(delegate::set(alice, bob, {"Payment"}));
935 env.close();
936
937 // Delegated TX: Alice pays Bob (Signed by Bob using Delegation)
938 env(pay(alice, bob, XRP(20)), delegate::As(bob));
939 env.close();
940
941 // Normal TX: Bob pays Carol (Signed by Bob for himself)
942 env(pay(bob, carol, XRP(30)));
943 env.close();
944
945 auto const countTxs = [&](AccountID const& account,
946 json::Value const& delegateParams,
947 std::optional<std::uint32_t> const limit = std::nullopt) -> int {
948 int count = 0;
949 json::Value marker;
950 bool haveMarker = false;
951 int pages = 0;
952
953 while (true)
954 {
955 json::Value params;
956 params[jss::account] = toBase58(account);
957 params[jss::ledger_index_min] = -1;
958 params[jss::ledger_index_max] = -1;
959
960 if (!delegateParams.isNull())
961 params[jss::delegate] = delegateParams;
962 if (limit)
963 params[jss::limit] = *limit;
964 if (haveMarker)
965 params[jss::marker] = marker;
966
967 auto const res = env.rpc("json", "account_tx", to_string(params));
968 auto const& result = res[jss::result];
969
970 if (result.isMember(jss::transactions))
971 count += result[jss::transactions].size();
972
973 if (!limit || !result.isMember(jss::marker))
974 break;
975
976 marker = result[jss::marker];
977 haveMarker = true;
978 ++pages;
979 if (!BEAST_EXPECT(pages < 20))
980 break;
981 }
982
983 return count;
984 };
985
986 auto const checkError = [&](json::Value const& delegateParams,
987 std::string const& errToken) {
988 json::Value params;
989 params[jss::account] = alice.human();
990 params[jss::delegate] = delegateParams;
991 auto res = env.rpc("json", "account_tx", to_string(params));
992 BEAST_EXPECT(res[jss::result][jss::error] == errToken);
993 };
994
995 // Filter: Delegatee. Expects TX #2 (Signed by Bob)
996 {
997 json::Value p;
998 p[jss::delegate_filter] = "actor";
999 BEAST_EXPECT(countTxs(alice.id(), p) == 1);
1000 }
1001
1002 // Filter: Delegatee + Counterparty Bob. Expects TX #2.
1003 {
1004 json::Value p;
1005 p[jss::delegate_filter] = "actor";
1006 p[jss::counter_party] = bob.human();
1007 BEAST_EXPECT(countTxs(alice.id(), p) == 1);
1008 }
1009
1010 // Filter: Delegatee + Counterparty Carol. Expects 0.
1011 {
1012 json::Value p;
1013 p[jss::delegate_filter] = "actor";
1014 p[jss::counter_party] = carol.human();
1015 BEAST_EXPECT(countTxs(alice.id(), p) == 0);
1016 }
1017
1018 // Filter: Delegator. Expects TX #2.
1019 // (Bob signed it, but Alice is the owner).
1020 {
1021 json::Value p;
1022 p[jss::delegate_filter] = "authorizer";
1023 BEAST_EXPECT(countTxs(bob.id(), p) == 1);
1024 }
1025
1026 // Filter: Delegator + Counterparty Alice. Expects TX #2.
1027 {
1028 json::Value p;
1029 p[jss::delegate_filter] = "authorizer";
1030 p[jss::counter_party] = alice.human();
1031 BEAST_EXPECT(countTxs(bob.id(), p) == 1);
1032 }
1033
1034 // Filter: Authorizer. Expect: None.
1035 // TX #2 has sfDelegate present, but Alice is the delegator/owner, not
1036 // the delegate signer
1037 {
1038 json::Value p;
1039 p[jss::delegate_filter] = "authorizer";
1040 BEAST_EXPECT(countTxs(alice.id(), p) == 0);
1041 }
1042
1043 // Query Bob (Signer), Filter: Delegator, Counterparty: Carol
1044 // Expect: None (Alice is Owner, not Carol)
1045 {
1046 json::Value p;
1047 p[jss::delegate_filter] = "authorizer";
1048 p[jss::counter_party] = carol.human();
1049 BEAST_EXPECT(countTxs(bob.id(), p) == 0);
1050 }
1051
1052 // Query Bob (Signer), Filter: Delegatee
1053 // Expect: None. Bob did not employ a delegatee for his own TXs (TX C).
1054 {
1055 json::Value p;
1056 p[jss::delegate_filter] = "actor";
1057 BEAST_EXPECT(countTxs(bob.id(), p) == 0);
1058 }
1059
1060 // "delegate" is not an object (e.g., string)
1061 {
1062 json::Value const p = "not_an_object";
1063 checkError(p, "invalidParams");
1064 }
1065
1066 // Missing "delegate_filter" inside object
1067 {
1069 checkError(p, "invalidParams");
1070 }
1071
1072 // "delegate_filter" is not a string (e.g., int)
1073 {
1074 json::Value p;
1075 p[jss::delegate_filter] = 123;
1076 checkError(p, "invalidParams");
1077 }
1078
1079 // "delegate_filter" has invalid value
1080 {
1081 json::Value p;
1082 p[jss::delegate_filter] = "random_string";
1083 checkError(p, "invalidParams");
1084 }
1085
1086 // "counterparty" is not a string
1087 {
1088 json::Value p;
1089 p[jss::delegate_filter] = "actor";
1090 p[jss::counter_party] = 123;
1091 checkError(p, "invalidParams");
1092 }
1093
1094 // "counterparty" is malformed base58
1095 {
1096 json::Value p;
1097 p[jss::delegate_filter] = "actor";
1098 p[jss::counter_party] = "not_an_account";
1099 checkError(p, "actMalformed");
1100 }
1101
1102 // Multi-signed non-delegated TX: Alice pays Carol via multi-sig.
1103 // sfDelegate is absent and sfSigningPubKey is empty — the filter
1104 // must skip it without crashing.
1105 {
1106 Account const daria{"daria"};
1107 Account const edward{"edward"};
1108 env.fund(XRP(1000), daria, edward);
1109 env.close();
1110 env(signers(alice, 2, {{daria, 1}, {edward, 1}}));
1111 env.close();
1112 env(pay(alice, carol, XRP(1)),
1113 Fee(drops(env.current()->fees().increment * 2)),
1114 Msig(daria, edward));
1115 env.close();
1116
1117 // Alice's actor filter should still see only the 1 delegated tx,
1118 // not the multi-signed one.
1119 json::Value p;
1120 p[jss::delegate_filter] = "actor";
1121 BEAST_EXPECT(countTxs(alice.id(), p) == 1);
1122 }
1123
1124 // Regular-key-signed non-delegated TX: Alice pays Bob, signed by Bob
1125 // as Alice's regular key. This must not be treated as delegation.
1126 {
1127 env(regkey(alice, bob));
1128 env.close();
1129 env(pay(alice, bob, XRP(1)));
1130 env.close();
1131
1132 json::Value actorFilter;
1133 actorFilter[jss::delegate_filter] = "actor";
1134 BEAST_EXPECT(countTxs(alice.id(), actorFilter) == 1);
1135 // limit: 1 forces pagination past newer non-delegated rows.
1136 BEAST_EXPECT(countTxs(alice.id(), actorFilter, 1) == 1);
1137
1138 actorFilter[jss::counter_party] = bob.human();
1139 BEAST_EXPECT(countTxs(alice.id(), actorFilter) == 1);
1140 BEAST_EXPECT(countTxs(alice.id(), actorFilter, 1) == 1);
1141
1142 json::Value authorizerFilter;
1143 authorizerFilter[jss::delegate_filter] = "authorizer";
1144 BEAST_EXPECT(countTxs(bob.id(), authorizerFilter) == 1);
1145 BEAST_EXPECT(countTxs(bob.id(), authorizerFilter, 1) == 1);
1146
1147 authorizerFilter[jss::counter_party] = alice.human();
1148 BEAST_EXPECT(countTxs(bob.id(), authorizerFilter) == 1);
1149 BEAST_EXPECT(countTxs(bob.id(), authorizerFilter, 1) == 1);
1150 }
1151
1152 // Pagination marker/delegate-filter consistency. A marker returned by a
1153 // delegate-filtered query carries a `delegate` flag and is only valid
1154 // for a follow-up request that repeats the filter; mixing the two
1155 // marker conventions must be rejected with invalidParams.
1156 {
1157 json::Value actorFilter;
1158 actorFilter[jss::delegate_filter] = "actor";
1159
1160 // Obtain a delegate-filtered marker (limit 1 forces pagination).
1161 json::Value dp;
1162 dp[jss::account] = alice.human();
1163 dp[jss::ledger_index_min] = -1;
1164 dp[jss::ledger_index_max] = -1;
1165 dp[jss::delegate] = actorFilter;
1166 dp[jss::limit] = 1;
1167 auto const dRes = env.rpc("json", "account_tx", to_string(dp));
1168 BEAST_EXPECT(dRes[jss::result].isMember(jss::marker));
1169 json::Value const delegateMarker = dRes[jss::result][jss::marker];
1170 BEAST_EXPECT(
1171 delegateMarker.isMember(jss::delegate) && delegateMarker[jss::delegate].asBool());
1172
1173 // Reusing a delegate marker without the delegate filter is rejected.
1174 {
1175 json::Value p;
1176 p[jss::account] = alice.human();
1177 p[jss::ledger_index_min] = -1;
1178 p[jss::ledger_index_max] = -1;
1179 p[jss::limit] = 1;
1180 p[jss::marker] = delegateMarker;
1181 auto const r = env.rpc("json", "account_tx", to_string(p));
1182 BEAST_EXPECT(r[jss::result][jss::error] == "invalidParams");
1183 }
1184
1185 // Obtain a non-delegate marker; it must not carry the flag.
1186 json::Value np;
1187 np[jss::account] = alice.human();
1188 np[jss::ledger_index_min] = -1;
1189 np[jss::ledger_index_max] = -1;
1190 np[jss::limit] = 1;
1191 auto const nRes = env.rpc("json", "account_tx", to_string(np));
1192 BEAST_EXPECT(nRes[jss::result].isMember(jss::marker));
1193 json::Value const normalMarker = nRes[jss::result][jss::marker];
1194 BEAST_EXPECT(!normalMarker.isMember(jss::delegate));
1195
1196 // Reusing a non-delegate marker with a delegate filter is rejected.
1197 {
1198 json::Value p;
1199 p[jss::account] = alice.human();
1200 p[jss::ledger_index_min] = -1;
1201 p[jss::ledger_index_max] = -1;
1202 p[jss::limit] = 1;
1203 p[jss::delegate] = actorFilter;
1204 p[jss::marker] = normalMarker;
1205 auto const r = env.rpc("json", "account_tx", to_string(p));
1206 BEAST_EXPECT(r[jss::result][jss::error] == "invalidParams");
1207 }
1208 }
1209 }
1210
1211 void
1213 {
1214 testcase("Delegation filter with multi-signed delegatee");
1215 using namespace test::jtx;
1216
1217 Env env(*this);
1218 Account const alice{"alice"};
1219 Account const bob{"bob"};
1220 Account const carol{"carol"};
1221 Account const daria{"daria"};
1222 Account const edward{"edward"};
1223
1224 env.fund(XRP(10000), alice, bob, carol, daria, edward);
1225 env.close();
1226
1227 // Bob's identity is established via multi-sig (daria + edward)
1228 env(signers(bob, 2, {{daria, 1}, {edward, 1}}));
1229 env.close();
1230
1231 env(delegate::set(alice, bob, {"Payment"}));
1232 env.close();
1233
1234 // Delegated tx: Alice pays Carol, Bob signs via multi-sig
1235 env(pay(alice, carol, XRP(10)), Fee(XRP(1)), delegate::As(bob), Msig(daria, edward));
1236 env.close();
1237
1238 auto const countTxs = [&](AccountID const& account,
1239 json::Value const& delegateParams) -> int {
1240 json::Value params;
1241 params[jss::account] = toBase58(account);
1242 params[jss::ledger_index_min] = -1;
1243 params[jss::ledger_index_max] = -1;
1244 params[jss::delegate] = delegateParams;
1245
1246 auto const res = env.rpc("json", "account_tx", to_string(params));
1247
1248 if (res[jss::result].isMember(jss::transactions))
1249 return res[jss::result][jss::transactions].size();
1250 return 0;
1251 };
1252
1253 // Alice (owner) finds the tx with actor filter
1254 {
1255 json::Value p;
1256 p[jss::delegate_filter] = "actor";
1257 BEAST_EXPECT(countTxs(alice.id(), p) == 1);
1258 }
1259
1260 // Alice (owner) + counterparty Bob finds the tx
1261 {
1262 json::Value p;
1263 p[jss::delegate_filter] = "actor";
1264 p[jss::counter_party] = bob.human();
1265 BEAST_EXPECT(countTxs(alice.id(), p) == 1);
1266 }
1267
1268 // Bob (delegatee) finds the tx with authorizer filter
1269 {
1270 json::Value p;
1271 p[jss::delegate_filter] = "authorizer";
1272 BEAST_EXPECT(countTxs(bob.id(), p) == 1);
1273 }
1274
1275 // Bob (delegatee) + counterparty Alice finds the tx
1276 {
1277 json::Value p;
1278 p[jss::delegate_filter] = "authorizer";
1279 p[jss::counter_party] = alice.human();
1280 BEAST_EXPECT(countTxs(bob.id(), p) == 1);
1281 }
1282 }
1283
1284 void
1286 {
1287 testcase("Delegation filter marker within a single query page");
1288
1289 using namespace test::jtx;
1290
1291 Env env(*this);
1292 Account const alice{"alice"};
1293 Account const bob{"bob"};
1294 Account const carol{"carol"};
1295
1296 env.fund(XRP(10000), alice, bob, carol);
1297 env.close();
1298
1299 env(delegate::set(alice, bob, {"Payment"}));
1300 env.close();
1301
1302 auto const startLedger = env.closed()->header().seq + 1;
1303
1304 env(pay(alice, carol, XRP(1)), delegate::As(bob));
1305 env.close();
1306 env(pay(alice, carol, XRP(1)), delegate::As(bob));
1307 env.close();
1308
1309 json::Value p;
1310 p[jss::delegate_filter] = "actor";
1311
1312 json::Value params;
1313 params[jss::account] = alice.human();
1314 params[jss::ledger_index_min] = startLedger;
1315 params[jss::ledger_index_max] = -1;
1316 params[jss::delegate] = p;
1317 params[jss::limit] = 1;
1318
1319 auto const res = env.rpc("json", "account_tx", to_string(params));
1320 auto const& result = res[jss::result];
1321
1322 // The first page emits only the first delegated payment. (as page limit is set to 1)
1323 BEAST_EXPECT(result[jss::transactions].size() == 1);
1324 BEAST_EXPECT(result.isMember(jss::marker));
1325
1326 // Following the marker resumes right after the first payment and
1327 // returns the second one.
1328 json::Value page2 = params;
1329 page2[jss::marker] = result[jss::marker];
1330 auto const res2 = env.rpc("json", "account_tx", to_string(page2));
1331 BEAST_EXPECT(res2[jss::result][jss::transactions].size() == 1);
1332 }
1333
1334 void
1336 {
1337 // test all sponsored transactions are in sponsor and sponsee's account
1338 // tx list
1339 testcase("Sponsorship");
1340
1341 using namespace test::jtx;
1342 Env env(*this);
1343 Account const alice("alice");
1344 Account const sponsor("sponsor");
1345 Account const sponsor2("sponsor2");
1346 env.fund(XRP(10000), alice, sponsor, sponsor2);
1347 env.close();
1348
1349 // check the latest sponsorship-related txn is in account tx list
1350 auto const checkTx = [&](Account const& account, json::StaticString txType) {
1351 json::Value params;
1352 params[jss::account] = account.human();
1353 params[jss::limit] = 100;
1354 auto const jv = env.rpc("json", "account_tx", to_string(params))[jss::result];
1355
1356 auto const& tx0(jv[jss::transactions][0u][jss::tx]);
1357 BEAST_EXPECT(tx0[jss::TransactionType] == txType);
1358
1359 std::string const txHash{
1360 env.tx()->getJson(JsonOptions::Values::None)[jss::hash].asString()};
1361 BEAST_EXPECT(tx0[jss::hash] == txHash);
1362 };
1363
1364 // fee sponsorship
1365 env(noop(alice), sponsor::As(sponsor, spfSponsorFee), Sig(sfSponsorSignature, sponsor));
1366 env.close();
1367 checkTx(alice, jss::AccountSet);
1368 checkTx(sponsor, jss::AccountSet);
1369
1370 // set sponsor
1371 env(sponsor::set(sponsor, 0, 100, XRP(100)), sponsor::SponseeAcc(alice), Ter(tesSUCCESS));
1372 env.close();
1373 checkTx(alice, jss::SponsorshipSet);
1374 checkTx(sponsor, jss::SponsorshipSet);
1375
1376 // create an object with sponsor
1377 auto const checkId = keylet::check(alice, SeqProxy::rawSequence(env.seq(alice))).key;
1379 env.close();
1380 checkTx(alice, jss::CheckCreate);
1381 checkTx(sponsor, jss::CheckCreate);
1382
1383 // transfer object sponsorship
1384 env(sponsor::transfer(alice, tfSponsorshipReassign, checkId),
1385 sponsor::As(sponsor2, spfSponsorReserve),
1386 Sig(sfSponsorSignature, sponsor2));
1387 env.close();
1388 checkTx(alice, jss::SponsorshipTransfer);
1389 checkTx(sponsor, jss::SponsorshipTransfer);
1390 checkTx(sponsor2, jss::SponsorshipTransfer);
1391
1392 // delete the sponsored object
1393 env(check::cancel(alice, checkId),
1395 Sig(sfSponsorSignature, sponsor));
1396 env.close();
1397 checkTx(alice, jss::CheckCancel);
1398 checkTx(sponsor, jss::CheckCancel);
1399 checkTx(sponsor2, jss::CheckCancel);
1400
1401 // account sponsorship
1402 env(sponsor::transfer(alice, tfSponsorshipCreate),
1404 Sig(sfSponsorSignature, sponsor));
1405 env.close();
1406 checkTx(alice, jss::SponsorshipTransfer);
1407 checkTx(sponsor, jss::SponsorshipTransfer);
1408 }
1409
1410public:
1411 void
1412 run() override
1413 {
1414 forAllApiVersions([this](unsigned apiVersion) { testParameters(apiVersion); });
1415 testContents();
1417 testMPT();
1422 }
1423};
1425
1426} // 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:1058
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:512
void create(MPTCreate const &arg=MPTCreate{}, std::source_location const &loc=std::source_location::current())
Definition mpt.cpp:343
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:424
Keylet check(AccountID const &id, SeqProxy const &seq) noexcept
A Check.
Definition Indexes.cpp:360
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:220
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 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 create(A const &account, A const &dest, STAmount const &sendMax)
Create a check.
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:38
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:465
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:657
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:299
constexpr FlagValue spfSponsorReserve
Definition TxFlags.h:466
@ tesSUCCESS
Definition TER.h:250
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)