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