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