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