xrpld
Loading...
Searching...
No Matches
Transaction_test.cpp
1#include <test/jtx/Account.h>
2#include <test/jtx/Env.h>
3#include <test/jtx/amount.h>
4#include <test/jtx/envconfig.h>
5#include <test/jtx/noop.h>
6#include <test/jtx/pay.h>
7#include <test/jtx/seq.h>
8#include <test/jtx/ter.h>
9
10#include <xrpld/app/rdb/backend/SQLiteDatabase.h>
11#include <xrpld/rpc/CTID.h>
12
13#include <xrpl/basics/base_uint.h>
14#include <xrpl/basics/strHex.h>
15#include <xrpl/beast/unit_test/suite.h>
16#include <xrpl/core/NetworkIDService.h>
17#include <xrpl/json/json_value.h>
18#include <xrpl/json/to_string.h>
19#include <xrpl/protocol/ApiVersion.h>
20#include <xrpl/protocol/ErrorCodes.h>
21#include <xrpl/protocol/Feature.h>
22#include <xrpl/protocol/SField.h>
23#include <xrpl/protocol/STBase.h>
24#include <xrpl/protocol/STObject.h>
25#include <xrpl/protocol/STTx.h>
26#include <xrpl/protocol/TER.h>
27#include <xrpl/protocol/jss.h>
28#include <xrpl/protocol/serialize.h>
29
30#include <algorithm>
31#include <cctype>
32#include <cstddef>
33#include <cstdint>
34#include <functional>
35#include <memory>
36#include <optional>
37#include <string>
38#include <tuple>
39#include <vector>
40
41namespace xrpl {
42
44{
46 makeNetworkConfig(uint32_t networkID)
47 {
48 using namespace test::jtx;
49 return envconfig([&](std::unique_ptr<Config> cfg) {
50 cfg->networkId = networkID;
51 return cfg;
52 });
53 }
54
55 void
57 {
58 testcase("Test Range Request");
59
60 using namespace test::jtx;
61 using std::to_string;
62
63 char const* command = jss::tx.cStr();
64 char const* binary = jss::binary.cStr();
65 char const* notFound = RPC::getErrorInfo(RpcTxnNotFound).token;
66 char const* invalid = RPC::getErrorInfo(RpcInvalidLgrRange).token;
67 char const* excessive = RPC::getErrorInfo(RpcExcessiveLgrRange).token;
68
69 Env env{*this, features};
70 auto const alice = Account("alice");
71 env.fund(XRP(1000), alice);
72 env.close();
73
76 auto const startLegSeq = env.current()->header().seq;
77 for (int i = 0; i < 750; ++i)
78 {
79 env(noop(alice));
80 txns.emplace_back(env.tx());
81 env.close();
82 metas.emplace_back(env.closed()->txRead(env.tx()->getTransactionID()).second);
83 }
84 auto const endLegSeq = env.closed()->header().seq;
85
86 // Find the existing transactions
87 for (size_t i = 0; i < txns.size(); ++i)
88 {
89 auto const& tx = txns[i];
90 auto const& meta = metas[i];
91 auto const result = env.rpc(
92 command,
93 to_string(tx->getTransactionID()),
94 binary,
95 to_string(startLegSeq),
96 to_string(endLegSeq));
97
98 BEAST_EXPECT(result[jss::result][jss::status] == jss::success);
99 BEAST_EXPECT(result[jss::result][jss::tx] == strHex(tx->getSerializer().getData()));
100 BEAST_EXPECT(result[jss::result][jss::meta] == strHex(meta->getSerializer().getData()));
101 }
102
103 auto const tx = env.jt(noop(alice), Seq(env.seq(alice))).stx;
104 for (int deltaEndSeq = 0; deltaEndSeq < 2; ++deltaEndSeq)
105 {
106 auto const result = env.rpc(
107 command,
108 to_string(tx->getTransactionID()),
109 binary,
110 to_string(startLegSeq),
111 to_string(endLegSeq + deltaEndSeq));
112
113 BEAST_EXPECT(
114 result[jss::result][jss::status] == jss::error &&
115 result[jss::result][jss::error] == notFound);
116
117 if (deltaEndSeq != 0)
118 {
119 BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
120 }
121 else
122 {
123 BEAST_EXPECT(result[jss::result][jss::searched_all].asBool());
124 }
125 }
126
127 // Find transactions outside of provided range.
128 for (auto&& tx : txns)
129 {
130 auto const result = env.rpc(
131 command,
132 to_string(tx->getTransactionID()),
133 binary,
134 to_string(endLegSeq + 1),
135 to_string(endLegSeq + 100));
136
137 BEAST_EXPECT(result[jss::result][jss::status] == jss::success);
138 BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
139 }
140
141 auto const deletedLedger = (startLegSeq + endLegSeq) / 2;
142 {
143 // Remove one of the ledgers from the database directly
144 env.app().getRelationalDatabase().deleteTransactionByLedgerSeq(deletedLedger);
145 }
146
147 for (int deltaEndSeq = 0; deltaEndSeq < 2; ++deltaEndSeq)
148 {
149 auto const result = env.rpc(
150 command,
151 to_string(tx->getTransactionID()),
152 binary,
153 to_string(startLegSeq),
154 to_string(endLegSeq + deltaEndSeq));
155
156 BEAST_EXPECT(
157 result[jss::result][jss::status] == jss::error &&
158 result[jss::result][jss::error] == notFound);
159 BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
160 }
161
162 // Provide range without providing the `binary`
163 // field. (Tests parameter parsing)
164 {
165 auto const result = env.rpc(
166 command,
167 to_string(tx->getTransactionID()),
168 to_string(startLegSeq),
169 to_string(endLegSeq));
170
171 BEAST_EXPECT(
172 result[jss::result][jss::status] == jss::error &&
173 result[jss::result][jss::error] == notFound);
174
175 BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
176 }
177
178 // Provide range without providing the `binary`
179 // field. (Tests parameter parsing)
180 {
181 auto const result = env.rpc(
182 command,
183 to_string(tx->getTransactionID()),
184 to_string(startLegSeq),
185 to_string(deletedLedger - 1));
186
187 BEAST_EXPECT(
188 result[jss::result][jss::status] == jss::error &&
189 result[jss::result][jss::error] == notFound);
190
191 BEAST_EXPECT(result[jss::result][jss::searched_all].asBool());
192 }
193
194 // Provide range without providing the `binary`
195 // field. (Tests parameter parsing)
196 {
197 auto const result = env.rpc(
198 command,
199 to_string(txns[0]->getTransactionID()),
200 to_string(startLegSeq),
201 to_string(deletedLedger - 1));
202
203 BEAST_EXPECT(result[jss::result][jss::status] == jss::success);
204 BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
205 }
206
207 // Provide an invalid range: (min > max)
208 {
209 auto const result = env.rpc(
210 command,
211 to_string(tx->getTransactionID()),
212 binary,
213 to_string(deletedLedger - 1),
214 to_string(startLegSeq));
215
216 BEAST_EXPECT(
217 result[jss::result][jss::status] == jss::error &&
218 result[jss::result][jss::error] == invalid);
219
220 BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
221 }
222
223 // Provide an invalid range: (min < 0)
224 {
225 auto const result = env.rpc(
226 command,
227 to_string(tx->getTransactionID()),
228 binary,
229 to_string(-1),
230 to_string(deletedLedger - 1));
231
232 BEAST_EXPECT(
233 result[jss::result][jss::status] == jss::error &&
234 result[jss::result][jss::error] == invalid);
235
236 BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
237 }
238
239 // Provide an invalid range: (min < 0, max < 0)
240 {
241 auto const result = env.rpc(
242 command, to_string(tx->getTransactionID()), binary, to_string(-20), to_string(-10));
243
244 BEAST_EXPECT(
245 result[jss::result][jss::status] == jss::error &&
246 result[jss::result][jss::error] == invalid);
247
248 BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
249 }
250
251 // Provide an invalid range: (only one value)
252 {
253 auto const result =
254 env.rpc(command, to_string(tx->getTransactionID()), binary, to_string(20));
255
256 BEAST_EXPECT(
257 result[jss::result][jss::status] == jss::error &&
258 result[jss::result][jss::error] == invalid);
259
260 BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
261 }
262
263 // Provide an invalid range: (only one value)
264 {
265 auto const result = env.rpc(command, to_string(tx->getTransactionID()), to_string(20));
266
267 // Since we only provided one value for the range,
268 // the interface parses it as a false binary flag,
269 // as single-value ranges are not accepted. Since
270 // the error this causes differs depending on the platform
271 // we don't call out a specific error here.
272 BEAST_EXPECT(result[jss::result][jss::status] == jss::error);
273
274 BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
275 }
276
277 // Provide an invalid range: (max - min > 1000)
278 {
279 auto const result = env.rpc(
280 command,
281 to_string(tx->getTransactionID()),
282 binary,
283 to_string(startLegSeq),
284 to_string(startLegSeq + 1001));
285
286 BEAST_EXPECT(
287 result[jss::result][jss::status] == jss::error &&
288 result[jss::result][jss::error] == excessive);
289
290 BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
291 }
292 }
293
294 void
296 {
297 testcase("CTID Range Request");
298
299 using namespace test::jtx;
300 using std::to_string;
301
302 char const* command = jss::tx.cStr();
303 char const* binary = jss::binary.cStr();
304 char const* notFound = RPC::getErrorInfo(RpcTxnNotFound).token;
305 char const* invalid = RPC::getErrorInfo(RpcInvalidLgrRange).token;
306 char const* excessive = RPC::getErrorInfo(RpcExcessiveLgrRange).token;
307
308 Env env{*this, makeNetworkConfig(11111)};
309 uint32_t const netID = env.app().getNetworkIDService().getNetworkID();
310
311 auto const alice = Account("alice");
312 env.fund(XRP(1000), alice);
313 env.close();
314
317 auto const startLegSeq = env.current()->header().seq;
318 for (int i = 0; i < 750; ++i)
319 {
320 env(noop(alice));
321 txns.emplace_back(env.tx());
322 env.close();
323 metas.emplace_back(env.closed()->txRead(env.tx()->getTransactionID()).second);
324 }
325 auto const endLegSeq = env.closed()->header().seq;
326
327 // Find the existing transactions
328 for (size_t i = 0; i < txns.size(); ++i)
329 {
330 auto const& tx = txns[i];
331 auto const& meta = metas[i];
332 uint32_t const txnIdx = meta->getFieldU32(sfTransactionIndex);
333 auto const result = env.rpc(
334 command,
335 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
336 *RPC::encodeCTID(startLegSeq + i, txnIdx, netID),
337 binary,
338 to_string(startLegSeq),
339 to_string(endLegSeq));
340
341 BEAST_EXPECT(result[jss::result][jss::status] == jss::success);
342 BEAST_EXPECT(result[jss::result][jss::tx] == strHex(tx->getSerializer().getData()));
343 BEAST_EXPECT(result[jss::result][jss::meta] == strHex(meta->getSerializer().getData()));
344 }
345
346 auto const tx = env.jt(noop(alice), Seq(env.seq(alice))).stx;
347 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
348 auto const ctid = *RPC::encodeCTID(endLegSeq, tx->getSeqValue(), netID);
349 for (int deltaEndSeq = 0; deltaEndSeq < 2; ++deltaEndSeq)
350 {
351 auto const result = env.rpc(
352 command, ctid, binary, to_string(startLegSeq), to_string(endLegSeq + deltaEndSeq));
353
354 BEAST_EXPECT(
355 result[jss::result][jss::status] == jss::error &&
356 result[jss::result][jss::error] == notFound);
357
358 if (deltaEndSeq != 0)
359 {
360 BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
361 }
362 else
363 {
364 BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
365 }
366 }
367
368 // Find transactions outside of provided range.
369 for (size_t i = 0; i < txns.size(); ++i)
370 {
371 // auto const& tx = txns[i];
372 auto const& meta = metas[i];
373 uint32_t const txnIdx = meta->getFieldU32(sfTransactionIndex);
374 auto const result = env.rpc(
375 command,
376 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
377 *RPC::encodeCTID(startLegSeq + i, txnIdx, netID),
378 binary,
379 to_string(endLegSeq + 1),
380 to_string(endLegSeq + 100));
381
382 BEAST_EXPECT(result[jss::result][jss::status] == jss::success);
383 BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
384 }
385
386 auto const deletedLedger = (startLegSeq + endLegSeq) / 2;
387 {
388 // Remove one of the ledgers from the database directly
389 env.app().getRelationalDatabase().deleteTransactionByLedgerSeq(deletedLedger);
390 }
391
392 for (int deltaEndSeq = 0; deltaEndSeq < 2; ++deltaEndSeq)
393 {
394 auto const result = env.rpc(
395 command, ctid, binary, to_string(startLegSeq), to_string(endLegSeq + deltaEndSeq));
396
397 BEAST_EXPECT(
398 result[jss::result][jss::status] == jss::error &&
399 result[jss::result][jss::error] == notFound);
400 BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
401 }
402
403 // Provide range without providing the `binary`
404 // field. (Tests parameter parsing)
405 {
406 auto const result =
407 env.rpc(command, ctid, to_string(startLegSeq), to_string(endLegSeq));
408
409 BEAST_EXPECT(
410 result[jss::result][jss::status] == jss::error &&
411 result[jss::result][jss::error] == notFound);
412
413 BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
414 }
415
416 // Provide range without providing the `binary`
417 // field. (Tests parameter parsing)
418 {
419 auto const result =
420 env.rpc(command, ctid, to_string(startLegSeq), to_string(deletedLedger - 1));
421
422 BEAST_EXPECT(
423 result[jss::result][jss::status] == jss::error &&
424 result[jss::result][jss::error] == notFound);
425
426 BEAST_EXPECT(!result[jss::result][jss::searched_all].asBool());
427 }
428
429 // Provide range without providing the `binary`
430 // field. (Tests parameter parsing)
431 {
432 auto const& meta = metas[0];
433 uint32_t const txnIdx = meta->getFieldU32(sfTransactionIndex);
434 auto const result = env.rpc(
435 command,
436 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
437 *RPC::encodeCTID(endLegSeq, txnIdx, netID),
438 to_string(startLegSeq),
439 to_string(deletedLedger - 1));
440
441 BEAST_EXPECT(result[jss::result][jss::status] == jss::success);
442 BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
443 }
444
445 // Provide an invalid range: (min > max)
446 {
447 auto const result = env.rpc(
448 command, ctid, binary, to_string(deletedLedger - 1), to_string(startLegSeq));
449
450 BEAST_EXPECT(
451 result[jss::result][jss::status] == jss::error &&
452 result[jss::result][jss::error] == invalid);
453
454 BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
455 }
456
457 // Provide an invalid range: (min < 0)
458 {
459 auto const result =
460 env.rpc(command, ctid, binary, to_string(-1), to_string(deletedLedger - 1));
461
462 BEAST_EXPECT(
463 result[jss::result][jss::status] == jss::error &&
464 result[jss::result][jss::error] == invalid);
465
466 BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
467 }
468
469 // Provide an invalid range: (min < 0, max < 0)
470 {
471 auto const result = env.rpc(command, ctid, binary, to_string(-20), to_string(-10));
472
473 BEAST_EXPECT(
474 result[jss::result][jss::status] == jss::error &&
475 result[jss::result][jss::error] == invalid);
476
477 BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
478 }
479
480 // Provide an invalid range: (only one value)
481 {
482 auto const result = env.rpc(command, ctid, binary, to_string(20));
483
484 BEAST_EXPECT(
485 result[jss::result][jss::status] == jss::error &&
486 result[jss::result][jss::error] == invalid);
487
488 BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
489 }
490
491 // Provide an invalid range: (only one value)
492 {
493 auto const result = env.rpc(command, ctid, to_string(20));
494
495 // Since we only provided one value for the range,
496 // the interface parses it as a false binary flag,
497 // as single-value ranges are not accepted. Since
498 // the error this causes differs depending on the platform
499 // we don't call out a specific error here.
500 BEAST_EXPECT(result[jss::result][jss::status] == jss::error);
501
502 BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
503 }
504
505 // Provide an invalid range: (max - min > 1000)
506 {
507 auto const result = env.rpc(
508 command, ctid, binary, to_string(startLegSeq), to_string(startLegSeq + 1001));
509
510 BEAST_EXPECT(
511 result[jss::result][jss::status] == jss::error &&
512 result[jss::result][jss::error] == excessive);
513
514 BEAST_EXPECT(!result[jss::result].isMember(jss::searched_all));
515 }
516 }
517
518 void
520 {
521 testcase("CTID Validation");
522
523 using namespace test::jtx;
524 using std::to_string;
525
526 Env const env{*this, makeNetworkConfig(11111)};
527
528 // Test case 1: Valid input values
529 auto const expected11 = std::optional<std::string>("CFFFFFFFFFFFFFFF");
530 BEAST_EXPECT(RPC::encodeCTID(0x0FFF'FFFFUL, 0xFFFFU, 0xFFFFU) == expected11);
531 auto const expected12 = std::optional<std::string>("C000000000000000");
532 BEAST_EXPECT(RPC::encodeCTID(0, 0, 0) == expected12);
533 auto const expected13 = std::optional<std::string>("C000000100020003");
534 BEAST_EXPECT(RPC::encodeCTID(1U, 2U, 3U) == expected13);
535 auto const expected14 = std::optional<std::string>("C0CA2AA7326FFFFF");
536 BEAST_EXPECT(RPC::encodeCTID(13249191UL, 12911U, 65535U) == expected14);
537
538 // Test case 2: ledger_seq greater than 0xFFFFFFF
539 BEAST_EXPECT(!RPC::encodeCTID(0x1000'0000UL, 0xFFFFU, 0xFFFFU));
540
541 // Test case 3: txn_index greater than 0xFFFF
542 BEAST_EXPECT(!RPC::encodeCTID(0x0FFF'FFFF, 0x1'0000, 0xFFFF));
543
544 // Test case 4: network_id greater than 0xFFFF
545 BEAST_EXPECT(!RPC::encodeCTID(0x0FFF'FFFFUL, 0xFFFFU, 0x1'0000U));
546
547 // Test case 5: Valid input values
548 auto const expected51 =
550 BEAST_EXPECT(RPC::decodeCTID("C000000000000000") == expected51);
551 auto const expected52 =
553 BEAST_EXPECT(RPC::decodeCTID("C000000100020003") == expected52);
555 std::make_tuple(13249191UL, 12911U, 49221U));
556 BEAST_EXPECT(RPC::decodeCTID("C0CA2AA7326FC045") == expected53);
557
558 // Test case 6: ctid not a string or big int
559 BEAST_EXPECT(!RPC::decodeCTID(0xCFF));
560
561 // Test case 7: ctid not a hexadecimal string
562 BEAST_EXPECT(!RPC::decodeCTID("C003FFFFFFFFFFFG"));
563
564 // Test case 8: ctid not exactly 16 nibbles
565 BEAST_EXPECT(!RPC::decodeCTID("C003FFFFFFFFFFF"));
566
567 // Test case 9: ctid too large to be a valid CTID value
568 BEAST_EXPECT(!RPC::decodeCTID("CFFFFFFFFFFFFFFFF"));
569
570 // Test case 10: ctid doesn't start with a C nibble
571 BEAST_EXPECT(!RPC::decodeCTID("FFFFFFFFFFFFFFFF"));
572
573 // Test case 11: Valid input values
574 BEAST_EXPECT(
575 (RPC::decodeCTID(0xCFFF'FFFF'FFFF'FFFFULL) ==
577 std::make_tuple(0x0FFF'FFFFUL, 0xFFFFU, 0xFFFFU))));
578 BEAST_EXPECT(
579 (RPC::decodeCTID(0xC000'0000'0000'0000ULL) ==
581 BEAST_EXPECT(
582 (RPC::decodeCTID(0xC000'0001'0002'0003ULL) ==
584 BEAST_EXPECT(
585 (RPC::decodeCTID(0xC0CA'2AA7'326F'C045ULL) ==
587 std::make_tuple(1324'9191UL, 12911U, 49221U))));
588
589 // Test case 12: ctid not exactly 16 nibbles
590 BEAST_EXPECT(!RPC::decodeCTID(0xC003'FFFF'FFFF'FFF));
591
592 // Test case 13: ctid too large to be a valid CTID value
593 // this test case is not possible in c++ because it would overflow the
594 // type, left in for completeness
595 // BEAST_EXPECT(!RPC::decodeCTID(0xCFFFFFFFFFFFFFFFFULL));
596
597 // Test case 14: ctid doesn't start with a C nibble
598 BEAST_EXPECT(!RPC::decodeCTID(0xFFFF'FFFF'FFFF'FFFFULL));
599 }
600
601 void
603 {
604 testcase("CTID RPC");
605
606 using namespace test::jtx;
607
608 // Use a Concise Transaction Identifier to request a transaction.
609 for (uint32_t const netID : {11111, 65535, 65536})
610 {
611 Env env{*this, makeNetworkConfig(netID)};
612 BEAST_EXPECT(netID == env.app().getNetworkIDService().getNetworkID());
613
614 auto const alice = Account("alice");
615 auto const bob = Account("bob");
616
617 auto const startLegSeq = env.current()->header().seq;
618 env.fund(XRP(10000), alice, bob);
619 env(pay(alice, bob, XRP(10)));
620 env.close();
621
622 auto const ctid = RPC::encodeCTID(startLegSeq, 0, netID);
623 if (netID > 0xFFFF)
624 {
625 // Concise transaction IDs do not support a network ID > 0xFFFF.
626 BEAST_EXPECT(ctid == std::nullopt);
627 continue;
628 }
629
630 json::Value jsonTx;
631 jsonTx[jss::binary] = false;
632 jsonTx[jss::ctid] = *ctid; // NOLINT(bugprone-unchecked-optional-access)
633 jsonTx[jss::id] = 1;
634 auto const jrr = env.rpc("json", "tx", to_string(jsonTx))[jss::result];
635 BEAST_EXPECT(jrr[jss::ctid] == ctid);
636 BEAST_EXPECT(jrr.isMember(jss::hash));
637 }
638
639 // test querying with mixed case ctid
640 {
641 Env env{*this, makeNetworkConfig(11111)};
642 std::uint32_t const netID = env.app().getNetworkIDService().getNetworkID();
643
644 Account const alice = Account("alice");
645 Account const bob = Account("bob");
646
647 std::uint32_t const startLegSeq = env.current()->header().seq;
648 env.fund(XRP(10000), alice, bob);
649 env(pay(alice, bob, XRP(10)));
650 env.close();
651
652 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
653 std::string const ctid = *RPC::encodeCTID(startLegSeq, 0, netID);
654 auto isUpper = [](char c) { return std::isupper(c) != 0; };
655
656 // Verify that there are at least two upper case letters in ctid and
657 // test a mixed case
658 if (BEAST_EXPECT(std::count_if(ctid.begin(), ctid.end(), isUpper) > 1))
659 {
660 // Change the first upper case letter to lower case.
661 std::string mixedCase = ctid;
662 {
663 auto const iter = std::ranges::find_if(mixedCase, isUpper);
664 *iter = std::tolower(*iter);
665 }
666 BEAST_EXPECT(ctid != mixedCase);
667
668 json::Value jsonTx;
669 jsonTx[jss::binary] = false;
670 jsonTx[jss::ctid] = mixedCase;
671 jsonTx[jss::id] = 1;
672 json::Value const jrr = env.rpc("json", "tx", to_string(jsonTx))[jss::result];
673 BEAST_EXPECT(jrr[jss::ctid] == ctid);
674 BEAST_EXPECT(jrr[jss::hash]);
675 }
676 }
677
678 // test that if the network is 65535 the ctid is not in the response
679 // Using a hash to request the transaction, test the network ID
680 // boundary where the CTID is (not) in the response.
681 for (uint32_t const netID : {2, 1024, 65535, 65536})
682 {
683 Env env{*this, makeNetworkConfig(netID)};
684 BEAST_EXPECT(netID == env.app().getNetworkIDService().getNetworkID());
685
686 auto const alice = Account("alice");
687 auto const bob = Account("bob");
688
689 env.fund(XRP(10000), alice, bob);
690 env(pay(alice, bob, XRP(10)));
691 env.close();
692
693 auto const ledgerSeq = env.current()->header().seq;
694
695 env(noop(alice), Ter(tesSUCCESS));
696 env.close();
697
698 json::Value params;
699 params[jss::id] = 1;
700 auto const hash = env.tx()->getJson(JsonOptions::Values::None)[jss::hash];
701 params[jss::transaction] = hash;
702 auto const jrr = env.rpc("json", "tx", to_string(params))[jss::result];
703 BEAST_EXPECT(jrr[jss::hash] == hash);
704
705 BEAST_EXPECT(jrr.isMember(jss::ctid) == (netID <= 0xFFFF));
706 if (jrr.isMember(jss::ctid))
707 {
708 auto const ctid = RPC::encodeCTID(ledgerSeq, 0, netID);
709 BEAST_EXPECT(
710 jrr[jss::ctid] == *ctid); // NOLINT(bugprone-unchecked-optional-access)
711 }
712 }
713
714 // test the wrong network ID was submitted
715 {
716 Env env{*this, makeNetworkConfig(21337)};
717 uint32_t const netID = env.app().getNetworkIDService().getNetworkID();
718
719 auto const alice = Account("alice");
720 auto const bob = Account("bob");
721
722 auto const startLegSeq = env.current()->header().seq;
723 env.fund(XRP(10000), alice, bob);
724 env(pay(alice, bob, XRP(10)));
725 env.close();
726
727 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
728 auto const ctid = *RPC::encodeCTID(startLegSeq, 0, netID + 1);
729 json::Value jsonTx;
730 jsonTx[jss::binary] = false;
731 jsonTx[jss::ctid] = ctid;
732 jsonTx[jss::id] = 1;
733 auto const jrr = env.rpc("json", "tx", to_string(jsonTx))[jss::result];
734 BEAST_EXPECT(jrr[jss::error] == "wrongNetwork");
735 BEAST_EXPECT(jrr[jss::error_code] == RpcWrongNetwork);
736 BEAST_EXPECT(
737 jrr[jss::error_message] ==
738 "Wrong network. You should submit this request to a node "
739 "running on NetworkID: 21338");
740 }
741 }
742
743 void
744 testRequest(FeatureBitset features, unsigned apiVersion)
745 {
746 testcase("Test Request API version " + std::to_string(apiVersion));
747
748 using namespace test::jtx;
749 using std::to_string;
750
751 Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
752 cfg->fees.referenceFee = 10;
753 return cfg;
754 })};
755 Account const alice{"alice"};
756 Account const alie{"alie"};
757 Account const gw{"gw"};
758 auto const usd{gw["USD"]};
759
760 env.fund(XRP(1000000), alice, gw);
761 env.close();
762
763 // AccountSet
764 env(noop(alice));
765
766 // Payment
767 env(pay(alice, gw, XRP(100)));
768
769 std::shared_ptr<STTx const> const txn = env.tx();
770 env.close();
772 env.closed()->txRead(env.tx()->getTransactionID()).second;
773
774 json::Value expected = txn->getJson(JsonOptions::Values::None);
775 expected[jss::DeliverMax] = expected[jss::Amount];
776 if (apiVersion > 1)
777 {
778 expected.removeMember(jss::hash);
779 expected.removeMember(jss::Amount);
780 }
781
782 json::Value const result = {[&env, txn, apiVersion]() {
784 params[jss::transaction] = to_string(txn->getTransactionID());
785 params[jss::binary] = false;
786 params[jss::api_version] = apiVersion;
787 return env.client().invoke("tx", params);
788 }()};
789
790 BEAST_EXPECT(result[jss::result][jss::status] == jss::success);
791 if (apiVersion > 1)
792 {
793 BEAST_EXPECT(result[jss::result][jss::close_time_iso] == "2000-01-01T00:00:20Z");
794 BEAST_EXPECT(result[jss::result][jss::hash] == to_string(txn->getTransactionID()));
795 BEAST_EXPECT(result[jss::result][jss::validated] == true);
796 BEAST_EXPECT(result[jss::result][jss::ledger_index] == 4);
797 BEAST_EXPECT(
798 result[jss::result][jss::ledger_hash] ==
799 "B41882E20F0EC6228417D28B9AE0F33833645D35F6799DFB782AC97FC4BB51"
800 "D2");
801 }
802
803 for (auto memberIt = expected.begin(); memberIt != expected.end(); memberIt++)
804 {
805 std::string const name = memberIt.memberName();
806 auto const& resultTransaction =
807 (apiVersion > 1 ? result[jss::result][jss::tx_json] : result[jss::result]);
808 if (BEAST_EXPECT(resultTransaction.isMember(name)))
809 {
810 auto const received = resultTransaction[name];
811 BEAST_EXPECTS(
812 received == *memberIt,
813 "Transaction contains \n\"" + name + "\": " //
814 + to_string(received) //
815 + " but expected " //
816 + to_string(expected));
817 }
818 }
819 }
820
821 void
822 testBinaryRequest(unsigned apiVersion)
823 {
824 testcase("Test binary request API version " + std::to_string(apiVersion));
825
826 using namespace test::jtx;
827 using std::to_string;
828
829 Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
830 cfg->fees.referenceFee = 10;
831 return cfg;
832 })};
833 Account const alice{"alice"};
834 Account const gw{"gw"};
835 auto const usd{gw["USD"]};
836
837 env.fund(XRP(1000000), alice, gw);
838 std::shared_ptr<STTx const> const txn = env.tx();
839 BEAST_EXPECT(
840 to_string(txn->getTransactionID()) ==
841 "3F8BDE5A5F82C4F4708E5E9255B713E303E6E1A371FD5C7A704AFD1387C23981");
842 env.close();
844 env.closed()->txRead(txn->getTransactionID()).second;
845
846 std::string const expectedTxBlob = serializeHex(*txn);
847 std::string const expectedMetaBlob = serializeHex(*meta);
848
849 json::Value const result = [&env, txn, apiVersion]() {
851 params[jss::transaction] = to_string(txn->getTransactionID());
852 params[jss::binary] = true;
853 params[jss::api_version] = apiVersion;
854 return env.client().invoke("tx", params);
855 }();
856
857 if (BEAST_EXPECT(result[jss::status] == "success"))
858 {
859 BEAST_EXPECT(result[jss::result][jss::status] == "success");
860 BEAST_EXPECT(result[jss::result][jss::validated] == true);
861 BEAST_EXPECT(result[jss::result][jss::hash] == to_string(txn->getTransactionID()));
862 BEAST_EXPECT(result[jss::result][jss::ledger_index] == 3);
863 BEAST_EXPECT(result[jss::result][jss::ctid] == "C000000300030000");
864
865 if (apiVersion > 1)
866 {
867 BEAST_EXPECT(result[jss::result][jss::tx_blob] == expectedTxBlob);
868 BEAST_EXPECT(result[jss::result][jss::meta_blob] == expectedMetaBlob);
869 BEAST_EXPECT(
870 result[jss::result][jss::ledger_hash] ==
871 "2D5150E5A5AA436736A732291E437ABF01BC9E206C2DF3C77C4F856915"
872 "7905AA");
873 BEAST_EXPECT(result[jss::result][jss::close_time_iso] == "2000-01-01T00:00:10Z");
874 }
875 else
876 {
877 BEAST_EXPECT(result[jss::result][jss::tx] == expectedTxBlob);
878 BEAST_EXPECT(result[jss::result][jss::meta] == expectedMetaBlob);
879 BEAST_EXPECT(result[jss::result][jss::date] == 10);
880 }
881 }
882 }
883
884public:
885 void
886 run() override
887 {
888 using namespace test::jtx;
890
891 FeatureBitset const all{testableAmendments()};
892 testWithFeats(all);
893 }
894
895 void
897 {
898 testRangeRequest(features);
899 testRangeCTIDRequest(features);
900 testCTIDValidation(features);
901 testRPCsForCTID(features);
903 }
904};
905
907
908} // namespace xrpl
T begin(T... args)
T bind_front(T... args)
A testsuite class.
Definition suite.h:50
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:149
Represents a JSON value.
Definition json_value.h:130
const_iterator begin() const
Value removeMember(char const *key)
Remove and return the named member.
const_iterator end() const
void testCTIDValidation(FeatureBitset features)
void run() override
Runs the suite.
void testBinaryRequest(unsigned apiVersion)
void testRangeCTIDRequest(FeatureBitset features)
static std::unique_ptr< Config > makeNetworkConfig(uint32_t networkID)
void testRangeRequest(FeatureBitset features)
void testRPCsForCTID(FeatureBitset features)
void testWithFeats(FeatureBitset features)
void testRequest(FeatureBitset features, unsigned apiVersion)
T count_if(T... args)
T emplace_back(T... args)
T end(T... args)
T find_if(T... args)
T make_tuple(T... args)
@ Object
object value (collection of name/value pairs).
Definition json_value.h:26
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:31
ErrorInfo const & getErrorInfo(ErrorCodeI code)
Returns an ErrorInfo that reflects the error code.
std::optional< std::tuple< uint32_t, uint16_t, uint16_t > > decodeCTID(T const ctid) noexcept
Decodes a CTID string or integer into its component parts.
Definition CTID.h:58
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ RpcExcessiveLgrRange
Definition ErrorCodes.h:117
@ RpcTxnNotFound
Definition ErrorCodes.h:62
@ RpcWrongNetwork
Definition ErrorCodes.h:32
@ RpcInvalidLgrRange
Definition ErrorCodes.h:118
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:10
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:633
void forAllApiVersions(Fn const &fn, Args &&... args)
Definition ApiVersion.h:158
std::string serializeHex(STObject const &o)
Serialize an object to a hex string.
Definition serialize.h:21
BEAST_DEFINE_TESTSUITE(AccountTxPaging, app, xrpl)
@ tesSUCCESS
Definition TER.h:240
T size(T... args)
json::StaticString token
Definition ErrorCodes.h:190
T to_string(T... args)