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