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