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": 1, )"
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  testAccountLineDelete()
368  {
369  testcase("Entry pointed to by marker is removed");
370  using namespace test::jtx;
371  Env env(*this);
372 
373  // The goal here is to observe account_lines marker behavior if the
374  // entry pointed at by a returned marker is removed from the ledger.
375  //
376  // It isn't easy to explicitly delete a trust line, so we do so in a
377  // round-about fashion. It takes 4 actors:
378  // o Gateway gw1 issues USD
379  // o alice offers to buy 100 USD for 100 XRP.
380  // o becky offers to sell 100 USD for 100 XRP.
381  // There will now be an inferred trustline between alice and gw1.
382  // o alice pays her 100 USD to cheri.
383  // alice should now have no USD and no trustline to gw1.
384  Account const alice{"alice"};
385  Account const becky{"becky"};
386  Account const cheri{"cheri"};
387  Account const gw1{"gw1"};
388  Account const gw2{"gw2"};
389  env.fund(XRP(10000), alice, becky, cheri, gw1, gw2);
390  env.close();
391 
392  auto const USD = gw1["USD"];
393  auto const EUR = gw2["EUR"];
394  env(trust(alice, USD(200)));
395  env(trust(becky, EUR(200)));
396  env(trust(cheri, EUR(200)));
397  env.close();
398 
399  // becky gets 100 USD from gw1.
400  env(pay(gw2, becky, EUR(100)));
401  env.close();
402 
403  // alice offers to buy 100 EUR for 100 XRP.
404  env(offer(alice, EUR(100), XRP(100)));
405  env.close();
406 
407  // becky offers to buy 100 XRP for 100 EUR.
408  env(offer(becky, XRP(100), EUR(100)));
409  env.close();
410 
411  // Get account_lines for alice. Limit at 1, so we get a marker.
412  auto const linesBeg = env.rpc(
413  "json",
414  "account_lines",
415  R"({"account": ")" + alice.human() +
416  R"(", )"
417  R"("limit": 1})");
418  BEAST_EXPECT(
419  linesBeg[jss::result][jss::lines][0u][jss::currency] == "USD");
420  BEAST_EXPECT(linesBeg[jss::result].isMember(jss::marker));
421 
422  // alice pays 100 EUR to cheri.
423  env(pay(alice, cheri, EUR(100)));
424  env.close();
425 
426  // Since alice paid all her EUR to cheri, alice should no longer
427  // have a trust line to gw1. So the old marker should now be invalid.
428  auto const linesEnd = env.rpc(
429  "json",
430  "account_lines",
431  R"({"account": ")" + alice.human() +
432  R"(", )"
433  R"("marker": ")" +
434  linesBeg[jss::result][jss::marker].asString() + R"("})");
435  BEAST_EXPECT(
436  linesEnd[jss::result][jss::error_message] ==
437  RPC::make_error(rpcINVALID_PARAMS)[jss::error_message]);
438  }
439 
440  // test API V2
441  void
442  testAccountLines2()
443  {
444  testcase("V2: acccount_lines");
445 
446  using namespace test::jtx;
447  Env env(*this);
448  {
449  // account_lines with mal-formed json2 (missing id field).
450  auto const lines = env.rpc(
451  "json2",
452  "{ "
453  R"("method" : "account_lines",)"
454  R"("jsonrpc" : "2.0",)"
455  R"("ripplerpc" : "2.0")"
456  " }");
457  BEAST_EXPECT(
458  lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
459  BEAST_EXPECT(
460  lines.isMember(jss::ripplerpc) &&
461  lines[jss::ripplerpc] == "2.0");
462  }
463  {
464  // account_lines with no account.
465  auto const lines = env.rpc(
466  "json2",
467  "{ "
468  R"("method" : "account_lines",)"
469  R"("jsonrpc" : "2.0",)"
470  R"("ripplerpc" : "2.0",)"
471  R"("id" : 5)"
472  " }");
473  BEAST_EXPECT(
474  lines[jss::error][jss::message] ==
475  RPC::missing_field_error(jss::account)[jss::error_message]);
476  BEAST_EXPECT(
477  lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
478  BEAST_EXPECT(
479  lines.isMember(jss::ripplerpc) &&
480  lines[jss::ripplerpc] == "2.0");
481  BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
482  }
483  {
484  // account_lines with a malformed account.
485  auto const lines = env.rpc(
486  "json2",
487  "{ "
488  R"("method" : "account_lines",)"
489  R"("jsonrpc" : "2.0",)"
490  R"("ripplerpc" : "2.0",)"
491  R"("id" : 5,)"
492  R"("params": )"
493  R"({"account": )"
494  R"("n9MJkEKHDhy5eTLuHUQeAAjo382frHNbFK4C8hcwN4nwM2SrLdBj"}})");
495  BEAST_EXPECT(
496  lines[jss::error][jss::message] ==
497  RPC::make_error(rpcBAD_SEED)[jss::error_message]);
498  BEAST_EXPECT(
499  lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
500  BEAST_EXPECT(
501  lines.isMember(jss::ripplerpc) &&
502  lines[jss::ripplerpc] == "2.0");
503  BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
504  }
505  Account const alice{"alice"};
506  {
507  // account_lines on an unfunded account.
508  auto const lines = env.rpc(
509  "json2",
510  "{ "
511  R"("method" : "account_lines",)"
512  R"("jsonrpc" : "2.0",)"
513  R"("ripplerpc" : "2.0",)"
514  R"("id" : 5,)"
515  R"("params": )"
516  R"({"account": ")" +
517  alice.human() + R"("}})");
518  BEAST_EXPECT(
519  lines[jss::error][jss::message] ==
520  RPC::make_error(rpcACT_NOT_FOUND)[jss::error_message]);
521  BEAST_EXPECT(
522  lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
523  BEAST_EXPECT(
524  lines.isMember(jss::ripplerpc) &&
525  lines[jss::ripplerpc] == "2.0");
526  BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
527  }
528  env.fund(XRP(10000), alice);
529  env.close();
530  LedgerInfo const ledger3Info = env.closed()->info();
531  BEAST_EXPECT(ledger3Info.seq == 3);
532 
533  {
534  // alice is funded but has no lines. An empty array is returned.
535  auto const lines = env.rpc(
536  "json2",
537  "{ "
538  R"("method" : "account_lines",)"
539  R"("jsonrpc" : "2.0",)"
540  R"("ripplerpc" : "2.0",)"
541  R"("id" : 5,)"
542  R"("params": )"
543  R"({"account": ")" +
544  alice.human() + R"("}})");
545  BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
546  BEAST_EXPECT(lines[jss::result][jss::lines].size() == 0);
547  BEAST_EXPECT(
548  lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
549  BEAST_EXPECT(
550  lines.isMember(jss::ripplerpc) &&
551  lines[jss::ripplerpc] == "2.0");
552  BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
553  }
554  {
555  // Specify a ledger that doesn't exist.
556  auto const lines = env.rpc(
557  "json2",
558  "{ "
559  R"("method" : "account_lines",)"
560  R"("jsonrpc" : "2.0",)"
561  R"("ripplerpc" : "2.0",)"
562  R"("id" : 5,)"
563  R"("params": )"
564  R"({"account": ")" +
565  alice.human() +
566  R"(", )"
567  R"("ledger_index": "nonsense"}})");
568  BEAST_EXPECT(
569  lines[jss::error][jss::message] == "ledgerIndexMalformed");
570  BEAST_EXPECT(
571  lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
572  BEAST_EXPECT(
573  lines.isMember(jss::ripplerpc) &&
574  lines[jss::ripplerpc] == "2.0");
575  BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
576  }
577  {
578  // Specify a different ledger that doesn't exist.
579  auto const lines = env.rpc(
580  "json2",
581  "{ "
582  R"("method" : "account_lines",)"
583  R"("jsonrpc" : "2.0",)"
584  R"("ripplerpc" : "2.0",)"
585  R"("id" : 5,)"
586  R"("params": )"
587  R"({"account": ")" +
588  alice.human() +
589  R"(", )"
590  R"("ledger_index": 50000}})");
591  BEAST_EXPECT(lines[jss::error][jss::message] == "ledgerNotFound");
592  BEAST_EXPECT(
593  lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
594  BEAST_EXPECT(
595  lines.isMember(jss::ripplerpc) &&
596  lines[jss::ripplerpc] == "2.0");
597  BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
598  }
599  // Create trust lines to share with alice.
600  Account const gw1{"gw1"};
601  env.fund(XRP(10000), gw1);
602  std::vector<IOU> gw1Currencies;
603 
604  for (char c = 0; c <= ('Z' - 'A'); ++c)
605  {
606  // gw1 currencies have names "YAA" -> "YAZ".
607  gw1Currencies.push_back(
608  gw1[std::string("YA") + static_cast<char>('A' + c)]);
609  IOU const& gw1Currency = gw1Currencies.back();
610 
611  // Establish trust lines.
612  env(trust(alice, gw1Currency(100 + c)));
613  env(pay(gw1, alice, gw1Currency(50 + c)));
614  }
615  env.close();
616  LedgerInfo const ledger4Info = env.closed()->info();
617  BEAST_EXPECT(ledger4Info.seq == 4);
618 
619  // Add another set of trust lines in another ledger so we can see
620  // differences in historic ledgers.
621  Account const gw2{"gw2"};
622  env.fund(XRP(10000), gw2);
623 
624  // gw2 requires authorization.
625  env(fset(gw2, asfRequireAuth));
626  env.close();
627  std::vector<IOU> gw2Currencies;
628 
629  for (char c = 0; c <= ('Z' - 'A'); ++c)
630  {
631  // gw2 currencies have names "ZAA" -> "ZAZ".
632  gw2Currencies.push_back(
633  gw2[std::string("ZA") + static_cast<char>('A' + c)]);
634  IOU const& gw2Currency = gw2Currencies.back();
635 
636  // Establish trust lines.
637  env(trust(alice, gw2Currency(200 + c)));
638  env(trust(gw2, gw2Currency(0), alice, tfSetfAuth));
639  env.close();
640  env(pay(gw2, alice, gw2Currency(100 + c)));
641  env.close();
642 
643  // Set flags on gw2 trust lines so we can look for them.
644  env(trust(alice, gw2Currency(0), gw2, tfSetNoRipple | tfSetFreeze));
645  }
646  env.close();
647  LedgerInfo const ledger58Info = env.closed()->info();
648  BEAST_EXPECT(ledger58Info.seq == 58);
649 
650  // A re-usable test for historic ledgers.
651  auto testAccountLinesHistory = [this, &env](
652  Account const& account,
653  LedgerInfo const& info,
654  int count) {
655  // Get account_lines by ledger index.
656  auto const linesSeq = env.rpc(
657  "json2",
658  "{ "
659  R"("method" : "account_lines",)"
660  R"("jsonrpc" : "2.0",)"
661  R"("ripplerpc" : "2.0",)"
662  R"("id" : 5,)"
663  R"("params": )"
664  R"({"account": ")" +
665  account.human() +
666  R"(", )"
667  R"("ledger_index": )" +
668  std::to_string(info.seq) + "}}");
669  BEAST_EXPECT(linesSeq[jss::result][jss::lines].isArray());
670  BEAST_EXPECT(linesSeq[jss::result][jss::lines].size() == count);
671  BEAST_EXPECT(
672  linesSeq.isMember(jss::jsonrpc) &&
673  linesSeq[jss::jsonrpc] == "2.0");
674  BEAST_EXPECT(
675  linesSeq.isMember(jss::ripplerpc) &&
676  linesSeq[jss::ripplerpc] == "2.0");
677  BEAST_EXPECT(linesSeq.isMember(jss::id) && linesSeq[jss::id] == 5);
678 
679  // Get account_lines by ledger hash.
680  auto const linesHash = env.rpc(
681  "json2",
682  "{ "
683  R"("method" : "account_lines",)"
684  R"("jsonrpc" : "2.0",)"
685  R"("ripplerpc" : "2.0",)"
686  R"("id" : 5,)"
687  R"("params": )"
688  R"({"account": ")" +
689  account.human() +
690  R"(", )"
691  R"("ledger_hash": ")" +
692  to_string(info.hash) + R"("}})");
693  BEAST_EXPECT(linesHash[jss::result][jss::lines].isArray());
694  BEAST_EXPECT(linesHash[jss::result][jss::lines].size() == count);
695  BEAST_EXPECT(
696  linesHash.isMember(jss::jsonrpc) &&
697  linesHash[jss::jsonrpc] == "2.0");
698  BEAST_EXPECT(
699  linesHash.isMember(jss::ripplerpc) &&
700  linesHash[jss::ripplerpc] == "2.0");
701  BEAST_EXPECT(
702  linesHash.isMember(jss::id) && linesHash[jss::id] == 5);
703  };
704 
705  // Alice should have no trust lines in ledger 3.
706  testAccountLinesHistory(alice, ledger3Info, 0);
707 
708  // Alice should have 26 trust lines in ledger 4.
709  testAccountLinesHistory(alice, ledger4Info, 26);
710 
711  // Alice should have 52 trust lines in ledger 58.
712  testAccountLinesHistory(alice, ledger58Info, 52);
713 
714  {
715  // Surprisingly, it's valid to specify both index and hash, in
716  // which case the hash wins.
717  auto const lines = env.rpc(
718  "json2",
719  "{ "
720  R"("method" : "account_lines",)"
721  R"("jsonrpc" : "2.0",)"
722  R"("ripplerpc" : "2.0",)"
723  R"("id" : 5,)"
724  R"("params": )"
725  R"({"account": ")" +
726  alice.human() +
727  R"(", )"
728  R"("ledger_hash": ")" +
729  to_string(ledger4Info.hash) +
730  R"(", )"
731  R"("ledger_index": )" +
732  std::to_string(ledger58Info.seq) + "}}");
733  BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
734  BEAST_EXPECT(lines[jss::result][jss::lines].size() == 26);
735  BEAST_EXPECT(
736  lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
737  BEAST_EXPECT(
738  lines.isMember(jss::ripplerpc) &&
739  lines[jss::ripplerpc] == "2.0");
740  BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
741  }
742  {
743  // alice should have 52 trust lines in the current ledger.
744  auto const lines = env.rpc(
745  "json2",
746  "{ "
747  R"("method" : "account_lines",)"
748  R"("jsonrpc" : "2.0",)"
749  R"("ripplerpc" : "2.0",)"
750  R"("id" : 5,)"
751  R"("params": )"
752  R"({"account": ")" +
753  alice.human() + R"("}})");
754  BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
755  BEAST_EXPECT(lines[jss::result][jss::lines].size() == 52);
756  BEAST_EXPECT(
757  lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
758  BEAST_EXPECT(
759  lines.isMember(jss::ripplerpc) &&
760  lines[jss::ripplerpc] == "2.0");
761  BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
762  }
763  {
764  // alice should have 26 trust lines with gw1.
765  auto const lines = env.rpc(
766  "json2",
767  "{ "
768  R"("method" : "account_lines",)"
769  R"("jsonrpc" : "2.0",)"
770  R"("ripplerpc" : "2.0",)"
771  R"("id" : 5,)"
772  R"("params": )"
773  R"({"account": ")" +
774  alice.human() +
775  R"(", )"
776  R"("peer": ")" +
777  gw1.human() + R"("}})");
778  BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
779  BEAST_EXPECT(lines[jss::result][jss::lines].size() == 26);
780  BEAST_EXPECT(
781  lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
782  BEAST_EXPECT(
783  lines.isMember(jss::ripplerpc) &&
784  lines[jss::ripplerpc] == "2.0");
785  BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
786  }
787  {
788  // Use a malformed peer.
789  auto const lines = env.rpc(
790  "json2",
791  "{ "
792  R"("method" : "account_lines",)"
793  R"("jsonrpc" : "2.0",)"
794  R"("ripplerpc" : "2.0",)"
795  R"("id" : 5,)"
796  R"("params": )"
797  R"({"account": ")" +
798  alice.human() +
799  R"(", )"
800  R"("peer": )"
801  R"("n9MJkEKHDhy5eTLuHUQeAAjo382frHNbFK4C8hcwN4nwM2SrLdBj"}})");
802  BEAST_EXPECT(
803  lines[jss::error][jss::message] ==
804  RPC::make_error(rpcBAD_SEED)[jss::error_message]);
805  BEAST_EXPECT(
806  lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
807  BEAST_EXPECT(
808  lines.isMember(jss::ripplerpc) &&
809  lines[jss::ripplerpc] == "2.0");
810  BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
811  }
812  {
813  // A negative limit should fail.
814  auto const lines = env.rpc(
815  "json2",
816  "{ "
817  R"("method" : "account_lines",)"
818  R"("jsonrpc" : "2.0",)"
819  R"("ripplerpc" : "2.0",)"
820  R"("id" : 5,)"
821  R"("params": )"
822  R"({"account": ")" +
823  alice.human() +
824  R"(", )"
825  R"("limit": -1}})");
826  BEAST_EXPECT(
827  lines[jss::error][jss::message] ==
828  RPC::expected_field_message(jss::limit, "unsigned integer"));
829  BEAST_EXPECT(
830  lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
831  BEAST_EXPECT(
832  lines.isMember(jss::ripplerpc) &&
833  lines[jss::ripplerpc] == "2.0");
834  BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
835  }
836  {
837  // Limit the response to 1 trust line.
838  auto const linesA = env.rpc(
839  "json2",
840  "{ "
841  R"("method" : "account_lines",)"
842  R"("jsonrpc" : "2.0",)"
843  R"("ripplerpc" : "2.0",)"
844  R"("id" : 5,)"
845  R"("params": )"
846  R"({"account": ")" +
847  alice.human() +
848  R"(", )"
849  R"("limit": 1}})");
850  BEAST_EXPECT(linesA[jss::result][jss::lines].isArray());
851  BEAST_EXPECT(linesA[jss::result][jss::lines].size() == 1);
852  BEAST_EXPECT(
853  linesA.isMember(jss::jsonrpc) && linesA[jss::jsonrpc] == "2.0");
854  BEAST_EXPECT(
855  linesA.isMember(jss::ripplerpc) &&
856  linesA[jss::ripplerpc] == "2.0");
857  BEAST_EXPECT(linesA.isMember(jss::id) && linesA[jss::id] == 5);
858 
859  // Pick up from where the marker left off. We should get 51.
860  auto marker = linesA[jss::result][jss::marker].asString();
861  auto const linesB = env.rpc(
862  "json2",
863  "{ "
864  R"("method" : "account_lines",)"
865  R"("jsonrpc" : "2.0",)"
866  R"("ripplerpc" : "2.0",)"
867  R"("id" : 5,)"
868  R"("params": )"
869  R"({"account": ")" +
870  alice.human() +
871  R"(", )"
872  R"("marker": ")" +
873  marker + R"("}})");
874  BEAST_EXPECT(linesB[jss::result][jss::lines].isArray());
875  BEAST_EXPECT(linesB[jss::result][jss::lines].size() == 51);
876  BEAST_EXPECT(
877  linesB.isMember(jss::jsonrpc) && linesB[jss::jsonrpc] == "2.0");
878  BEAST_EXPECT(
879  linesB.isMember(jss::ripplerpc) &&
880  linesB[jss::ripplerpc] == "2.0");
881  BEAST_EXPECT(linesB.isMember(jss::id) && linesB[jss::id] == 5);
882 
883  // Go again from where the marker left off, but set a limit of 3.
884  auto const linesC = env.rpc(
885  "json2",
886  "{ "
887  R"("method" : "account_lines",)"
888  R"("jsonrpc" : "2.0",)"
889  R"("ripplerpc" : "2.0",)"
890  R"("id" : 5,)"
891  R"("params": )"
892  R"({"account": ")" +
893  alice.human() +
894  R"(", )"
895  R"("limit": 3, )"
896  R"("marker": ")" +
897  marker + R"("}})");
898  BEAST_EXPECT(linesC[jss::result][jss::lines].isArray());
899  BEAST_EXPECT(linesC[jss::result][jss::lines].size() == 3);
900  BEAST_EXPECT(
901  linesC.isMember(jss::jsonrpc) && linesC[jss::jsonrpc] == "2.0");
902  BEAST_EXPECT(
903  linesC.isMember(jss::ripplerpc) &&
904  linesC[jss::ripplerpc] == "2.0");
905  BEAST_EXPECT(linesC.isMember(jss::id) && linesC[jss::id] == 5);
906 
907  // Mess with the marker so it becomes bad and check for the error.
908  marker[5] = marker[5] == '7' ? '8' : '7';
909  auto const linesD = env.rpc(
910  "json2",
911  "{ "
912  R"("method" : "account_lines",)"
913  R"("jsonrpc" : "2.0",)"
914  R"("ripplerpc" : "2.0",)"
915  R"("id" : 5,)"
916  R"("params": )"
917  R"({"account": ")" +
918  alice.human() +
919  R"(", )"
920  R"("marker": ")" +
921  marker + R"("}})");
922  BEAST_EXPECT(
923  linesD[jss::error][jss::message] ==
924  RPC::make_error(rpcINVALID_PARAMS)[jss::error_message]);
925  BEAST_EXPECT(
926  linesD.isMember(jss::jsonrpc) && linesD[jss::jsonrpc] == "2.0");
927  BEAST_EXPECT(
928  linesD.isMember(jss::ripplerpc) &&
929  linesD[jss::ripplerpc] == "2.0");
930  BEAST_EXPECT(linesD.isMember(jss::id) && linesD[jss::id] == 5);
931  }
932  {
933  // A non-string marker should also fail.
934  auto const lines = env.rpc(
935  "json2",
936  "{ "
937  R"("method" : "account_lines",)"
938  R"("jsonrpc" : "2.0",)"
939  R"("ripplerpc" : "2.0",)"
940  R"("id" : 5,)"
941  R"("params": )"
942  R"({"account": ")" +
943  alice.human() +
944  R"(", )"
945  R"("marker": true}})");
946  BEAST_EXPECT(
947  lines[jss::error][jss::message] ==
948  RPC::expected_field_message(jss::marker, "string"));
949  BEAST_EXPECT(
950  lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
951  BEAST_EXPECT(
952  lines.isMember(jss::ripplerpc) &&
953  lines[jss::ripplerpc] == "2.0");
954  BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
955  }
956  {
957  // Check that the flags we expect from alice to gw2 are present.
958  auto const lines = env.rpc(
959  "json2",
960  "{ "
961  R"("method" : "account_lines",)"
962  R"("jsonrpc" : "2.0",)"
963  R"("ripplerpc" : "2.0",)"
964  R"("id" : 5,)"
965  R"("params": )"
966  R"({"account": ")" +
967  alice.human() +
968  R"(", )"
969  R"("limit": 1, )"
970  R"("peer": ")" +
971  gw2.human() + R"("}})");
972  auto const& line = lines[jss::result][jss::lines][0u];
973  BEAST_EXPECT(line[jss::freeze].asBool() == true);
974  BEAST_EXPECT(line[jss::no_ripple].asBool() == true);
975  BEAST_EXPECT(line[jss::peer_authorized].asBool() == true);
976  BEAST_EXPECT(
977  lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
978  BEAST_EXPECT(
979  lines.isMember(jss::ripplerpc) &&
980  lines[jss::ripplerpc] == "2.0");
981  BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
982  }
983  {
984  // Check that the flags we expect from gw2 to alice are present.
985  auto const linesA = env.rpc(
986  "json2",
987  "{ "
988  R"("method" : "account_lines",)"
989  R"("jsonrpc" : "2.0",)"
990  R"("ripplerpc" : "2.0",)"
991  R"("id" : 5,)"
992  R"("params": )"
993  R"({"account": ")" +
994  gw2.human() +
995  R"(", )"
996  R"("limit": 1, )"
997  R"("peer": ")" +
998  alice.human() + R"("}})");
999  auto const& lineA = linesA[jss::result][jss::lines][0u];
1000  BEAST_EXPECT(lineA[jss::freeze_peer].asBool() == true);
1001  BEAST_EXPECT(lineA[jss::no_ripple_peer].asBool() == true);
1002  BEAST_EXPECT(lineA[jss::authorized].asBool() == true);
1003  BEAST_EXPECT(
1004  linesA.isMember(jss::jsonrpc) && linesA[jss::jsonrpc] == "2.0");
1005  BEAST_EXPECT(
1006  linesA.isMember(jss::ripplerpc) &&
1007  linesA[jss::ripplerpc] == "2.0");
1008  BEAST_EXPECT(linesA.isMember(jss::id) && linesA[jss::id] == 5);
1009 
1010  // Continue from the returned marker to make sure that works.
1011  BEAST_EXPECT(linesA[jss::result].isMember(jss::marker));
1012  auto const marker = linesA[jss::result][jss::marker].asString();
1013  auto const linesB = env.rpc(
1014  "json2",
1015  "{ "
1016  R"("method" : "account_lines",)"
1017  R"("jsonrpc" : "2.0",)"
1018  R"("ripplerpc" : "2.0",)"
1019  R"("id" : 5,)"
1020  R"("params": )"
1021  R"({"account": ")" +
1022  gw2.human() +
1023  R"(", )"
1024  R"("limit": 25, )"
1025  R"("marker": ")" +
1026  marker +
1027  R"(", )"
1028  R"("peer": ")" +
1029  alice.human() + R"("}})");
1030  BEAST_EXPECT(linesB[jss::result][jss::lines].isArray());
1031  BEAST_EXPECT(linesB[jss::result][jss::lines].size() == 25);
1032  BEAST_EXPECT(!linesB[jss::result].isMember(jss::marker));
1033  BEAST_EXPECT(
1034  linesB.isMember(jss::jsonrpc) && linesB[jss::jsonrpc] == "2.0");
1035  BEAST_EXPECT(
1036  linesB.isMember(jss::ripplerpc) &&
1037  linesB[jss::ripplerpc] == "2.0");
1038  BEAST_EXPECT(linesB.isMember(jss::id) && linesB[jss::id] == 5);
1039  }
1040  }
1041 
1042  // test API V2
1043  void
1044  testAccountLineDelete2()
1045  {
1046  testcase("V2: account_lines with removed marker");
1047 
1048  using namespace test::jtx;
1049  Env env(*this);
1050 
1051  // The goal here is to observe account_lines marker behavior if the
1052  // entry pointed at by a returned marker is removed from the ledger.
1053  //
1054  // It isn't easy to explicitly delete a trust line, so we do so in a
1055  // round-about fashion. It takes 4 actors:
1056  // o Gateway gw1 issues EUR
1057  // o alice offers to buy 100 EUR for 100 XRP.
1058  // o becky offers to sell 100 EUR for 100 XRP.
1059  // There will now be an inferred trustline between alice and gw2.
1060  // o alice pays her 100 EUR to cheri.
1061  // alice should now have no EUR and no trustline to gw2.
1062  Account const alice{"alice"};
1063  Account const becky{"becky"};
1064  Account const cheri{"cheri"};
1065  Account const gw1{"gw1"};
1066  Account const gw2{"gw2"};
1067  env.fund(XRP(10000), alice, becky, cheri, gw1, gw2);
1068  env.close();
1069 
1070  auto const USD = gw1["USD"];
1071  auto const EUR = gw2["EUR"];
1072  env(trust(alice, USD(200)));
1073  env(trust(becky, EUR(200)));
1074  env(trust(cheri, EUR(200)));
1075  env.close();
1076 
1077  // becky gets 100 EUR from gw1.
1078  env(pay(gw2, becky, EUR(100)));
1079  env.close();
1080 
1081  // alice offers to buy 100 EUR for 100 XRP.
1082  env(offer(alice, EUR(100), XRP(100)));
1083  env.close();
1084 
1085  // becky offers to buy 100 XRP for 100 EUR.
1086  env(offer(becky, XRP(100), EUR(100)));
1087  env.close();
1088 
1089  // Get account_lines for alice. Limit at 1, so we get a marker.
1090  auto const linesBeg = env.rpc(
1091  "json2",
1092  "{ "
1093  R"("method" : "account_lines",)"
1094  R"("jsonrpc" : "2.0",)"
1095  R"("ripplerpc" : "2.0",)"
1096  R"("id" : 5,)"
1097  R"("params": )"
1098  R"({"account": ")" +
1099  alice.human() +
1100  R"(", )"
1101  R"("limit": 1}})");
1102  BEAST_EXPECT(
1103  linesBeg[jss::result][jss::lines][0u][jss::currency] == "USD");
1104  BEAST_EXPECT(linesBeg[jss::result].isMember(jss::marker));
1105  BEAST_EXPECT(
1106  linesBeg.isMember(jss::jsonrpc) && linesBeg[jss::jsonrpc] == "2.0");
1107  BEAST_EXPECT(
1108  linesBeg.isMember(jss::ripplerpc) &&
1109  linesBeg[jss::ripplerpc] == "2.0");
1110  BEAST_EXPECT(linesBeg.isMember(jss::id) && linesBeg[jss::id] == 5);
1111 
1112  // alice pays 100 USD to cheri.
1113  env(pay(alice, cheri, EUR(100)));
1114  env.close();
1115 
1116  // Since alice paid all her EUR to cheri, alice should no longer
1117  // have a trust line to gw1. So the old marker should now be invalid.
1118  auto const linesEnd = env.rpc(
1119  "json2",
1120  "{ "
1121  R"("method" : "account_lines",)"
1122  R"("jsonrpc" : "2.0",)"
1123  R"("ripplerpc" : "2.0",)"
1124  R"("id" : 5,)"
1125  R"("params": )"
1126  R"({"account": ")" +
1127  alice.human() +
1128  R"(", )"
1129  R"("marker": ")" +
1130  linesBeg[jss::result][jss::marker].asString() + R"("}})");
1131  BEAST_EXPECT(
1132  linesEnd[jss::error][jss::message] ==
1133  RPC::make_error(rpcINVALID_PARAMS)[jss::error_message]);
1134  BEAST_EXPECT(
1135  linesEnd.isMember(jss::jsonrpc) && linesEnd[jss::jsonrpc] == "2.0");
1136  BEAST_EXPECT(
1137  linesEnd.isMember(jss::ripplerpc) &&
1138  linesEnd[jss::ripplerpc] == "2.0");
1139  BEAST_EXPECT(linesEnd.isMember(jss::id) && linesEnd[jss::id] == 5);
1140  }
1141 
1142  void
1143  run() override
1144  {
1145  testAccountLines();
1146  testAccountLineDelete();
1147  testAccountLines2();
1148  testAccountLineDelete2();
1149  }
1150 };
1151 
1152 BEAST_DEFINE_TESTSUITE(AccountLinesRPC, app, ripple);
1153 
1154 } // namespace RPC
1155 } // 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::LedgerInfo::hash
uint256 hash
Definition: ReadView.h:96
std::vector
STL class.
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
std::vector::back
T back(T... args)
ripple::LedgerInfo::seq
LedgerIndex seq
Definition: ReadView.h:88
ripple::to_string
std::string to_string(ListDisposition disposition)
Definition: ValidatorList.cpp:41
ripple::test::jtx::offer
Json::Value offer(Account const &account, STAmount const &in, STAmount const &out, std::uint32_t flags)
Create an offer.
Definition: offer.cpp:28
ripple::RPC::missing_field_error
Json::Value missing_field_error(std::string const &name)
Definition: ErrorCodes.h:236
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:290
ripple::asfRequireAuth
const std::uint32_t asfRequireAuth
Definition: TxFlags.h:66
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::tfSetFreeze
const std::uint32_t tfSetFreeze
Definition: TxFlags.h:94
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::tfSetNoRipple
const std::uint32_t tfSetNoRipple
Definition: TxFlags.h:92
ripple::test::jtx::pay
Json::Value pay(Account const &account, Account const &to, AnyAmount amount)
Create a payment.
Definition: pay.cpp:29
std::count
T count(T... args)
ripple::LedgerInfo
Information about the notional ledger backing the view.
Definition: ReadView.h:80
ripple::tfSetfAuth
const std::uint32_t tfSetfAuth
Definition: TxFlags.h:91
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:202