xrpld
Loading...
Searching...
No Matches
LedgerRPC_test.cpp
1
2#include <test/jtx/Account.h>
3#include <test/jtx/Env.h>
4#include <test/jtx/amount.h>
5#include <test/jtx/envconfig.h>
6#include <test/jtx/fee.h>
7#include <test/jtx/last_ledger_sequence.h>
8#include <test/jtx/mpt.h>
9#include <test/jtx/noop.h>
10#include <test/jtx/offer.h>
11#include <test/jtx/pay.h>
12#include <test/jtx/seq.h>
13#include <test/jtx/ter.h>
14
15#include <xrpld/app/misc/TxQ.h>
16#include <xrpld/rpc/CTID.h>
17
18#include <xrpl/basics/base_uint.h>
19#include <xrpl/beast/unit_test/suite.h>
20#include <xrpl/config/Constants.h>
21#include <xrpl/core/NetworkIDService.h>
22#include <xrpl/json/json_value.h>
23#include <xrpl/json/to_string.h>
24#include <xrpl/protocol/ErrorCodes.h>
25#include <xrpl/protocol/SField.h>
26#include <xrpl/protocol/TER.h>
27#include <xrpl/protocol/TxFlags.h>
28#include <xrpl/protocol/jss.h>
29
30#include <cstdint>
31#include <memory>
32#include <string>
33#include <utility>
34
35namespace xrpl::test {
36
38{
39 void
40 checkErrorValue(json::Value const& jv, std::string const& err, std::string const& msg)
41 {
42 if (BEAST_EXPECT(jv.isMember(jss::status)))
43 BEAST_EXPECT(jv[jss::status] == "error");
44 if (BEAST_EXPECT(jv.isMember(jss::error)))
45 BEAST_EXPECT(jv[jss::error] == err);
46 if (msg.empty())
47 {
48 BEAST_EXPECT(
49 jv[jss::error_message] == json::ValueType::Null || jv[jss::error_message] == "");
50 }
51 else if (BEAST_EXPECT(jv.isMember(jss::error_message)))
52 {
53 BEAST_EXPECTS(
54 jv[jss::error_message] == msg,
55 "Expected error message \"" + msg + "\", received \"" +
56 jv[jss::error_message].asString() + "\"");
57 }
58 }
59
60 // Corrupt a valid address by replacing the 10th character with '!'.
61 // '!' is not part of the ripple alphabet.
62 static std::string
64 {
65 std::string ret = std::move(good);
66 ret.replace(10, 1, 1, '!');
67 return ret;
68 }
69
70 void
72 {
73 testcase("Basic Request");
74 using namespace test::jtx;
75
76 Env env{*this};
77
78 env.close();
79 BEAST_EXPECT(env.current()->header().seq == 4);
80
81 {
82 json::Value jvParams;
83 // can be either numeric or quoted numeric
84 jvParams[jss::ledger_index] = 1;
85 auto const jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
86 BEAST_EXPECT(jrr[jss::ledger][jss::closed] == true);
87 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "1");
88 }
89
90 {
91 json::Value jvParams;
92 jvParams[jss::ledger_index] = "1";
93 auto const jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
94 BEAST_EXPECT(jrr[jss::ledger][jss::closed] == true);
95 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "1");
96 }
97
98 {
99 // using current identifier
100 auto const jrr = env.rpc("ledger", "current")[jss::result];
101 BEAST_EXPECT(jrr[jss::ledger][jss::closed] == false);
102 BEAST_EXPECT(
103 jrr[jss::ledger][jss::ledger_index] == std::to_string(env.current()->header().seq));
104 BEAST_EXPECT(jrr[jss::ledger_current_index] == env.current()->header().seq);
105 }
106
107 {
108 auto const jrr = env.rpc("json", "ledger", "{}")[jss::result];
109 BEAST_EXPECT(!jrr.isMember(jss::ledger));
110 BEAST_EXPECT(jrr[jss::closed][jss::ledger][jss::closed] == true);
111 BEAST_EXPECT(
112 jrr[jss::closed][jss::ledger][jss::ledger_index] ==
113 std::to_string(env.closed()->header().seq));
114 BEAST_EXPECT(jrr[jss::open][jss::ledger][jss::closed] == false);
115 BEAST_EXPECT(
116 jrr[jss::open][jss::ledger][jss::ledger_index] ==
117 std::to_string(env.current()->header().seq));
118 }
119 }
120
121 void
123 {
124 testcase("Bad Input");
125 using namespace test::jtx;
126 Env env{*this};
127 Account const gw{"gateway"};
128 auto const usd = gw["USD"];
129 Account const bob{"bob"};
130
131 env.fund(XRP(10000), gw, bob);
132 env.close();
133 env.trust(usd(1000), bob);
134 env.close();
135
136 {
137 // ask for an arbitrary string - index
138 json::Value jvParams;
139 jvParams[jss::ledger_index] = "potato";
140 auto const jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
142 jrr, "invalidParams", "Invalid field 'ledger_index', not string or number.");
143 }
144
145 {
146 // ask for a negative index
147 json::Value jvParams;
148 jvParams[jss::ledger_index] = -1;
149 auto const jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
151 jrr, "invalidParams", "Invalid field 'ledger_index', not string or number.");
152 }
153
154 {
155 // ask for a bad ledger index
156 json::Value jvParams;
157 jvParams[jss::ledger_index] = 10u;
158 auto const jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
159 checkErrorValue(jrr, "lgrNotFound", "ledgerNotFound");
160 }
161
162 {
163 // unrecognized string arg -- error
164 auto const jrr = env.rpc("ledger", "arbitrary_text")[jss::result];
165 checkErrorValue(jrr, "lgrNotFound", "ledgerNotFound");
166 }
167
168 {
169 // Request queue for closed ledger
170 json::Value jvParams;
171 jvParams[jss::ledger_index] = "validated";
172 jvParams[jss::queue] = true;
173 auto const jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
174 checkErrorValue(jrr, "invalidParams", "Invalid parameters.");
175 }
176
177 {
178 // Request a ledger with a very large (double) sequence.
179 auto const ret = env.rpc("json", "ledger", "{ \"ledger_index\" : 2e15 }");
180 BEAST_EXPECT(rpc::containsError(ret));
181 BEAST_EXPECT(ret[jss::error_message] == "Invalid parameters.");
182 }
183
184 {
185 // Request a ledger with very large (integer) sequence.
186 auto const ret = env.rpc("json", "ledger", "{ \"ledger_index\" : 1000000000000000 }");
187 checkErrorValue(ret, "invalidParams", "Invalid parameters.");
188 }
189
190 {
191 // test all boolean fields with non-boolean values
192 auto testBooleanField = [&](json::StaticString const& field) {
193 json::Value jvParams;
194 jvParams[field] = "blah";
195 auto const jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
196 checkErrorValue(jrr, "invalidParams", "Invalid parameters.");
197 };
198 testBooleanField(jss::full);
199 testBooleanField(jss::accounts);
200 testBooleanField(jss::transactions);
201 testBooleanField(jss::expand);
202 testBooleanField(jss::binary);
203 testBooleanField(jss::owner_funds);
204 testBooleanField(jss::queue);
205 }
206 }
207
208 void
210 {
211 testcase("ledger_current Request");
212 using namespace test::jtx;
213
214 Env env{*this};
215
216 env.close();
217 BEAST_EXPECT(env.current()->header().seq == 4);
218
219 {
220 auto const jrr = env.rpc("ledger_current")[jss::result];
221 BEAST_EXPECT(jrr[jss::ledger_current_index] == env.current()->header().seq);
222 }
223 }
224
225 void
227 {
228 testcase("Ledger Request, Full Option");
229 using namespace test::jtx;
230
231 Env env{*this};
232
233 env.close();
234
235 json::Value jvParams;
236 jvParams[jss::ledger_index] = 3u;
237 jvParams[jss::full] = true;
238 auto const jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
239 BEAST_EXPECT(jrr[jss::ledger].isMember(jss::accountState));
240 BEAST_EXPECT(jrr[jss::ledger][jss::accountState].isArray());
241 BEAST_EXPECT(jrr[jss::ledger][jss::accountState].size() == 3u);
242 }
243
244 void
246 {
247 testcase("Ledger Request, Full Option Without Admin");
248 using namespace test::jtx;
249
250 Env env{*this, envconfig(noAdmin)};
251
252 // env.close();
253
254 json::Value jvParams;
255 jvParams[jss::ledger_index] = 1u;
256 jvParams[jss::full] = true;
257 auto const jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
258 checkErrorValue(jrr, "noPermission", "You don't have permission for this command.");
259 }
260
261 void
263 {
264 testcase("Ledger Request, Accounts Option");
265 using namespace test::jtx;
266
267 Env env{*this};
268
269 env.close();
270
271 json::Value jvParams;
272 jvParams[jss::ledger_index] = 3u;
273 jvParams[jss::accounts] = true;
274 auto const jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
275 BEAST_EXPECT(jrr[jss::ledger].isMember(jss::accountState));
276 BEAST_EXPECT(jrr[jss::ledger][jss::accountState].isArray());
277 BEAST_EXPECT(jrr[jss::ledger][jss::accountState].size() == 3u);
278 }
279
280 void
282 {
283 testcase("Ledger owner_funds with MPT offer");
284 using namespace test::jtx;
285
286 Env env{*this};
287 Account const gw{"gateway"};
288 Account const alice{"alice"};
289 auto const usd = gw["USD"];
290
291 env.fund(XRP(10'000), gw, alice);
292 env.close();
293 env.trust(usd(1'000), alice);
294 env(pay(gw, alice, usd(100)));
295 MPTTester mpt(
296 {.env = env,
297 .issuer = gw,
298 .holders = {alice},
299 .pay = 100,
300 .flags = tfMPTRequireAuth | kMptDexFlags,
301 .authHolder = true,
302 .close = false});
303 MPT const mptAsset = mpt;
304 env.close();
305
306 env(noop(alice));
307 // These offers differ only by TakerGets asset type. Omitting
308 // owner_funds serializes the tx JSON without computing offer balances;
309 // owner_funds=true asks LedgerToJson to compute accountFunds(TakerGets)
310 // for both offers, which is where IOU and MPT used to diverge.
311 env(offer(alice, XRP(10), usd(10)));
312 env(offer(alice, XRP(10), mptAsset(10)));
313 // The MPT offer was created while authorized. Unauthorizing in the
314 // same ledger makes owner_funds depend on AuthHandling::IgnoreAuth.
315 mpt.authorize({.account = gw, .holder = alice, .flags = tfMPTUnauthorize});
316 env(noop(alice));
317 env.close();
318
319 auto const ledgerHash = to_string(env.closed()->header().hash);
320
321 auto const getTransactions = [&](bool includeOwnerFunds) {
322 json::Value params;
323 params[jss::ledger_hash] = ledgerHash;
324 params[jss::transactions] = true;
325 params[jss::expand] = true;
326 // The baseline omits owner_funds, which the RPC treats as false.
327 // Setting it true requests the same ledger, but asks the ledger
328 // serializer to add owner_funds to offer transactions in that
329 // ledger's transaction array.
330 if (includeOwnerFunds)
331 params[jss::owner_funds] = true;
332
333 auto const result = env.rpc("json", "ledger", to_string(params))[jss::result];
334 BEAST_EXPECT(!result.isMember(jss::error));
335 BEAST_EXPECT(result[jss::ledger][jss::transactions].isArray());
336 return result[jss::ledger][jss::transactions];
337 };
338
339 auto const findOffer = [](json::Value const& txs, bool mpt) -> json::Value const* {
340 for (auto i = 0u; i < txs.size(); ++i)
341 {
342 auto const& tx = txs[i].isMember(jss::tx_json) ? txs[i][jss::tx_json] : txs[i];
343 if (tx[jss::TransactionType] == jss::OfferCreate &&
344 tx[jss::TakerGets].isMember(jss::mpt_issuance_id) == mpt)
345 {
346 return &txs[i];
347 }
348 }
349 return nullptr;
350 };
351
352 // Baseline: same ledger request without owner_funds fields.
353 auto const baseline = getTransactions(false);
354 BEAST_EXPECT(baseline.size() == 5u);
355 BEAST_EXPECT(findOffer(baseline, false) != nullptr);
356 BEAST_EXPECT(findOffer(baseline, true) != nullptr);
357
358 // Same ledger request with owner_funds added to eligible offer txs.
359 auto const withOwnerFunds = getTransactions(true);
360 // Requesting owner_funds must not change which ledger transactions are
361 // returned, even when one offer's TakerGets is MPT.
362 BEAST_EXPECT(withOwnerFunds.size() == baseline.size());
363
364 // The IOU offer is the control case for expected owner_funds output.
365 auto const* iouOfferTx = findOffer(withOwnerFunds, false);
366 if (BEAST_EXPECT(iouOfferTx != nullptr))
367 BEAST_EXPECT((*iouOfferTx)[jss::owner_funds] == "100");
368
369 // MPT owner_funds should match the IOU behavior, even though Alice is
370 // unauthorized by the ledger snapshot used for serialization.
371 auto const* mptOfferTx = findOffer(withOwnerFunds, true);
372 if (BEAST_EXPECT(mptOfferTx != nullptr))
373 BEAST_EXPECT((*mptOfferTx)[jss::owner_funds] == "100");
374 }
375
382 void
384 {
385 testcase("Lookup ledger");
386 using namespace test::jtx;
387
388 auto cfg = envconfig();
389 cfg->fees.referenceFee = 10;
390 Env env{*this, std::move(cfg), FeatureBitset{}}; // hashes requested below
391 // assume no amendments
392 env.fund(XRP(10000), "alice");
393 env.close();
394 env.fund(XRP(10000), "bob");
395 env.close();
396 env.fund(XRP(10000), "jim");
397 env.close();
398 env.fund(XRP(10000), "jill");
399
400 {
401 // access via the legacy ledger field, keyword index values
402 json::Value jvParams;
403 jvParams[jss::ledger] = "closed";
404 auto jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
405 BEAST_EXPECT(jrr.isMember(jss::ledger));
406 BEAST_EXPECT(jrr.isMember(jss::ledger_hash));
407 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "5");
408
409 jvParams[jss::ledger] = "validated";
410 jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
411 BEAST_EXPECT(jrr.isMember(jss::ledger));
412 BEAST_EXPECT(jrr.isMember(jss::ledger_hash));
413 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "5");
414
415 jvParams[jss::ledger] = "current";
416 jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
417 BEAST_EXPECT(jrr.isMember(jss::ledger));
418 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "6");
419
420 // ask for a bad ledger keyword
421 jvParams[jss::ledger] = "invalid";
422 jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
423 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
424 BEAST_EXPECT(
425 jrr[jss::error_message] == "Invalid field 'ledger', not string or number.");
426
427 // numeric index
428 jvParams[jss::ledger] = 4;
429 jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
430 BEAST_EXPECT(jrr.isMember(jss::ledger));
431 BEAST_EXPECT(jrr.isMember(jss::ledger_hash));
432 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "4");
433
434 // numeric index - out of range
435 jvParams[jss::ledger] = 20;
436 jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
437 BEAST_EXPECT(jrr[jss::error] == "lgrNotFound");
438 BEAST_EXPECT(jrr[jss::error_message] == "ledgerNotFound");
439 }
440
441 {
442 std::string const hash3{
443 "0F1A9E0C109ADEF6DA2BDE19217C12BBEC57174CBDBD212B0EBDC1CEDB8531"
444 "85"};
445 // access via the ledger_hash field
446 json::Value jvParams;
447 jvParams[jss::ledger_hash] = hash3;
448 auto jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
449 BEAST_EXPECT(jrr.isMember(jss::ledger));
450 BEAST_EXPECT(jrr.isMember(jss::ledger_hash));
451 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "3");
452
453 // extra leading hex chars in hash are not allowed
454 jvParams[jss::ledger_hash] = "DEADBEEF" + hash3;
455 jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
456 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
457 BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'ledger_hash', not hex string.");
458
459 // request with non-string ledger_hash
460 jvParams[jss::ledger_hash] = 2;
461 jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
462 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
463 BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'ledger_hash', not hex string.");
464
465 // malformed (non hex) hash
466 jvParams[jss::ledger_hash] =
467 "2E81FC6EC0DD943197EGC7E3FBE9AE30"
468 "7F2775F2F7485BB37307984C3C0F2340";
469 jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
470 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
471 BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'ledger_hash', not hex string.");
472
473 // properly formed, but just doesn't exist
474 jvParams[jss::ledger_hash] =
475 "8C3EEDB3124D92E49E75D81A8826A2E6"
476 "5A75FD71FC3FD6F36FEB803C5F1D812D";
477 jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
478 BEAST_EXPECT(jrr[jss::error] == "lgrNotFound");
479 BEAST_EXPECT(jrr[jss::error_message] == "ledgerNotFound");
480 }
481
482 {
483 // access via the ledger_index field, keyword index values
484 json::Value jvParams;
485 jvParams[jss::ledger_index] = "closed";
486 auto jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
487 BEAST_EXPECT(jrr.isMember(jss::ledger));
488 BEAST_EXPECT(jrr.isMember(jss::ledger_hash));
489 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "5");
490 BEAST_EXPECT(jrr.isMember(jss::ledger_index));
491
492 jvParams[jss::ledger_index] = "validated";
493 jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
494 BEAST_EXPECT(jrr.isMember(jss::ledger));
495 BEAST_EXPECT(jrr.isMember(jss::ledger_hash));
496 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "5");
497
498 jvParams[jss::ledger_index] = "current";
499 jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
500 BEAST_EXPECT(jrr.isMember(jss::ledger));
501 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == "6");
502 BEAST_EXPECT(jrr.isMember(jss::ledger_current_index));
503
504 // ask for a bad ledger keyword
505 jvParams[jss::ledger_index] = "invalid";
506 jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
507 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
508 BEAST_EXPECT(
509 jrr[jss::error_message] == "Invalid field 'ledger_index', not string or number.");
510
511 // numeric index
512 for (auto i : {1, 2, 3, 4, 5, 6})
513 {
514 jvParams[jss::ledger_index] = i;
515 jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
516 BEAST_EXPECT(jrr.isMember(jss::ledger));
517 if (i < 6)
518 BEAST_EXPECT(jrr.isMember(jss::ledger_hash));
519 BEAST_EXPECT(jrr[jss::ledger][jss::ledger_index] == std::to_string(i));
520 }
521
522 // numeric index - out of range
523 jvParams[jss::ledger_index] = 7;
524 jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
525 BEAST_EXPECT(jrr[jss::error] == "lgrNotFound");
526 BEAST_EXPECT(jrr[jss::error_message] == "ledgerNotFound");
527 }
528 }
529
530 void
532 {
533 testcase("Ledger with queueing disabled");
534 using namespace test::jtx;
535 Env env{*this};
536
537 json::Value jv;
538 jv[jss::ledger_index] = "current";
539 jv[jss::queue] = true;
540 jv[jss::expand] = true;
541
542 auto jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
543 BEAST_EXPECT(!jrr.isMember(jss::queue_data));
544 }
545
546 void
548 {
549 testcase("Ledger with Queued Transactions");
550 using namespace test::jtx;
551 auto cfg = envconfig([](std::unique_ptr<Config> cfg) {
552 auto& section = cfg->section(Sections::kTransactionQueue);
553 section.set(Keys::kMinimumTxnInLedgerStandalone, "3");
555 return cfg;
556 });
557
558 cfg->fees.referenceFee = 10;
559 Env env(*this, std::move(cfg));
560
561 json::Value jv;
562 jv[jss::ledger_index] = "current";
563 jv[jss::queue] = true;
564 jv[jss::expand] = true;
565
566 Account const alice{"alice"};
567 Account const bob{"bob"};
568 Account const charlie{"charlie"};
569 Account const daria{"daria"};
570 env.fund(XRP(10000), alice);
571 env.fund(XRP(10000), bob);
572 env.close();
573 env.fund(XRP(10000), charlie);
574 env.fund(XRP(10000), daria);
575 env.close();
576
577 auto jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
578 BEAST_EXPECT(!jrr.isMember(jss::queue_data));
579
580 // Fill the open ledger
581 for (;;)
582 {
583 auto metrics = env.app().getTxQ().getMetrics(*env.current());
584 if (metrics.openLedgerFeeLevel > metrics.minProcessingFeeLevel)
585 break;
586 env(noop(alice));
587 }
588
589 BEAST_EXPECT(env.current()->header().seq == 5);
590 // Put some txs in the queue
591 // Alice
592 auto aliceSeq = env.seq(alice);
593 env(pay(alice, "george", XRP(1000)), LastLedgerSeq(7), Ter(terQUEUED));
594 env(offer(alice, XRP(50000), alice["USD"](5000)), Seq(aliceSeq + 1), Ter(terQUEUED));
595 env(noop(alice), Seq(aliceSeq + 2), Ter(terQUEUED));
596 // Bob
597 auto batch = [&env](Account a) {
598 auto aSeq = env.seq(a);
599 // Enough fee to get in front of alice in the queue
600 for (int i = 0; i < 10; ++i)
601 {
602 env(noop(a), Fee(1000 + i), Seq(aSeq + i), Ter(terQUEUED));
603 }
604 };
605 batch(bob);
606 // Charlie
607 batch(charlie);
608 // Daria
609 batch(daria);
610
611 jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
612 BEAST_EXPECT(jrr[jss::queue_data].size() == 33);
613
614 // Close enough ledgers so that alice's first tx expires.
615 env.close();
616 env.close();
617 env.close();
618 BEAST_EXPECT(env.current()->header().seq == 8);
619
620 jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
621 BEAST_EXPECT(jrr[jss::queue_data].size() == 11);
622
623 env.close();
624
625 jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
626 std::string const txid0 = [&]() {
627 auto const& parentHash = env.current()->header().parentHash;
628 if (BEAST_EXPECT(jrr[jss::queue_data].size() == 2))
629 {
630 std::string const txid1 = [&]() {
631 auto const& txj = jrr[jss::queue_data][1u];
632 BEAST_EXPECT(txj[jss::account] == alice.human());
633 BEAST_EXPECT(txj[jss::fee_level] == "256");
634 BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
635 BEAST_EXPECT(txj["retries_remaining"] == 10);
636 BEAST_EXPECT(txj.isMember(jss::tx));
637 auto const& tx = txj[jss::tx];
638 BEAST_EXPECT(tx[jss::Account] == alice.human());
639 BEAST_EXPECT(tx[jss::TransactionType] == jss::AccountSet);
640 return tx[jss::hash].asString();
641 }();
642
643 auto const& txj = jrr[jss::queue_data][0u];
644 BEAST_EXPECT(txj[jss::account] == alice.human());
645 BEAST_EXPECT(txj[jss::fee_level] == "256");
646 BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
647 BEAST_EXPECT(txj["retries_remaining"] == 10);
648 BEAST_EXPECT(txj.isMember(jss::tx));
649 auto const& tx = txj[jss::tx];
650 BEAST_EXPECT(tx[jss::Account] == alice.human());
651 BEAST_EXPECT(tx[jss::TransactionType] == jss::OfferCreate);
652 auto const txid0 = tx[jss::hash].asString();
653 UInt256 tx0, tx1;
654 BEAST_EXPECT(tx0.parseHex(txid0));
655 BEAST_EXPECT(tx1.parseHex(txid1));
656 BEAST_EXPECT((tx0 ^ parentHash) < (tx1 ^ parentHash));
657 return txid0;
658 }
659 return std::string{};
660 }();
661
662 env.close();
663
664 jv[jss::expand] = false;
665
666 jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
667 if (BEAST_EXPECT(jrr[jss::queue_data].size() == 2))
668 {
669 auto const& parentHash = env.current()->header().parentHash;
670 auto const txid1 = [&]() {
671 auto const& txj = jrr[jss::queue_data][1u];
672 BEAST_EXPECT(txj[jss::account] == alice.human());
673 BEAST_EXPECT(txj[jss::fee_level] == "256");
674 BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
675 BEAST_EXPECT(txj.isMember(jss::tx));
676 return txj[jss::tx].asString();
677 }();
678 auto const& txj = jrr[jss::queue_data][0u];
679 BEAST_EXPECT(txj[jss::account] == alice.human());
680 BEAST_EXPECT(txj[jss::fee_level] == "256");
681 BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
682 BEAST_EXPECT(txj["retries_remaining"] == 9);
683 BEAST_EXPECT(txj["last_result"] == "terPRE_SEQ");
684 BEAST_EXPECT(txj.isMember(jss::tx));
685 BEAST_EXPECT(txj[jss::tx] == txid0);
686 UInt256 tx0, tx1;
687 BEAST_EXPECT(tx0.parseHex(txid0));
688 BEAST_EXPECT(tx1.parseHex(txid1));
689 BEAST_EXPECT((tx0 ^ parentHash) < (tx1 ^ parentHash));
690 }
691
692 env.close();
693
694 jv[jss::expand] = true;
695 jv[jss::binary] = true;
696
697 jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
698 if (BEAST_EXPECT(jrr[jss::queue_data].size() == 2))
699 {
700 auto const& txj = jrr[jss::queue_data][1u];
701 BEAST_EXPECT(txj[jss::account] == alice.human());
702 BEAST_EXPECT(txj[jss::fee_level] == "256");
703 BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
704 BEAST_EXPECT(txj["retries_remaining"] == 8);
705 BEAST_EXPECT(txj["last_result"] == "terPRE_SEQ");
706 BEAST_EXPECT(txj.isMember(jss::tx));
707 BEAST_EXPECT(txj[jss::tx].isMember(jss::tx_blob));
708
709 auto const& txj2 = jrr[jss::queue_data][0u];
710 BEAST_EXPECT(txj2[jss::account] == alice.human());
711 BEAST_EXPECT(txj2[jss::fee_level] == "256");
712 BEAST_EXPECT(txj2["preflight_result"] == "tesSUCCESS");
713 BEAST_EXPECT(txj2["retries_remaining"] == 10);
714 BEAST_EXPECT(!txj2.isMember("last_result"));
715 BEAST_EXPECT(txj2.isMember(jss::tx));
716 BEAST_EXPECT(txj2[jss::tx].isMember(jss::tx_blob));
717 }
718
719 for (int i = 0; i != 9; ++i)
720 {
721 env.close();
722 }
723
724 jv[jss::expand] = false;
725 jv[jss::binary] = false;
726
727 jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
728 std::string const txid2 = [&]() {
729 if (BEAST_EXPECT(jrr[jss::queue_data].size() == 1))
730 {
731 auto const& txj = jrr[jss::queue_data][0u];
732 BEAST_EXPECT(txj[jss::account] == alice.human());
733 BEAST_EXPECT(txj[jss::fee_level] == "256");
734 BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
735 BEAST_EXPECT(txj["retries_remaining"] == 1);
736 BEAST_EXPECT(txj["last_result"] == "terPRE_SEQ");
737 BEAST_EXPECT(txj.isMember(jss::tx));
738 BEAST_EXPECT(txj[jss::tx] != txid0);
739 return txj[jss::tx].asString();
740 }
741 return std::string{};
742 }();
743
744 jv[jss::full] = true;
745
746 jrr = env.rpc("json", "ledger", to_string(jv))[jss::result];
747 if (BEAST_EXPECT(jrr[jss::queue_data].size() == 1))
748 {
749 auto const& txj = jrr[jss::queue_data][0u];
750 BEAST_EXPECT(txj[jss::account] == alice.human());
751 BEAST_EXPECT(txj[jss::fee_level] == "256");
752 BEAST_EXPECT(txj["preflight_result"] == "tesSUCCESS");
753 BEAST_EXPECT(txj["retries_remaining"] == 1);
754 BEAST_EXPECT(txj["last_result"] == "terPRE_SEQ");
755 BEAST_EXPECT(txj.isMember(jss::tx));
756 auto const& tx = txj[jss::tx];
757 BEAST_EXPECT(tx[jss::Account] == alice.human());
758 BEAST_EXPECT(tx[jss::TransactionType] == jss::AccountSet);
759 BEAST_EXPECT(tx[jss::hash] == txid2);
760 }
761 }
762
763 void
765 {
766 testcase("Ledger Request, Accounts Hashes");
767 using namespace test::jtx;
768
769 Env env{*this};
770
771 env.close();
772
773 std::string index;
774 int hashesLedgerEntryIndex = -1;
775 {
776 json::Value jvParams;
777 jvParams[jss::ledger_index] = 3u;
778 jvParams[jss::accounts] = true;
779 jvParams[jss::expand] = true;
780 jvParams[jss::type] = "hashes";
781 auto const jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
782 BEAST_EXPECT(jrr[jss::ledger].isMember(jss::accountState));
783 BEAST_EXPECT(jrr[jss::ledger][jss::accountState].isArray());
784
785 for (auto i = 0; i < jrr[jss::ledger][jss::accountState].size(); i++)
786 {
787 if (jrr[jss::ledger][jss::accountState][i]["LedgerEntryType"] == jss::LedgerHashes)
788 {
789 index = jrr[jss::ledger][jss::accountState][i]["index"].asString();
790 hashesLedgerEntryIndex = i;
791 }
792 }
793
794 for (auto const& object : jrr[jss::ledger][jss::accountState])
795 {
796 if (object["LedgerEntryType"] == jss::LedgerHashes)
797 index = object["index"].asString();
798 }
799
800 // jss::type is a deprecated field
801 BEAST_EXPECT(
802 jrr.isMember(jss::warnings) && jrr[jss::warnings].isArray() &&
803 jrr[jss::warnings].size() == 1 &&
804 jrr[jss::warnings][0u][jss::id].asInt() == WarnRpcFieldsDeprecated);
805 }
806 {
807 json::Value jvParams;
808 jvParams[jss::ledger_index] = 3u;
809 jvParams[jss::accounts] = true;
810 jvParams[jss::expand] = false;
811 jvParams[jss::type] = "hashes";
812 auto const jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
813 BEAST_EXPECT(jrr[jss::ledger].isMember(jss::accountState));
814 BEAST_EXPECT(jrr[jss::ledger][jss::accountState].isArray());
815 BEAST_EXPECT(
816 hashesLedgerEntryIndex > 0 &&
817 jrr[jss::ledger][jss::accountState][hashesLedgerEntryIndex] == index);
818
819 // jss::type is a deprecated field
820 BEAST_EXPECT(
821 jrr.isMember(jss::warnings) && jrr[jss::warnings].isArray() &&
822 jrr[jss::warnings].size() == 1 &&
823 jrr[jss::warnings][0u][jss::id].asInt() == WarnRpcFieldsDeprecated);
824 }
825 }
826
827 void
829 {
830 testcase("Expanded Transactions CTID");
831 using namespace test::jtx;
832
833 Env env{*this};
834 Account const alice{"alice"};
835 env.fund(XRP(10000), alice);
836 env.close();
837
838 uint32_t const netID = env.app().getNetworkIDService().getNetworkID();
839
840 // API v2 non-binary: CTID present
841 {
842 json::Value jvParams;
843 jvParams[jss::ledger_index] = "validated";
844 jvParams[jss::transactions] = true;
845 jvParams[jss::expand] = true;
846 jvParams[jss::api_version] = 2;
847 auto const jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
848 BEAST_EXPECT(jrr[jss::status] == "success");
849 auto const& txns = jrr[jss::ledger][jss::transactions];
850 BEAST_EXPECT(txns.isArray() && txns.size() > 0);
851 for (auto const& txn : txns)
852 {
853 BEAST_EXPECT(txn.isMember(jss::ctid));
854 auto const expectedCtid = rpc::encodeCTID(
855 jrr[jss::ledger][jss::ledger_index].asUInt(),
856 txn[jss::meta][sfTransactionIndex.jsonName].asUInt(),
857 netID);
858 // NOLINTBEGIN(bugprone-unchecked-optional-access)
859 if (BEAST_EXPECT(expectedCtid.has_value()))
860 BEAST_EXPECT(txn[jss::ctid] == expectedCtid.value());
861 // NOLINTEND(bugprone-unchecked-optional-access)
862 }
863 }
864
865 // API v1 non-binary: CTID present
866 {
867 json::Value jvParams;
868 jvParams[jss::ledger_index] = "validated";
869 jvParams[jss::transactions] = true;
870 jvParams[jss::expand] = true;
871 auto const jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
872 BEAST_EXPECT(jrr[jss::status] == "success");
873 auto const& txns = jrr[jss::ledger][jss::transactions];
874 BEAST_EXPECT(txns.isArray() && txns.size() > 0);
875 for (auto const& txn : txns)
876 {
877 BEAST_EXPECT(txn.isMember(jss::ctid));
878 }
879 }
880
881 // Binary expanded: CTID present
882 {
883 json::Value jvParams;
884 jvParams[jss::ledger_index] = "validated";
885 jvParams[jss::transactions] = true;
886 jvParams[jss::expand] = true;
887 jvParams[jss::binary] = true;
888 jvParams[jss::api_version] = 2;
889 auto const jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
890 BEAST_EXPECT(jrr[jss::status] == "success");
891 auto const& txns = jrr[jss::ledger][jss::transactions];
892 BEAST_EXPECT(txns.isArray() && txns.size() > 0);
893 for (auto const& txn : txns)
894 {
895 BEAST_EXPECT(txn.isMember(jss::ctid));
896 }
897 }
898
899 // Non-expanded: transactions are plain hash strings, no CTID
900 {
901 json::Value jvParams;
902 jvParams[jss::ledger_index] = "validated";
903 jvParams[jss::transactions] = true;
904 jvParams[jss::api_version] = 2;
905 auto const jrr = env.rpc("json", "ledger", to_string(jvParams))[jss::result];
906 BEAST_EXPECT(jrr[jss::status] == "success");
907 auto const& txns = jrr[jss::ledger][jss::transactions];
908 BEAST_EXPECT(txns.isArray() && txns.size() > 0);
909 for (auto const& txn : txns)
910 {
911 BEAST_EXPECT(txn.isString());
912 }
913 }
914 }
915
916public:
917 void
933};
934
936
937} // namespace xrpl::test
A testsuite class.
Definition suite.h:52
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
UInt size() const
Number of values in array or object.
bool isMember(char const *key) const
Return true if the object has a member named key.
constexpr bool parseHex(std::string_view sv)
Parse a hex string into a base_uint.
Definition base_uint.h:525
virtual std::uint32_t getNetworkID() const noexcept=0
Get the configured network ID.
virtual TxQ & getTxQ()=0
virtual NetworkIDService & getNetworkIDService()=0
Metrics getMetrics(OpenView const &view) const
Returns fee metrics in reference fee level units.
Definition TxQ.cpp:1786
void testLookupLedger()
ledger RPC requests as a way to drive input options to lookupLedger.
void run() override
Runs the suite.
static std::string makeBadAddress(std::string good)
void checkErrorValue(json::Value const &jv, std::string const &err, std::string const &msg)
Immutable cryptographic account descriptor.
Definition jtx/Account.h:21
std::string const & human() const
Returns the human readable public key.
A transaction testing environment.
Definition Env.h:161
Application & app()
Definition Env.h:300
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
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition Env.cpp:354
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:377
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
Converts to MPT Issue or STAmount.
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:18
T empty(T... args)
@ Null
'null' value
Definition json_value.h:22
API version numbers used in later API versions.
Definition ApiVersion.h:36
bool containsError(json::Value const &json)
Returns true if the json contains an rpc error specification.
std::optional< std::string > encodeCTID(uint32_t ledgerSeq, uint32_t txnIndex, uint32_t networkID) noexcept
Encodes ledger sequence, transaction index, and network ID into a CTID string.
Definition CTID.h:36
Helpers for constructing Batch test transactions.
Definition batch.h:26
auto const kMptDexFlags
Definition mpt.h:47
json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:14
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
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
std::unique_ptr< Config > noAdmin(std::unique_ptr< Config >)
adjust config so no admin ports are enabled
Definition envconfig.cpp:75
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
static UInt256 ledgerHash(LedgerHeader const &info)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ WarnRpcFieldsDeprecated
Definition ErrorCodes.h:162
@ terQUEUED
Definition TER.h:226
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:657
BaseUInt< 256 > UInt256
Definition base_uint.h:580
T replace(T... args)
static constexpr auto kNormalConsensusIncreasePercent
Definition Constants.h:135
static constexpr auto kMinimumTxnInLedgerStandalone
Definition Constants.h:134
static constexpr auto kTransactionQueue
Definition Constants.h:66
Set the sequence number on a JTx.
Definition seq.h:16
T to_string(T... args)