rippled
Loading...
Searching...
No Matches
AccountLines_test.cpp
1#include <test/jtx.h>
2
3#include <xrpl/beast/unit_test.h>
4#include <xrpl/protocol/ErrorCodes.h>
5#include <xrpl/protocol/TxFlags.h>
6#include <xrpl/protocol/jss.h>
7
8namespace xrpl {
9namespace RPC {
10
12{
13public:
14 void
16 {
17 testcase("account_lines");
18
19 using namespace test::jtx;
20 Env env(*this);
21 {
22 // account_lines with no account.
23 auto const lines = env.rpc("json", "account_lines", "{ }");
24 BEAST_EXPECT(
25 lines[jss::result][jss::error_message] ==
26 RPC::missing_field_error(jss::account)[jss::error_message]);
27 }
28 {
29 // account_lines with a malformed account.
30 Json::Value params;
31 params[jss::account] =
32 "n9MJkEKHDhy5eTLuHUQeAAjo382frHNbFK4C8hcwN4nwM2SrLdBj";
33 auto const lines =
34 env.rpc("json", "account_lines", to_string(params));
35 BEAST_EXPECT(
36 lines[jss::result][jss::error_message] ==
37 RPC::make_error(rpcACT_MALFORMED)[jss::error_message]);
38 }
39 {
40 // test account non-string
41 auto testInvalidAccountParam = [&](auto const& param) {
42 Json::Value params;
43 params[jss::account] = param;
44 auto jrr = env.rpc(
45 "json", "account_lines", to_string(params))[jss::result];
46 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
47 BEAST_EXPECT(
48 jrr[jss::error_message] == "Invalid field 'account'.");
49 };
50
51 testInvalidAccountParam(1);
52 testInvalidAccountParam(1.1);
53 testInvalidAccountParam(true);
54 testInvalidAccountParam(Json::Value(Json::nullValue));
55 testInvalidAccountParam(Json::Value(Json::objectValue));
56 testInvalidAccountParam(Json::Value(Json::arrayValue));
57 }
58 Account const alice{"alice"};
59 {
60 // account_lines on an unfunded account.
61 Json::Value params;
62 params[jss::account] = alice.human();
63 auto const lines =
64 env.rpc("json", "account_lines", to_string(params));
65 BEAST_EXPECT(
66 lines[jss::result][jss::error_message] ==
67 RPC::make_error(rpcACT_NOT_FOUND)[jss::error_message]);
68 }
69 env.fund(XRP(10000), alice);
70 env.close();
71 LedgerHeader const ledger3Info = env.closed()->header();
72 BEAST_EXPECT(ledger3Info.seq == 3);
73
74 {
75 // alice is funded but has no lines. An empty array is returned.
76 Json::Value params;
77 params[jss::account] = alice.human();
78 auto const lines =
79 env.rpc("json", "account_lines", to_string(params));
80 BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
81 BEAST_EXPECT(lines[jss::result][jss::lines].size() == 0);
82 }
83 {
84 // Specify a ledger that doesn't exist.
85 Json::Value params;
86 params[jss::account] = alice.human();
87 params[jss::ledger_index] = "nonsense";
88 auto const lines =
89 env.rpc("json", "account_lines", to_string(params));
90 BEAST_EXPECT(
91 lines[jss::result][jss::error_message] ==
92 "Invalid field 'ledger_index', not string or number.");
93 }
94 {
95 // Specify a different ledger that doesn't exist.
96 Json::Value params;
97 params[jss::account] = alice.human();
98 params[jss::ledger_index] = 50000;
99 auto const lines =
100 env.rpc("json", "account_lines", to_string(params));
101 BEAST_EXPECT(
102 lines[jss::result][jss::error_message] == "ledgerNotFound");
103 }
104 // Create trust lines to share with alice.
105 Account const gw1{"gw1"};
106 env.fund(XRP(10000), gw1);
107 std::vector<IOU> gw1Currencies;
108
109 for (char c = 0; c <= ('Z' - 'A'); ++c)
110 {
111 // gw1 currencies have names "YAA" -> "YAZ".
112 gw1Currencies.push_back(
113 gw1[std::string("YA") + static_cast<char>('A' + c)]);
114 IOU const& gw1Currency = gw1Currencies.back();
115
116 // Establish trust lines.
117 env(trust(alice, gw1Currency(100 + c)));
118 env(pay(gw1, alice, gw1Currency(50 + c)));
119 }
120 env.close();
121 LedgerHeader const ledger4Info = env.closed()->header();
122 BEAST_EXPECT(ledger4Info.seq == 4);
123
124 // Add another set of trust lines in another ledger so we can see
125 // differences in historic ledgers.
126 Account const gw2{"gw2"};
127 env.fund(XRP(10000), gw2);
128
129 // gw2 requires authorization.
130 env(fset(gw2, asfRequireAuth));
131 env.close();
132 std::vector<IOU> gw2Currencies;
133
134 for (char c = 0; c <= ('Z' - 'A'); ++c)
135 {
136 // gw2 currencies have names "ZAA" -> "ZAZ".
137 gw2Currencies.push_back(
138 gw2[std::string("ZA") + static_cast<char>('A' + c)]);
139 IOU const& gw2Currency = gw2Currencies.back();
140
141 // Establish trust lines.
142 env(trust(alice, gw2Currency(200 + c)));
143 env(trust(gw2, gw2Currency(0), alice, tfSetfAuth));
144 env.close();
145 env(pay(gw2, alice, gw2Currency(100 + c)));
146 env.close();
147
148 // Set flags on gw2 trust lines so we can look for them.
149 env(trust(
150 alice,
151 gw2Currency(0),
152 gw2,
154 }
155 env.close();
156 LedgerHeader const ledger58Info = env.closed()->header();
157 BEAST_EXPECT(ledger58Info.seq == 58);
158
159 // A re-usable test for historic ledgers.
160 auto testAccountLinesHistory = [this, &env](
161 Account const& account,
162 LedgerHeader const& info,
163 int count) {
164 // Get account_lines by ledger index.
165 Json::Value paramsSeq;
166 paramsSeq[jss::account] = account.human();
167 paramsSeq[jss::ledger_index] = info.seq;
168 auto const linesSeq =
169 env.rpc("json", "account_lines", to_string(paramsSeq));
170 BEAST_EXPECT(linesSeq[jss::result][jss::lines].isArray());
171 BEAST_EXPECT(linesSeq[jss::result][jss::lines].size() == count);
172
173 // Get account_lines by ledger hash.
174 Json::Value paramsHash;
175 paramsHash[jss::account] = account.human();
176 paramsHash[jss::ledger_hash] = to_string(info.hash);
177 auto const linesHash =
178 env.rpc("json", "account_lines", to_string(paramsHash));
179 BEAST_EXPECT(linesHash[jss::result][jss::lines].isArray());
180 BEAST_EXPECT(linesHash[jss::result][jss::lines].size() == count);
181 };
182
183 // Alice should have no trust lines in ledger 3.
184 testAccountLinesHistory(alice, ledger3Info, 0);
185
186 // Alice should have 26 trust lines in ledger 4.
187 testAccountLinesHistory(alice, ledger4Info, 26);
188
189 // Alice should have 52 trust lines in ledger 58.
190 testAccountLinesHistory(alice, ledger58Info, 52);
191
192 {
193 // Invalid to specify both index and hash
194 Json::Value params;
195 params[jss::account] = alice.human();
196 params[jss::ledger_hash] = to_string(ledger4Info.hash);
197 params[jss::ledger_index] = ledger58Info.seq;
198 auto const lines = env.rpc(
199 "json", "account_lines", to_string(params))[jss::result];
200 BEAST_EXPECT(lines[jss::error] == "invalidParams");
201 BEAST_EXPECT(
202 lines[jss::error_message] ==
203 "Exactly one of 'ledger_hash' or 'ledger_index' can be "
204 "specified.");
205 }
206 {
207 // Invalid index
208 Json::Value params;
209 params[jss::account] = alice.human();
210 params[jss::ledger_index] = Json::objectValue;
211 auto const lines = env.rpc(
212 "json", "account_lines", to_string(params))[jss::result];
213 BEAST_EXPECT(lines[jss::error] == "invalidParams");
214 BEAST_EXPECT(
215 lines[jss::error_message] ==
216 "Invalid field 'ledger_index', not string or number.");
217 }
218 {
219 // alice should have 52 trust lines in the current ledger.
220 Json::Value params;
221 params[jss::account] = alice.human();
222 auto const lines =
223 env.rpc("json", "account_lines", to_string(params));
224 BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
225 BEAST_EXPECT(lines[jss::result][jss::lines].size() == 52);
226 }
227 {
228 // alice should have 26 trust lines with gw1.
229 Json::Value params;
230 params[jss::account] = alice.human();
231 params[jss::peer] = gw1.human();
232 auto const lines =
233 env.rpc("json", "account_lines", to_string(params));
234 BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
235 BEAST_EXPECT(lines[jss::result][jss::lines].size() == 26);
236
237 // Check no ripple is not set for trustlines between alice and gw1
238 auto const& line = lines[jss::result][jss::lines][0u];
239 BEAST_EXPECT(!line[jss::no_ripple].isMember(jss::no_ripple));
240 }
241 {
242 // Use a malformed peer.
243 Json::Value params;
244 params[jss::account] = alice.human();
245 params[jss::peer] =
246 "n9MJkEKHDhy5eTLuHUQeAAjo382frHNbFK4C8hcwN4nwM2SrLdBj";
247 auto const lines =
248 env.rpc("json", "account_lines", to_string(params));
249 BEAST_EXPECT(
250 lines[jss::result][jss::error_message] ==
251 RPC::make_error(rpcACT_MALFORMED)[jss::error_message]);
252 }
253 {
254 // A negative limit should fail.
255 Json::Value params;
256 params[jss::account] = alice.human();
257 params[jss::limit] = -1;
258 auto const lines =
259 env.rpc("json", "account_lines", to_string(params));
260 BEAST_EXPECT(
261 lines[jss::result][jss::error_message] ==
262 RPC::expected_field_message(jss::limit, "unsigned integer"));
263 }
264 {
265 // Limit the response to 1 trust line.
266 Json::Value paramsA;
267 paramsA[jss::account] = alice.human();
268 paramsA[jss::limit] = 1;
269 auto const linesA =
270 env.rpc("json", "account_lines", to_string(paramsA));
271 BEAST_EXPECT(linesA[jss::result][jss::lines].isArray());
272 BEAST_EXPECT(linesA[jss::result][jss::lines].size() == 1);
273
274 // Pick up from where the marker left off. We should get 51.
275 auto marker = linesA[jss::result][jss::marker].asString();
276 Json::Value paramsB;
277 paramsB[jss::account] = alice.human();
278 paramsB[jss::marker] = marker;
279 auto const linesB =
280 env.rpc("json", "account_lines", to_string(paramsB));
281 BEAST_EXPECT(linesB[jss::result][jss::lines].isArray());
282 BEAST_EXPECT(linesB[jss::result][jss::lines].size() == 51);
283
284 // Go again from where the marker left off, but set a limit of 3.
285 Json::Value paramsC;
286 paramsC[jss::account] = alice.human();
287 paramsC[jss::limit] = 3;
288 paramsC[jss::marker] = marker;
289 auto const linesC =
290 env.rpc("json", "account_lines", to_string(paramsC));
291 BEAST_EXPECT(linesC[jss::result][jss::lines].isArray());
292 BEAST_EXPECT(linesC[jss::result][jss::lines].size() == 3);
293
294 // Mess with the marker so it becomes bad and check for the error.
295 marker[5] = marker[5] == '7' ? '8' : '7';
296 Json::Value paramsD;
297 paramsD[jss::account] = alice.human();
298 paramsD[jss::marker] = marker;
299 auto const linesD =
300 env.rpc("json", "account_lines", to_string(paramsD));
301 BEAST_EXPECT(
302 linesD[jss::result][jss::error_message] ==
303 RPC::make_error(rpcINVALID_PARAMS)[jss::error_message]);
304 }
305 {
306 // A non-string marker should also fail.
307 Json::Value params;
308 params[jss::account] = alice.human();
309 params[jss::marker] = true;
310 auto const lines =
311 env.rpc("json", "account_lines", to_string(params));
312 BEAST_EXPECT(
313 lines[jss::result][jss::error_message] ==
314 RPC::expected_field_message(jss::marker, "string"));
315 }
316 {
317 // Check that the flags we expect from alice to gw2 are present.
318 Json::Value params;
319 params[jss::account] = alice.human();
320 params[jss::limit] = 10;
321 params[jss::peer] = gw2.human();
322 auto const lines =
323 env.rpc("json", "account_lines", to_string(params));
324 auto const& line = lines[jss::result][jss::lines][0u];
325 BEAST_EXPECT(line[jss::freeze].asBool() == true);
326 BEAST_EXPECT(line[jss::deep_freeze].asBool() == true);
327 BEAST_EXPECT(line[jss::no_ripple].asBool() == true);
328 BEAST_EXPECT(line[jss::peer_authorized].asBool() == true);
329 }
330 {
331 // Check that the flags we expect from gw2 to alice are present.
332 Json::Value paramsA;
333 paramsA[jss::account] = gw2.human();
334 paramsA[jss::limit] = 1;
335 paramsA[jss::peer] = alice.human();
336 auto const linesA =
337 env.rpc("json", "account_lines", to_string(paramsA));
338 auto const& lineA = linesA[jss::result][jss::lines][0u];
339 BEAST_EXPECT(lineA[jss::freeze_peer].asBool() == true);
340 BEAST_EXPECT(lineA[jss::deep_freeze_peer].asBool() == true);
341 BEAST_EXPECT(lineA[jss::no_ripple_peer].asBool() == true);
342 BEAST_EXPECT(lineA[jss::authorized].asBool() == true);
343
344 // Continue from the returned marker to make sure that works.
345 BEAST_EXPECT(linesA[jss::result].isMember(jss::marker));
346 auto const marker = linesA[jss::result][jss::marker].asString();
347 Json::Value paramsB;
348 paramsB[jss::account] = gw2.human();
349 paramsB[jss::limit] = 25;
350 paramsB[jss::marker] = marker;
351 paramsB[jss::peer] = alice.human();
352 auto const linesB =
353 env.rpc("json", "account_lines", to_string(paramsB));
354 BEAST_EXPECT(linesB[jss::result][jss::lines].isArray());
355 BEAST_EXPECT(linesB[jss::result][jss::lines].size() == 25);
356 BEAST_EXPECT(!linesB[jss::result].isMember(jss::marker));
357 }
358 }
359
360 void
362 {
363 testcase("Entry pointed to by marker is not owned by account");
364 using namespace test::jtx;
365 Env env(*this);
366
367 // The goal of this test is observe account_lines RPC calls return an
368 // error message when the SLE pointed to by the marker is not owned by
369 // the Account being traversed.
370 //
371 // To start, we'll create an environment with some trust lines, offers
372 // and a signers list.
373 Account const alice{"alice"};
374 Account const becky{"becky"};
375 Account const gw1{"gw1"};
376 env.fund(XRP(10000), alice, becky, gw1);
377 env.close();
378
379 // Give alice a SignerList.
380 Account const bogie{"bogie"};
381 env(signers(alice, 2, {{bogie, 3}}));
382 env.close();
383
384 auto const EUR = gw1["EUR"];
385 env(trust(alice, EUR(200)));
386 env(trust(becky, EUR(200)));
387 env.close();
388
389 // Get all account objects for alice and verify that her
390 // signerlist is first. This is only a (reliable) coincidence of
391 // object naming. So if any of alice's objects are renamed this
392 // may fail.
393 Json::Value aliceObjectsParams;
394 aliceObjectsParams[jss::account] = alice.human();
395 aliceObjectsParams[jss::limit] = 10;
396 Json::Value const aliceObjects =
397 env.rpc("json", "account_objects", to_string(aliceObjectsParams));
398 Json::Value const& aliceSignerList =
399 aliceObjects[jss::result][jss::account_objects][0u];
400 if (!(aliceSignerList[sfLedgerEntryType.jsonName] == jss::SignerList))
401 {
402 fail(
403 "alice's account objects are misordered. "
404 "Please reorder the objects so the SignerList is first.",
405 __FILE__,
406 __LINE__);
407 return;
408 }
409
410 // Get account_lines for alice. Limit at 1, so we get a marker
411 // pointing to her SignerList.
412 Json::Value aliceLines1Params;
413 aliceLines1Params[jss::account] = alice.human();
414 aliceLines1Params[jss::limit] = 1;
415 auto const aliceLines1 =
416 env.rpc("json", "account_lines", to_string(aliceLines1Params));
417 BEAST_EXPECT(aliceLines1[jss::result].isMember(jss::marker));
418
419 // Verify that the marker points at the signer list.
420 std::string const aliceMarker =
421 aliceLines1[jss::result][jss::marker].asString();
422 std::string const markerIndex =
423 aliceMarker.substr(0, aliceMarker.find(','));
424 BEAST_EXPECT(markerIndex == aliceSignerList[jss::index].asString());
425
426 // When we fetch Alice's remaining lines we should find one and no more.
427 Json::Value aliceLines2Params;
428 aliceLines2Params[jss::account] = alice.human();
429 aliceLines2Params[jss::marker] = aliceMarker;
430 auto const aliceLines2 =
431 env.rpc("json", "account_lines", to_string(aliceLines2Params));
432 BEAST_EXPECT(aliceLines2[jss::result][jss::lines].size() == 1);
433 BEAST_EXPECT(!aliceLines2[jss::result].isMember(jss::marker));
434
435 // Get account lines for beckys account, using alices SignerList as a
436 // marker. This should cause an error.
437 Json::Value beckyLinesParams;
438 beckyLinesParams[jss::account] = becky.human();
439 beckyLinesParams[jss::marker] = aliceMarker;
440 auto const beckyLines =
441 env.rpc("json", "account_lines", to_string(beckyLinesParams));
442 BEAST_EXPECT(beckyLines[jss::result].isMember(jss::error_message));
443 }
444
445 void
447 {
448 testcase("Entry pointed to by marker is removed");
449 using namespace test::jtx;
450 Env env(*this);
451
452 // The goal here is to observe account_lines marker behavior if the
453 // entry pointed at by a returned marker is removed from the ledger.
454 //
455 // It isn't easy to explicitly delete a trust line, so we do so in a
456 // round-about fashion. It takes 4 actors:
457 // o Gateway gw1 issues USD
458 // o alice offers to buy 100 USD for 100 XRP.
459 // o becky offers to sell 100 USD for 100 XRP.
460 // There will now be an inferred trustline between alice and gw1.
461 // o alice pays her 100 USD to cheri.
462 // alice should now have no USD and no trustline to gw1.
463 Account const alice{"alice"};
464 Account const becky{"becky"};
465 Account const cheri{"cheri"};
466 Account const gw1{"gw1"};
467 Account const gw2{"gw2"};
468 env.fund(XRP(10000), alice, becky, cheri, gw1, gw2);
469 env.close();
470
471 auto const USD = gw1["USD"];
472 auto const AUD = gw1["AUD"];
473 auto const EUR = gw2["EUR"];
474 env(trust(alice, USD(200)));
475 env(trust(alice, AUD(200)));
476 env(trust(becky, EUR(200)));
477 env(trust(cheri, EUR(200)));
478 env.close();
479
480 // becky gets 100 USD from gw1.
481 env(pay(gw2, becky, EUR(100)));
482 env.close();
483
484 // alice offers to buy 100 EUR for 100 XRP.
485 env(offer(alice, EUR(100), XRP(100)));
486 env.close();
487
488 // becky offers to buy 100 XRP for 100 EUR.
489 env(offer(becky, XRP(100), EUR(100)));
490 env.close();
491
492 // Get account_lines for alice. Limit at 1, so we get a marker.
493 Json::Value linesBegParams;
494 linesBegParams[jss::account] = alice.human();
495 linesBegParams[jss::limit] = 2;
496 auto const linesBeg =
497 env.rpc("json", "account_lines", to_string(linesBegParams));
498 BEAST_EXPECT(
499 linesBeg[jss::result][jss::lines][0u][jss::currency] == "USD");
500 BEAST_EXPECT(linesBeg[jss::result].isMember(jss::marker));
501
502 // alice pays 100 EUR to cheri.
503 env(pay(alice, cheri, EUR(100)));
504 env.close();
505
506 // Since alice paid all her EUR to cheri, alice should no longer
507 // have a trust line to gw1. So the old marker should now be invalid.
508 Json::Value linesEndParams;
509 linesEndParams[jss::account] = alice.human();
510 linesEndParams[jss::marker] = linesBeg[jss::result][jss::marker];
511 auto const linesEnd =
512 env.rpc("json", "account_lines", to_string(linesEndParams));
513 BEAST_EXPECT(
514 linesEnd[jss::result][jss::error_message] ==
515 RPC::make_error(rpcINVALID_PARAMS)[jss::error_message]);
516 }
517
518 void
520 {
521 testcase("Marker can point to any appropriate ledger entry type");
522 using namespace test::jtx;
523 using namespace std::chrono_literals;
524 Env env(*this);
525
526 // The goal of this test is observe account_lines RPC calls return an
527 // error message when the SLE pointed to by the marker is not owned by
528 // the Account being traversed.
529 //
530 // To start, we'll create an environment with some trust lines, offers
531 // and a signers list.
532 Account const alice{"alice"};
533 Account const becky{"becky"};
534 Account const gw1{"gw1"};
535 env.fund(XRP(10000), alice, becky, gw1);
536 env.close();
537
538 auto payChan = [](Account const& account,
539 Account const& to,
540 STAmount const& amount,
541 NetClock::duration const& settleDelay,
542 PublicKey const& pk) {
543 Json::Value jv;
544 jv[jss::TransactionType] = jss::PaymentChannelCreate;
545 jv[jss::Account] = account.human();
546 jv[jss::Destination] = to.human();
547 jv[jss::Amount] = amount.getJson(JsonOptions::none);
548 jv["SettleDelay"] = settleDelay.count();
549 jv["PublicKey"] = strHex(pk.slice());
550 return jv;
551 };
552
553 // Test all available object types. Not all of these objects will be
554 // included in the search, nor found by `account_objects`. If that ever
555 // changes for any reason, this test will help catch that.
556 //
557 // SignerList, for alice
558 Account const bogie{"bogie"};
559 env(signers(alice, 2, {{bogie, 3}}));
560 env.close();
561
562 // SignerList, includes alice
563 env(signers(becky, 2, {{alice, 3}}));
564 env.close();
565
566 // Trust lines
567 auto const EUR = gw1["EUR"];
568 env(trust(alice, EUR(200)));
569 env(trust(becky, EUR(200)));
570 env.close();
571
572 // Escrow, in each direction
573 env(escrow::create(alice, becky, XRP(1000)),
574 escrow::finish_time(env.now() + 1s));
575 env(escrow::create(becky, alice, XRP(1000)),
576 escrow::finish_time(env.now() + 1s));
577
578 // Pay channels, in each direction
579 env(payChan(alice, becky, XRP(1000), 100s, alice.pk()));
580 env(payChan(becky, alice, XRP(1000), 100s, becky.pk()));
581
582 // Mint NFTs, for each account
583 uint256 const aliceNFtokenID =
584 token::getNextID(env, alice, 0, tfTransferable);
585 env(token::mint(alice, 0), txflags(tfTransferable));
586
587 uint256 const beckyNFtokenID =
588 token::getNextID(env, becky, 0, tfTransferable);
589 env(token::mint(becky, 0), txflags(tfTransferable));
590
591 // NFT Offers, for each other's NFTs
592 env(token::createOffer(alice, beckyNFtokenID, drops(1)),
593 token::owner(becky));
594 env(token::createOffer(becky, aliceNFtokenID, drops(1)),
595 token::owner(alice));
596
597 env(token::createOffer(becky, beckyNFtokenID, drops(1)),
598 txflags(tfSellNFToken),
599 token::destination(alice));
600 env(token::createOffer(alice, aliceNFtokenID, drops(1)),
601 txflags(tfSellNFToken),
602 token::destination(becky));
603
604 env(token::createOffer(gw1, beckyNFtokenID, drops(1)),
605 token::owner(becky),
606 token::destination(alice));
607 env(token::createOffer(gw1, aliceNFtokenID, drops(1)),
608 token::owner(alice),
609 token::destination(becky));
610
611 env(token::createOffer(becky, beckyNFtokenID, drops(1)),
612 txflags(tfSellNFToken));
613 env(token::createOffer(alice, aliceNFtokenID, drops(1)),
614 txflags(tfSellNFToken));
615
616 // Checks, in each direction
617 env(check::create(alice, becky, XRP(50)));
618 env(check::create(becky, alice, XRP(50)));
619
620 // Deposit preauth, in each direction
621 env(deposit::auth(alice, becky));
622 env(deposit::auth(becky, alice));
623
624 // Offers, one where alice is the owner, and one where alice is the
625 // issuer
626 auto const USDalice = alice["USD"];
627 env(offer(alice, EUR(10), XRP(100)));
628 env(offer(becky, USDalice(10), XRP(100)));
629
630 // Tickets
631 env(ticket::create(alice, 2));
632
633 // Add another trustline for good measure
634 auto const BTCbecky = becky["BTC"];
635 env(trust(alice, BTCbecky(200)));
636
637 env.close();
638
639 {
640 // Now make repeated calls to `account_lines` with a limit of 1.
641 // That should iterate all of alice's relevant objects, even though
642 // the list will be empty for most calls.
643 auto getNextLine = [](Env& env,
644 Account const& alice,
645 std::optional<std::string> const marker) {
647 params[jss::account] = alice.human();
648 params[jss::limit] = 1;
649 if (marker)
650 params[jss::marker] = *marker;
651
652 return env.rpc("json", "account_lines", to_string(params));
653 };
654
655 auto aliceLines = getNextLine(env, alice, std::nullopt);
656 constexpr std::size_t expectedIterations = 16;
657 constexpr std::size_t expectedLines = 2;
658 constexpr std::size_t expectedNFTs = 1;
659 std::size_t foundLines = 0;
660
661 auto hasMarker = [](auto const& aliceLines) {
662 return aliceLines[jss::result].isMember(jss::marker);
663 };
664 auto marker = [](auto const& aliceLines) {
665 return aliceLines[jss::result][jss::marker].asString();
666 };
667 auto checkLines = [](auto const& aliceLines) {
668 return aliceLines.isMember(jss::result) &&
669 !aliceLines[jss::result].isMember(jss::error_message) &&
670 aliceLines[jss::result].isMember(jss::lines) &&
671 aliceLines[jss::result][jss::lines].isArray() &&
672 aliceLines[jss::result][jss::lines].size() <= 1;
673 };
674
675 BEAST_EXPECT(hasMarker(aliceLines));
676 BEAST_EXPECT(checkLines(aliceLines));
677 BEAST_EXPECT(aliceLines[jss::result][jss::lines].size() == 0);
678
679 int iterations = 1;
680
681 while (hasMarker(aliceLines))
682 {
683 // Iterate through the markers
684 aliceLines = getNextLine(env, alice, marker(aliceLines));
685 BEAST_EXPECT(checkLines(aliceLines));
686 foundLines += aliceLines[jss::result][jss::lines].size();
687 ++iterations;
688 }
689 BEAST_EXPECT(expectedLines == foundLines);
690
691 Json::Value aliceObjectsParams2;
692 aliceObjectsParams2[jss::account] = alice.human();
693 aliceObjectsParams2[jss::limit] = 200;
694 Json::Value const aliceObjects = env.rpc(
695 "json", "account_objects", to_string(aliceObjectsParams2));
696 BEAST_EXPECT(aliceObjects.isMember(jss::result));
697 BEAST_EXPECT(
698 !aliceObjects[jss::result].isMember(jss::error_message));
699 BEAST_EXPECT(
700 aliceObjects[jss::result].isMember(jss::account_objects));
701 BEAST_EXPECT(
702 aliceObjects[jss::result][jss::account_objects].isArray());
703 // account_objects does not currently return NFTPages. If
704 // that ever changes, without also changing account_lines,
705 // this test will need to be updated.
706 BEAST_EXPECT(
707 aliceObjects[jss::result][jss::account_objects].size() ==
708 iterations + expectedNFTs);
709 // If ledger object association ever changes, for whatever
710 // reason, this test will need to be updated.
711 BEAST_EXPECTS(
712 iterations == expectedIterations, std::to_string(iterations));
713
714 // Get becky's objects just to confirm that they're symmetrical
715 Json::Value beckyObjectsParams;
716 beckyObjectsParams[jss::account] = becky.human();
717 beckyObjectsParams[jss::limit] = 200;
718 Json::Value const beckyObjects = env.rpc(
719 "json", "account_objects", to_string(beckyObjectsParams));
720 BEAST_EXPECT(beckyObjects.isMember(jss::result));
721 BEAST_EXPECT(
722 !beckyObjects[jss::result].isMember(jss::error_message));
723 BEAST_EXPECT(
724 beckyObjects[jss::result].isMember(jss::account_objects));
725 BEAST_EXPECT(
726 beckyObjects[jss::result][jss::account_objects].isArray());
727 // becky should have the same number of objects as alice, except the
728 // 2 tickets that only alice created.
729 BEAST_EXPECT(
730 beckyObjects[jss::result][jss::account_objects].size() ==
731 aliceObjects[jss::result][jss::account_objects].size() - 2);
732 }
733 }
734
735 // test API V2
736 void
738 {
739 testcase("V2: account_lines");
740
741 using namespace test::jtx;
742 Env env(*this);
743 {
744 // account_lines with mal-formed json2 (missing id field).
745 Json::Value request;
746 request[jss::method] = "account_lines";
747 request[jss::jsonrpc] = "2.0";
748 request[jss::ripplerpc] = "2.0";
749 auto const lines = env.rpc("json2", to_string(request));
750 BEAST_EXPECT(
751 lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
752 BEAST_EXPECT(
753 lines.isMember(jss::ripplerpc) &&
754 lines[jss::ripplerpc] == "2.0");
755 }
756 {
757 // account_lines with no account.
758 Json::Value request;
759 request[jss::method] = "account_lines";
760 request[jss::jsonrpc] = "2.0";
761 request[jss::ripplerpc] = "2.0";
762 request[jss::id] = 5;
763 auto const lines = env.rpc("json2", to_string(request));
764 BEAST_EXPECT(
765 lines[jss::error][jss::message] ==
766 RPC::missing_field_error(jss::account)[jss::error_message]);
767 BEAST_EXPECT(
768 lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
769 BEAST_EXPECT(
770 lines.isMember(jss::ripplerpc) &&
771 lines[jss::ripplerpc] == "2.0");
772 BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
773 }
774 {
775 // account_lines with a malformed account.
776 Json::Value params;
777 params[jss::account] =
778 "n9MJkEKHDhy5eTLuHUQeAAjo382frHNbFK4C8hcwN4nwM2SrLdBj";
779 Json::Value request;
780 request[jss::method] = "account_lines";
781 request[jss::jsonrpc] = "2.0";
782 request[jss::ripplerpc] = "2.0";
783 request[jss::id] = 5;
784 request[jss::params] = params;
785 auto const lines = env.rpc("json2", to_string(request));
786 BEAST_EXPECT(
787 lines[jss::error][jss::message] ==
788 RPC::make_error(rpcACT_MALFORMED)[jss::error_message]);
789 BEAST_EXPECT(
790 lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
791 BEAST_EXPECT(
792 lines.isMember(jss::ripplerpc) &&
793 lines[jss::ripplerpc] == "2.0");
794 BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
795 }
796 Account const alice{"alice"};
797 {
798 // account_lines on an unfunded account.
799 Json::Value params;
800 params[jss::account] = alice.human();
801 Json::Value request;
802 request[jss::method] = "account_lines";
803 request[jss::jsonrpc] = "2.0";
804 request[jss::ripplerpc] = "2.0";
805 request[jss::id] = 5;
806 request[jss::params] = params;
807 auto const lines = env.rpc("json2", to_string(request));
808 BEAST_EXPECT(
809 lines[jss::error][jss::message] ==
810 RPC::make_error(rpcACT_NOT_FOUND)[jss::error_message]);
811 BEAST_EXPECT(
812 lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
813 BEAST_EXPECT(
814 lines.isMember(jss::ripplerpc) &&
815 lines[jss::ripplerpc] == "2.0");
816 BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
817 }
818 env.fund(XRP(10000), alice);
819 env.close();
820 LedgerHeader const ledger3Info = env.closed()->header();
821 BEAST_EXPECT(ledger3Info.seq == 3);
822
823 {
824 // alice is funded but has no lines. An empty array is returned.
825 Json::Value params;
826 params[jss::account] = alice.human();
827 Json::Value request;
828 request[jss::method] = "account_lines";
829 request[jss::jsonrpc] = "2.0";
830 request[jss::ripplerpc] = "2.0";
831 request[jss::id] = 5;
832 request[jss::params] = params;
833 auto const lines = env.rpc("json2", to_string(request));
834 BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
835 BEAST_EXPECT(lines[jss::result][jss::lines].size() == 0);
836 BEAST_EXPECT(
837 lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
838 BEAST_EXPECT(
839 lines.isMember(jss::ripplerpc) &&
840 lines[jss::ripplerpc] == "2.0");
841 BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
842 }
843 {
844 // Specify a ledger that doesn't exist.
845 Json::Value params;
846 params[jss::account] = alice.human();
847 params[jss::ledger_index] = "nonsense";
848 Json::Value request;
849 request[jss::method] = "account_lines";
850 request[jss::jsonrpc] = "2.0";
851 request[jss::ripplerpc] = "2.0";
852 request[jss::id] = 5;
853 request[jss::params] = params;
854 auto const lines = env.rpc("json2", to_string(request));
855 BEAST_EXPECT(
856 lines[jss::error][jss::message] ==
857 "Invalid field 'ledger_index', not string or number.");
858 BEAST_EXPECT(
859 lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
860 BEAST_EXPECT(
861 lines.isMember(jss::ripplerpc) &&
862 lines[jss::ripplerpc] == "2.0");
863 BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
864 }
865 {
866 // Specify a different ledger that doesn't exist.
867 Json::Value params;
868 params[jss::account] = alice.human();
869 params[jss::ledger_index] = 50000;
870 Json::Value request;
871 request[jss::method] = "account_lines";
872 request[jss::jsonrpc] = "2.0";
873 request[jss::ripplerpc] = "2.0";
874 request[jss::id] = 5;
875 request[jss::params] = params;
876 auto const lines = env.rpc("json2", to_string(request));
877 BEAST_EXPECT(lines[jss::error][jss::message] == "ledgerNotFound");
878 BEAST_EXPECT(
879 lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
880 BEAST_EXPECT(
881 lines.isMember(jss::ripplerpc) &&
882 lines[jss::ripplerpc] == "2.0");
883 BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
884 }
885 // Create trust lines to share with alice.
886 Account const gw1{"gw1"};
887 env.fund(XRP(10000), gw1);
888 std::vector<IOU> gw1Currencies;
889
890 for (char c = 0; c <= ('Z' - 'A'); ++c)
891 {
892 // gw1 currencies have names "YAA" -> "YAZ".
893 gw1Currencies.push_back(
894 gw1[std::string("YA") + static_cast<char>('A' + c)]);
895 IOU const& gw1Currency = gw1Currencies.back();
896
897 // Establish trust lines.
898 env(trust(alice, gw1Currency(100 + c)));
899 env(pay(gw1, alice, gw1Currency(50 + c)));
900 }
901 env.close();
902 LedgerHeader const ledger4Info = env.closed()->header();
903 BEAST_EXPECT(ledger4Info.seq == 4);
904
905 // Add another set of trust lines in another ledger so we can see
906 // differences in historic ledgers.
907 Account const gw2{"gw2"};
908 env.fund(XRP(10000), gw2);
909
910 // gw2 requires authorization.
911 env(fset(gw2, asfRequireAuth));
912 env.close();
913 std::vector<IOU> gw2Currencies;
914
915 for (char c = 0; c <= ('Z' - 'A'); ++c)
916 {
917 // gw2 currencies have names "ZAA" -> "ZAZ".
918 gw2Currencies.push_back(
919 gw2[std::string("ZA") + static_cast<char>('A' + c)]);
920 IOU const& gw2Currency = gw2Currencies.back();
921
922 // Establish trust lines.
923 env(trust(alice, gw2Currency(200 + c)));
924 env(trust(gw2, gw2Currency(0), alice, tfSetfAuth));
925 env.close();
926 env(pay(gw2, alice, gw2Currency(100 + c)));
927 env.close();
928
929 // Set flags on gw2 trust lines so we can look for them.
930 env(trust(
931 alice,
932 gw2Currency(0),
933 gw2,
935 }
936 env.close();
937 LedgerHeader const ledger58Info = env.closed()->header();
938 BEAST_EXPECT(ledger58Info.seq == 58);
939
940 // A re-usable test for historic ledgers.
941 auto testAccountLinesHistory = [this, &env](
942 Account const& account,
943 LedgerHeader const& info,
944 int count) {
945 // Get account_lines by ledger index.
946 Json::Value paramsSeq;
947 paramsSeq[jss::account] = account.human();
948 paramsSeq[jss::ledger_index] = info.seq;
949 Json::Value requestSeq;
950 requestSeq[jss::method] = "account_lines";
951 requestSeq[jss::jsonrpc] = "2.0";
952 requestSeq[jss::ripplerpc] = "2.0";
953 requestSeq[jss::id] = 5;
954 requestSeq[jss::params] = paramsSeq;
955 auto const linesSeq = env.rpc("json2", to_string(requestSeq));
956 BEAST_EXPECT(linesSeq[jss::result][jss::lines].isArray());
957 BEAST_EXPECT(linesSeq[jss::result][jss::lines].size() == count);
958 BEAST_EXPECT(
959 linesSeq.isMember(jss::jsonrpc) &&
960 linesSeq[jss::jsonrpc] == "2.0");
961 BEAST_EXPECT(
962 linesSeq.isMember(jss::ripplerpc) &&
963 linesSeq[jss::ripplerpc] == "2.0");
964 BEAST_EXPECT(linesSeq.isMember(jss::id) && linesSeq[jss::id] == 5);
965
966 // Get account_lines by ledger hash.
967 Json::Value paramsHash;
968 paramsHash[jss::account] = account.human();
969 paramsHash[jss::ledger_hash] = to_string(info.hash);
970 Json::Value requestHash;
971 requestHash[jss::method] = "account_lines";
972 requestHash[jss::jsonrpc] = "2.0";
973 requestHash[jss::ripplerpc] = "2.0";
974 requestHash[jss::id] = 5;
975 requestHash[jss::params] = paramsHash;
976 auto const linesHash = env.rpc("json2", to_string(requestHash));
977 BEAST_EXPECT(linesHash[jss::result][jss::lines].isArray());
978 BEAST_EXPECT(linesHash[jss::result][jss::lines].size() == count);
979 BEAST_EXPECT(
980 linesHash.isMember(jss::jsonrpc) &&
981 linesHash[jss::jsonrpc] == "2.0");
982 BEAST_EXPECT(
983 linesHash.isMember(jss::ripplerpc) &&
984 linesHash[jss::ripplerpc] == "2.0");
985 BEAST_EXPECT(
986 linesHash.isMember(jss::id) && linesHash[jss::id] == 5);
987 };
988
989 // Alice should have no trust lines in ledger 3.
990 testAccountLinesHistory(alice, ledger3Info, 0);
991
992 // Alice should have 26 trust lines in ledger 4.
993 testAccountLinesHistory(alice, ledger4Info, 26);
994
995 // Alice should have 52 trust lines in ledger 58.
996 testAccountLinesHistory(alice, ledger58Info, 52);
997
998 {
999 // Surprisingly, it's valid to specify both index and hash, in
1000 // which case the hash wins.
1001 Json::Value params;
1002 params[jss::account] = alice.human();
1003 params[jss::ledger_hash] = to_string(ledger4Info.hash);
1004 params[jss::ledger_index] = ledger58Info.seq;
1005 Json::Value request;
1006 request[jss::method] = "account_lines";
1007 request[jss::jsonrpc] = "2.0";
1008 request[jss::ripplerpc] = "2.0";
1009 request[jss::id] = 5;
1010 request[jss::params] = params;
1011 auto const lines = env.rpc("json2", to_string(request));
1012 BEAST_EXPECT(lines[jss::error][jss::error] == "invalidParams");
1013 BEAST_EXPECT(
1014 lines[jss::error][jss::message] ==
1015 "Exactly one of 'ledger_hash' or 'ledger_index' can be "
1016 "specified.");
1017 BEAST_EXPECT(
1018 lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
1019 BEAST_EXPECT(
1020 lines.isMember(jss::ripplerpc) &&
1021 lines[jss::ripplerpc] == "2.0");
1022 BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
1023 }
1024 {
1025 // alice should have 52 trust lines in the current ledger.
1026 Json::Value params;
1027 params[jss::account] = alice.human();
1028 Json::Value request;
1029 request[jss::method] = "account_lines";
1030 request[jss::jsonrpc] = "2.0";
1031 request[jss::ripplerpc] = "2.0";
1032 request[jss::id] = 5;
1033 request[jss::params] = params;
1034 auto const lines = env.rpc("json2", to_string(request));
1035 BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
1036 BEAST_EXPECT(lines[jss::result][jss::lines].size() == 52);
1037 BEAST_EXPECT(
1038 lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
1039 BEAST_EXPECT(
1040 lines.isMember(jss::ripplerpc) &&
1041 lines[jss::ripplerpc] == "2.0");
1042 BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
1043 }
1044 {
1045 // alice should have 26 trust lines with gw1.
1046 Json::Value params;
1047 params[jss::account] = alice.human();
1048 params[jss::peer] = gw1.human();
1049 Json::Value request;
1050 request[jss::method] = "account_lines";
1051 request[jss::jsonrpc] = "2.0";
1052 request[jss::ripplerpc] = "2.0";
1053 request[jss::id] = 5;
1054 request[jss::params] = params;
1055 auto const lines = env.rpc("json2", to_string(request));
1056 BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
1057 BEAST_EXPECT(lines[jss::result][jss::lines].size() == 26);
1058 BEAST_EXPECT(
1059 lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
1060 BEAST_EXPECT(
1061 lines.isMember(jss::ripplerpc) &&
1062 lines[jss::ripplerpc] == "2.0");
1063 BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
1064 }
1065 {
1066 // Use a malformed peer.
1067 Json::Value params;
1068 params[jss::account] = alice.human();
1069 params[jss::peer] =
1070 "n9MJkEKHDhy5eTLuHUQeAAjo382frHNbFK4C8hcwN4nwM2SrLdBj";
1071 Json::Value request;
1072 request[jss::method] = "account_lines";
1073 request[jss::jsonrpc] = "2.0";
1074 request[jss::ripplerpc] = "2.0";
1075 request[jss::id] = 5;
1076 request[jss::params] = params;
1077 auto const lines = env.rpc("json2", to_string(request));
1078 BEAST_EXPECT(
1079 lines[jss::error][jss::message] ==
1080 RPC::make_error(rpcACT_MALFORMED)[jss::error_message]);
1081 BEAST_EXPECT(
1082 lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
1083 BEAST_EXPECT(
1084 lines.isMember(jss::ripplerpc) &&
1085 lines[jss::ripplerpc] == "2.0");
1086 BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
1087 }
1088 {
1089 // A negative limit should fail.
1090 Json::Value params;
1091 params[jss::account] = alice.human();
1092 params[jss::limit] = -1;
1093 Json::Value request;
1094 request[jss::method] = "account_lines";
1095 request[jss::jsonrpc] = "2.0";
1096 request[jss::ripplerpc] = "2.0";
1097 request[jss::id] = 5;
1098 request[jss::params] = params;
1099 auto const lines = env.rpc("json2", to_string(request));
1100 BEAST_EXPECT(
1101 lines[jss::error][jss::message] ==
1102 RPC::expected_field_message(jss::limit, "unsigned integer"));
1103 BEAST_EXPECT(
1104 lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
1105 BEAST_EXPECT(
1106 lines.isMember(jss::ripplerpc) &&
1107 lines[jss::ripplerpc] == "2.0");
1108 BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
1109 }
1110 {
1111 // Limit the response to 1 trust line.
1112 Json::Value paramsA;
1113 paramsA[jss::account] = alice.human();
1114 paramsA[jss::limit] = 1;
1115 Json::Value requestA;
1116 requestA[jss::method] = "account_lines";
1117 requestA[jss::jsonrpc] = "2.0";
1118 requestA[jss::ripplerpc] = "2.0";
1119 requestA[jss::id] = 5;
1120 requestA[jss::params] = paramsA;
1121 auto const linesA = env.rpc("json2", to_string(requestA));
1122 BEAST_EXPECT(linesA[jss::result][jss::lines].isArray());
1123 BEAST_EXPECT(linesA[jss::result][jss::lines].size() == 1);
1124 BEAST_EXPECT(
1125 linesA.isMember(jss::jsonrpc) && linesA[jss::jsonrpc] == "2.0");
1126 BEAST_EXPECT(
1127 linesA.isMember(jss::ripplerpc) &&
1128 linesA[jss::ripplerpc] == "2.0");
1129 BEAST_EXPECT(linesA.isMember(jss::id) && linesA[jss::id] == 5);
1130
1131 // Pick up from where the marker left off. We should get 51.
1132 auto marker = linesA[jss::result][jss::marker].asString();
1133 Json::Value paramsB;
1134 paramsB[jss::account] = alice.human();
1135 paramsB[jss::marker] = marker;
1136 Json::Value requestB;
1137 requestB[jss::method] = "account_lines";
1138 requestB[jss::jsonrpc] = "2.0";
1139 requestB[jss::ripplerpc] = "2.0";
1140 requestB[jss::id] = 5;
1141 requestB[jss::params] = paramsB;
1142 auto const linesB = env.rpc("json2", to_string(requestB));
1143 BEAST_EXPECT(linesB[jss::result][jss::lines].isArray());
1144 BEAST_EXPECT(linesB[jss::result][jss::lines].size() == 51);
1145 BEAST_EXPECT(
1146 linesB.isMember(jss::jsonrpc) && linesB[jss::jsonrpc] == "2.0");
1147 BEAST_EXPECT(
1148 linesB.isMember(jss::ripplerpc) &&
1149 linesB[jss::ripplerpc] == "2.0");
1150 BEAST_EXPECT(linesB.isMember(jss::id) && linesB[jss::id] == 5);
1151
1152 // Go again from where the marker left off, but set a limit of 3.
1153 Json::Value paramsC;
1154 paramsC[jss::account] = alice.human();
1155 paramsC[jss::limit] = 3;
1156 paramsC[jss::marker] = marker;
1157 Json::Value requestC;
1158 requestC[jss::method] = "account_lines";
1159 requestC[jss::jsonrpc] = "2.0";
1160 requestC[jss::ripplerpc] = "2.0";
1161 requestC[jss::id] = 5;
1162 requestC[jss::params] = paramsC;
1163 auto const linesC = env.rpc("json2", to_string(requestC));
1164 BEAST_EXPECT(linesC[jss::result][jss::lines].isArray());
1165 BEAST_EXPECT(linesC[jss::result][jss::lines].size() == 3);
1166 BEAST_EXPECT(
1167 linesC.isMember(jss::jsonrpc) && linesC[jss::jsonrpc] == "2.0");
1168 BEAST_EXPECT(
1169 linesC.isMember(jss::ripplerpc) &&
1170 linesC[jss::ripplerpc] == "2.0");
1171 BEAST_EXPECT(linesC.isMember(jss::id) && linesC[jss::id] == 5);
1172
1173 // Mess with the marker so it becomes bad and check for the error.
1174 marker[5] = marker[5] == '7' ? '8' : '7';
1175 Json::Value paramsD;
1176 paramsD[jss::account] = alice.human();
1177 paramsD[jss::marker] = marker;
1178 Json::Value requestD;
1179 requestD[jss::method] = "account_lines";
1180 requestD[jss::jsonrpc] = "2.0";
1181 requestD[jss::ripplerpc] = "2.0";
1182 requestD[jss::id] = 5;
1183 requestD[jss::params] = paramsD;
1184 auto const linesD = env.rpc("json2", to_string(requestD));
1185 BEAST_EXPECT(
1186 linesD[jss::error][jss::message] ==
1187 RPC::make_error(rpcINVALID_PARAMS)[jss::error_message]);
1188 BEAST_EXPECT(
1189 linesD.isMember(jss::jsonrpc) && linesD[jss::jsonrpc] == "2.0");
1190 BEAST_EXPECT(
1191 linesD.isMember(jss::ripplerpc) &&
1192 linesD[jss::ripplerpc] == "2.0");
1193 BEAST_EXPECT(linesD.isMember(jss::id) && linesD[jss::id] == 5);
1194 }
1195 {
1196 // A non-string marker should also fail.
1197 Json::Value params;
1198 params[jss::account] = alice.human();
1199 params[jss::marker] = true;
1200 Json::Value request;
1201 request[jss::method] = "account_lines";
1202 request[jss::jsonrpc] = "2.0";
1203 request[jss::ripplerpc] = "2.0";
1204 request[jss::id] = 5;
1205 request[jss::params] = params;
1206 auto const lines = env.rpc("json2", to_string(request));
1207 BEAST_EXPECT(
1208 lines[jss::error][jss::message] ==
1209 RPC::expected_field_message(jss::marker, "string"));
1210 BEAST_EXPECT(
1211 lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
1212 BEAST_EXPECT(
1213 lines.isMember(jss::ripplerpc) &&
1214 lines[jss::ripplerpc] == "2.0");
1215 BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
1216 }
1217 {
1218 // Check that the flags we expect from alice to gw2 are present.
1219 Json::Value params;
1220 params[jss::account] = alice.human();
1221 params[jss::limit] = 10;
1222 params[jss::peer] = gw2.human();
1223 Json::Value request;
1224 request[jss::method] = "account_lines";
1225 request[jss::jsonrpc] = "2.0";
1226 request[jss::ripplerpc] = "2.0";
1227 request[jss::id] = 5;
1228 request[jss::params] = params;
1229 auto const lines = env.rpc("json2", to_string(request));
1230 auto const& line = lines[jss::result][jss::lines][0u];
1231 BEAST_EXPECT(line[jss::freeze].asBool() == true);
1232 BEAST_EXPECT(line[jss::deep_freeze].asBool() == true);
1233 BEAST_EXPECT(line[jss::no_ripple].asBool() == true);
1234 BEAST_EXPECT(line[jss::peer_authorized].asBool() == true);
1235 BEAST_EXPECT(
1236 lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
1237 BEAST_EXPECT(
1238 lines.isMember(jss::ripplerpc) &&
1239 lines[jss::ripplerpc] == "2.0");
1240 BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
1241 }
1242 {
1243 // Check that the flags we expect from gw2 to alice are present.
1244 Json::Value paramsA;
1245 paramsA[jss::account] = gw2.human();
1246 paramsA[jss::limit] = 1;
1247 paramsA[jss::peer] = alice.human();
1248 Json::Value requestA;
1249 requestA[jss::method] = "account_lines";
1250 requestA[jss::jsonrpc] = "2.0";
1251 requestA[jss::ripplerpc] = "2.0";
1252 requestA[jss::id] = 5;
1253 requestA[jss::params] = paramsA;
1254 auto const linesA = env.rpc("json2", to_string(requestA));
1255 auto const& lineA = linesA[jss::result][jss::lines][0u];
1256 BEAST_EXPECT(lineA[jss::freeze_peer].asBool() == true);
1257 BEAST_EXPECT(lineA[jss::deep_freeze_peer].asBool() == true);
1258 BEAST_EXPECT(lineA[jss::no_ripple_peer].asBool() == true);
1259 BEAST_EXPECT(lineA[jss::authorized].asBool() == true);
1260 BEAST_EXPECT(
1261 linesA.isMember(jss::jsonrpc) && linesA[jss::jsonrpc] == "2.0");
1262 BEAST_EXPECT(
1263 linesA.isMember(jss::ripplerpc) &&
1264 linesA[jss::ripplerpc] == "2.0");
1265 BEAST_EXPECT(linesA.isMember(jss::id) && linesA[jss::id] == 5);
1266
1267 // Continue from the returned marker to make sure that works.
1268 BEAST_EXPECT(linesA[jss::result].isMember(jss::marker));
1269 auto const marker = linesA[jss::result][jss::marker].asString();
1270 Json::Value paramsB;
1271 paramsB[jss::account] = gw2.human();
1272 paramsB[jss::limit] = 25;
1273 paramsB[jss::marker] = marker;
1274 paramsB[jss::peer] = alice.human();
1275 Json::Value requestB;
1276 requestB[jss::method] = "account_lines";
1277 requestB[jss::jsonrpc] = "2.0";
1278 requestB[jss::ripplerpc] = "2.0";
1279 requestB[jss::id] = 5;
1280 requestB[jss::params] = paramsB;
1281 auto const linesB = env.rpc("json2", to_string(requestB));
1282 BEAST_EXPECT(linesB[jss::result][jss::lines].isArray());
1283 BEAST_EXPECT(linesB[jss::result][jss::lines].size() == 25);
1284 BEAST_EXPECT(!linesB[jss::result].isMember(jss::marker));
1285 BEAST_EXPECT(
1286 linesB.isMember(jss::jsonrpc) && linesB[jss::jsonrpc] == "2.0");
1287 BEAST_EXPECT(
1288 linesB.isMember(jss::ripplerpc) &&
1289 linesB[jss::ripplerpc] == "2.0");
1290 BEAST_EXPECT(linesB.isMember(jss::id) && linesB[jss::id] == 5);
1291 }
1292 }
1293
1294 // test API V2
1295 void
1297 {
1298 testcase("V2: account_lines with removed marker");
1299
1300 using namespace test::jtx;
1301 Env env(*this);
1302
1303 // The goal here is to observe account_lines marker behavior if the
1304 // entry pointed at by a returned marker is removed from the ledger.
1305 //
1306 // It isn't easy to explicitly delete a trust line, so we do so in a
1307 // round-about fashion. It takes 4 actors:
1308 // o Gateway gw1 issues EUR
1309 // o alice offers to buy 100 EUR for 100 XRP.
1310 // o becky offers to sell 100 EUR for 100 XRP.
1311 // There will now be an inferred trustline between alice and gw2.
1312 // o alice pays her 100 EUR to cheri.
1313 // alice should now have no EUR and no trustline to gw2.
1314 Account const alice{"alice"};
1315 Account const becky{"becky"};
1316 Account const cheri{"cheri"};
1317 Account const gw1{"gw1"};
1318 Account const gw2{"gw2"};
1319 env.fund(XRP(10000), alice, becky, cheri, gw1, gw2);
1320 env.close();
1321
1322 auto const USD = gw1["USD"];
1323 auto const AUD = gw1["AUD"];
1324 auto const EUR = gw2["EUR"];
1325 env(trust(alice, USD(200)));
1326 env(trust(alice, AUD(200)));
1327 env(trust(becky, EUR(200)));
1328 env(trust(cheri, EUR(200)));
1329 env.close();
1330
1331 // becky gets 100 EUR from gw1.
1332 env(pay(gw2, becky, EUR(100)));
1333 env.close();
1334
1335 // alice offers to buy 100 EUR for 100 XRP.
1336 env(offer(alice, EUR(100), XRP(100)));
1337 env.close();
1338
1339 // becky offers to buy 100 XRP for 100 EUR.
1340 env(offer(becky, XRP(100), EUR(100)));
1341 env.close();
1342
1343 // Get account_lines for alice. Limit at 1, so we get a marker.
1344 Json::Value linesBegParams;
1345 linesBegParams[jss::account] = alice.human();
1346 linesBegParams[jss::limit] = 2;
1347 Json::Value linesBegRequest;
1348 linesBegRequest[jss::method] = "account_lines";
1349 linesBegRequest[jss::jsonrpc] = "2.0";
1350 linesBegRequest[jss::ripplerpc] = "2.0";
1351 linesBegRequest[jss::id] = 5;
1352 linesBegRequest[jss::params] = linesBegParams;
1353 auto const linesBeg = env.rpc("json2", to_string(linesBegRequest));
1354 BEAST_EXPECT(
1355 linesBeg[jss::result][jss::lines][0u][jss::currency] == "USD");
1356 BEAST_EXPECT(linesBeg[jss::result].isMember(jss::marker));
1357 BEAST_EXPECT(
1358 linesBeg.isMember(jss::jsonrpc) && linesBeg[jss::jsonrpc] == "2.0");
1359 BEAST_EXPECT(
1360 linesBeg.isMember(jss::ripplerpc) &&
1361 linesBeg[jss::ripplerpc] == "2.0");
1362 BEAST_EXPECT(linesBeg.isMember(jss::id) && linesBeg[jss::id] == 5);
1363
1364 // alice pays 100 USD to cheri.
1365 env(pay(alice, cheri, EUR(100)));
1366 env.close();
1367
1368 // Since alice paid all her EUR to cheri, alice should no longer
1369 // have a trust line to gw1. So the old marker should now be invalid.
1370 Json::Value linesEndParams;
1371 linesEndParams[jss::account] = alice.human();
1372 linesEndParams[jss::marker] = linesBeg[jss::result][jss::marker];
1373 Json::Value linesEndRequest;
1374 linesEndRequest[jss::method] = "account_lines";
1375 linesEndRequest[jss::jsonrpc] = "2.0";
1376 linesEndRequest[jss::ripplerpc] = "2.0";
1377 linesEndRequest[jss::id] = 5;
1378 linesEndRequest[jss::params] = linesEndParams;
1379 auto const linesEnd = env.rpc("json2", to_string(linesEndRequest));
1380 BEAST_EXPECT(
1381 linesEnd[jss::error][jss::message] ==
1382 RPC::make_error(rpcINVALID_PARAMS)[jss::error_message]);
1383 BEAST_EXPECT(
1384 linesEnd.isMember(jss::jsonrpc) && linesEnd[jss::jsonrpc] == "2.0");
1385 BEAST_EXPECT(
1386 linesEnd.isMember(jss::ripplerpc) &&
1387 linesEnd[jss::ripplerpc] == "2.0");
1388 BEAST_EXPECT(linesEnd.isMember(jss::id) && linesEnd[jss::id] == 5);
1389 }
1390
1391 void
1401};
1402
1403BEAST_DEFINE_TESTSUITE(AccountLines, rpc, xrpl);
1404
1405} // namespace RPC
1406} // namespace xrpl
T back(T... args)
Represents a JSON value.
Definition json_value.h:131
bool isMember(char const *key) const
Return true if the object has a member named key.
A testsuite class.
Definition suite.h:52
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:152
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:530
A public key.
Definition PublicKey.h:43
void run() override
Runs the suite.
T find(T... args)
T is_same_v
@ nullValue
'null' value
Definition json_value.h:20
@ arrayValue
array value (ordered list)
Definition json_value.h:26
@ objectValue
object value (collection of name/value pairs).
Definition json_value.h:27
std::string expected_field_message(std::string const &name, std::string const &type)
Definition ErrorCodes.h:296
Json::Value missing_field_error(std::string const &name)
Definition ErrorCodes.h:242
Json::Value make_error(error_code_i code)
Returns a new json object that reflects the error code.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
constexpr std::uint32_t const tfTransferable
Definition TxFlags.h:123
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:11
constexpr std::uint32_t tfSetNoRipple
Definition TxFlags.h:97
constexpr std::uint32_t tfSetDeepFreeze
Definition TxFlags.h:101
constexpr std::uint32_t tfSetfAuth
Definition TxFlags.h:96
constexpr std::uint32_t asfRequireAuth
Definition TxFlags.h:59
constexpr std::uint32_t const tfSellNFToken
Definition TxFlags.h:211
constexpr std::uint32_t tfSetFreeze
Definition TxFlags.h:99
@ rpcACT_NOT_FOUND
Definition ErrorCodes.h:51
@ rpcINVALID_PARAMS
Definition ErrorCodes.h:65
@ rpcACT_MALFORMED
Definition ErrorCodes.h:71
T push_back(T... args)
Information about the notional ledger backing the view.
T substr(T... args)
T to_string(T... args)