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