rippled
NoRippleCheck_test.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2017 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/app/misc/TxQ.h>
21 #include <ripple/beast/utility/temp_dir.h>
22 #include <ripple/protocol/Feature.h>
23 #include <ripple/protocol/jss.h>
24 #include <ripple/resource/ResourceManager.h>
25 #include <ripple/resource/impl/Entry.h>
26 #include <ripple/resource/impl/Tuning.h>
27 #include <ripple/rpc/impl/Tuning.h>
28 #include <boost/algorithm/string/predicate.hpp>
29 #include <test/jtx.h>
30 #include <test/jtx/envconfig.h>
31 
32 namespace ripple {
33 
34 class NoRippleCheck_test : public beast::unit_test::suite
35 {
36  void
38  {
39  testcase("Bad input to noripple_check");
40 
41  using namespace test::jtx;
42  Env env{*this};
43 
44  auto const alice = Account{"alice"};
45  env.fund(XRP(10000), alice);
46  env.close();
47 
48  { // missing account field
49  auto const result =
50  env.rpc("json", "noripple_check", "{}")[jss::result];
51  BEAST_EXPECT(result[jss::error] == "invalidParams");
52  BEAST_EXPECT(
53  result[jss::error_message] == "Missing field 'account'.");
54  }
55 
56  { // missing role field
57  Json::Value params;
58  params[jss::account] = alice.human();
59  auto const result = env.rpc(
60  "json",
61  "noripple_check",
62  boost::lexical_cast<std::string>(params))[jss::result];
63  BEAST_EXPECT(result[jss::error] == "invalidParams");
64  BEAST_EXPECT(result[jss::error_message] == "Missing field 'role'.");
65  }
66 
67  { // invalid role field
68  Json::Value params;
69  params[jss::account] = alice.human();
70  params[jss::role] = "not_a_role";
71  auto const result = env.rpc(
72  "json",
73  "noripple_check",
74  boost::lexical_cast<std::string>(params))[jss::result];
75  BEAST_EXPECT(result[jss::error] == "invalidParams");
76  BEAST_EXPECT(result[jss::error_message] == "Invalid field 'role'.");
77  }
78 
79  { // invalid limit
80  Json::Value params;
81  params[jss::account] = alice.human();
82  params[jss::role] = "user";
83  params[jss::limit] = -1;
84  auto const result = env.rpc(
85  "json",
86  "noripple_check",
87  boost::lexical_cast<std::string>(params))[jss::result];
88  BEAST_EXPECT(result[jss::error] == "invalidParams");
89  BEAST_EXPECT(
90  result[jss::error_message] ==
91  "Invalid field 'limit', not unsigned integer.");
92  }
93 
94  { // invalid ledger (hash)
95  Json::Value params;
96  params[jss::account] = alice.human();
97  params[jss::role] = "user";
98  params[jss::ledger_hash] = 1;
99  auto const result = env.rpc(
100  "json",
101  "noripple_check",
102  boost::lexical_cast<std::string>(params))[jss::result];
103  BEAST_EXPECT(result[jss::error] == "invalidParams");
104  BEAST_EXPECT(result[jss::error_message] == "ledgerHashNotString");
105  }
106 
107  { // account not found
108  Json::Value params;
109  params[jss::account] = Account{"nobody"}.human();
110  params[jss::role] = "user";
111  params[jss::ledger] = "current";
112  auto const result = env.rpc(
113  "json",
114  "noripple_check",
115  boost::lexical_cast<std::string>(params))[jss::result];
116  BEAST_EXPECT(result[jss::error] == "actNotFound");
117  BEAST_EXPECT(result[jss::error_message] == "Account not found.");
118  }
119 
120  { // passing an account private key will cause
121  // parsing as a seed to fail
122  Json::Value params;
123  params[jss::account] = toBase58(TokenType::NodePrivate, alice.sk());
124  params[jss::role] = "user";
125  params[jss::ledger] = "current";
126  auto const result = env.rpc(
127  "json",
128  "noripple_check",
129  boost::lexical_cast<std::string>(params))[jss::result];
130  BEAST_EXPECT(result[jss::error] == "badSeed");
131  BEAST_EXPECT(result[jss::error_message] == "Disallowed seed.");
132  }
133  }
134 
135  void
136  testBasic(bool user, bool problems)
137  {
138  testcase << "Request noripple_check for " << (user ? "user" : "gateway")
139  << " role, expect" << (problems ? "" : " no") << " problems";
140 
141  using namespace test::jtx;
142  Env env{*this};
143 
144  auto const gw = Account{"gw"};
145  auto const alice = Account{"alice"};
146 
147  env.fund(XRP(10000), gw, alice);
148  if ((user && problems) || (!user && !problems))
149  {
150  env(fset(alice, asfDefaultRipple));
151  env(trust(alice, gw["USD"](100)));
152  }
153  else
154  {
155  env(fclear(alice, asfDefaultRipple));
156  env(trust(alice, gw["USD"](100), gw, tfSetNoRipple));
157  }
158  env.close();
159 
160  Json::Value params;
161  params[jss::account] = alice.human();
162  params[jss::role] = (user ? "user" : "gateway");
163  params[jss::ledger] = "current";
164  auto result = env.rpc(
165  "json",
166  "noripple_check",
167  boost::lexical_cast<std::string>(params))[jss::result];
168 
169  auto const pa = result["problems"];
170  if (!BEAST_EXPECT(pa.isArray()))
171  return;
172 
173  if (problems)
174  {
175  if (!BEAST_EXPECT(pa.size() == 2))
176  return;
177 
178  if (user)
179  {
180  BEAST_EXPECT(boost::starts_with(
181  pa[0u].asString(), "You appear to have set"));
182  BEAST_EXPECT(boost::starts_with(
183  pa[1u].asString(), "You should probably set"));
184  }
185  else
186  {
187  BEAST_EXPECT(boost::starts_with(
188  pa[0u].asString(), "You should immediately set"));
189  BEAST_EXPECT(
190  boost::starts_with(pa[1u].asString(), "You should clear"));
191  }
192  }
193  else
194  {
195  BEAST_EXPECT(pa.size() == 0);
196  }
197 
198  // now make a second request asking for the relevant transactions this
199  // time.
200  params[jss::transactions] = true;
201  result = env.rpc(
202  "json",
203  "noripple_check",
204  boost::lexical_cast<std::string>(params))[jss::result];
205  if (!BEAST_EXPECT(result[jss::transactions].isArray()))
206  return;
207 
208  auto const txs = result[jss::transactions];
209  if (problems)
210  {
211  if (!BEAST_EXPECT(txs.size() == (user ? 1 : 2)))
212  return;
213 
214  if (!user)
215  {
216  BEAST_EXPECT(txs[0u][jss::Account] == alice.human());
217  BEAST_EXPECT(txs[0u][jss::TransactionType] == jss::AccountSet);
218  }
219 
220  BEAST_EXPECT(
221  result[jss::transactions][txs.size() - 1][jss::Account] ==
222  alice.human());
223  BEAST_EXPECT(
224  result[jss::transactions][txs.size() - 1]
225  [jss::TransactionType] == jss::TrustSet);
226  BEAST_EXPECT(
227  result[jss::transactions][txs.size() - 1][jss::LimitAmount] ==
228  gw["USD"](100).value().getJson(JsonOptions::none));
229  }
230  else
231  {
232  BEAST_EXPECT(txs.size() == 0);
233  }
234  }
235 
236 public:
237  void
238  run() override
239  {
240  testBadInput();
241  for (auto user : {true, false})
242  for (auto problem : {true, false})
243  testBasic(user, problem);
244  }
245 };
246 
247 class NoRippleCheckLimits_test : public beast::unit_test::suite
248 {
249  void
250  testLimits(bool admin)
251  {
252  testcase << "Check limits in returned data, "
253  << (admin ? "admin" : "non-admin");
254 
255  using namespace test::jtx;
256 
257  Env env{*this, admin ? envconfig() : envconfig(no_admin)};
258 
259  auto const alice = Account{"alice"};
260  env.fund(XRP(100000), alice);
261  env(fset(alice, asfDefaultRipple));
262  env.close();
263 
264  auto checkBalance = [&env]() {
265  // this is endpoint drop prevention. Non admin ports will drop
266  // requests if they are coming too fast, so we manipulate the
267  // resource manager here to reset the enpoint balance (for
268  // localhost) if we get too close to the drop limit. It would
269  // be better if we could add this functionality to Env somehow
270  // or otherwise disable endpoint charging for certain test
271  // cases.
272  using namespace ripple::Resource;
273  using namespace std::chrono;
274  using namespace beast::IP;
275  auto c = env.app().getResourceManager().newInboundEndpoint(
276  Endpoint::from_string(test::getEnvLocalhostAddr()));
277 
278  // if we go above the warning threshold, reset
279  if (c.balance() > warningThreshold)
280  {
282  c.entry().local_balance =
283  DecayingSample<decayWindowSeconds, ct>{steady_clock::now()};
284  }
285  };
286 
287  for (auto i = 0; i < ripple::RPC::Tuning::noRippleCheck.rmax + 5; ++i)
288  {
289  if (!admin)
290  checkBalance();
291 
292  auto& txq = env.app().getTxQ();
293  auto const gw = Account{"gw" + std::to_string(i)};
294  env.memoize(gw);
295  auto const baseFee = env.current()->fees().base;
296  env(pay(env.master, gw, XRP(1000)),
297  seq(autofill),
298  fee(toDrops(
299  txq.getMetrics(*env.current()).openLedgerFeeLevel,
300  baseFee)
301  .second +
302  1),
303  sig(autofill));
304  env(fset(gw, asfDefaultRipple),
305  seq(autofill),
306  fee(toDrops(
307  txq.getMetrics(*env.current()).openLedgerFeeLevel,
308  baseFee)
309  .second +
310  1),
311  sig(autofill));
312  env(trust(alice, gw["USD"](10)),
313  fee(toDrops(
314  txq.getMetrics(*env.current()).openLedgerFeeLevel,
315  baseFee)
316  .second +
317  1));
318  env.close();
319  }
320 
321  // default limit value
322  Json::Value params;
323  params[jss::account] = alice.human();
324  params[jss::role] = "user";
325  params[jss::ledger] = "current";
326  auto result = env.rpc(
327  "json",
328  "noripple_check",
329  boost::lexical_cast<std::string>(params))[jss::result];
330 
331  BEAST_EXPECT(result["problems"].size() == 301);
332 
333  // one below minimum
334  params[jss::limit] = 9;
335  result = env.rpc(
336  "json",
337  "noripple_check",
338  boost::lexical_cast<std::string>(params))[jss::result];
339  BEAST_EXPECT(result["problems"].size() == (admin ? 10 : 11));
340 
341  // at minimum
342  params[jss::limit] = 10;
343  result = env.rpc(
344  "json",
345  "noripple_check",
346  boost::lexical_cast<std::string>(params))[jss::result];
347  BEAST_EXPECT(result["problems"].size() == 11);
348 
349  // at max
350  params[jss::limit] = 400;
351  result = env.rpc(
352  "json",
353  "noripple_check",
354  boost::lexical_cast<std::string>(params))[jss::result];
355  BEAST_EXPECT(result["problems"].size() == 401);
356 
357  // at max+1
358  params[jss::limit] = 401;
359  result = env.rpc(
360  "json",
361  "noripple_check",
362  boost::lexical_cast<std::string>(params))[jss::result];
363  BEAST_EXPECT(result["problems"].size() == (admin ? 402 : 401));
364  }
365 
366 public:
367  void
368  run() override
369  {
370  for (auto admin : {true, false})
371  testLimits(admin);
372  }
373 };
374 
375 BEAST_DEFINE_TESTSUITE(NoRippleCheck, app, ripple);
376 
377 // These tests that deal with limit amounts are slow because of the
378 // offer/account setup, so making them manual -- the additional coverage
379 // provided by them is minimal
380 
381 BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(NoRippleCheckLimits, app, ripple, 1);
382 
383 } // namespace ripple
ripple::RPC::Tuning::noRippleCheck
static constexpr LimitRange noRippleCheck
Limits for the no_ripple_check command.
Definition: rpc/impl/Tuning.h:52
ripple::NoRippleCheck_test::testBadInput
void testBadInput()
Definition: NoRippleCheck_test.cpp:37
ripple::BEAST_DEFINE_TESTSUITE
BEAST_DEFINE_TESTSUITE(AccountTxPaging, app, ripple)
ripple::NoRippleCheck_test::run
void run() override
Definition: NoRippleCheck_test.cpp:238
ripple::asfDefaultRipple
const std::uint32_t asfDefaultRipple
Definition: TxFlags.h:72
ripple::toBase58
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition: AccountID.cpp:29
ripple::Resource::warningThreshold
@ warningThreshold
Definition: resource/impl/Tuning.h:31
ripple::NoRippleCheck_test::testBasic
void testBasic(bool user, bool problems)
Definition: NoRippleCheck_test.cpp:136
ripple::test::getEnvLocalhostAddr
const char * getEnvLocalhostAddr()
Definition: envconfig.h:31
ripple::JsonOptions::none
@ none
std::to_string
T to_string(T... args)
ripple::NoRippleCheckLimits_test::testLimits
void testLimits(bool admin)
Definition: NoRippleCheck_test.cpp:250
ripple::Resource
Definition: Application.h:39
beast::IP
Definition: IPAddressConversion.cpp:23
ripple::RPC::Tuning::LimitRange::rmax
unsigned int rmax
Definition: rpc/impl/Tuning.h:33
ripple::NoRippleCheckLimits_test::run
void run() override
Definition: NoRippleCheck_test.cpp:368
beast::abstract_clock
Abstract interface to a clock.
Definition: abstract_clock.h:57
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::toDrops
std::pair< bool, XRPAmount > toDrops(FeeLevel< T > const &level, XRPAmount const &baseFee)
Definition: TxQ.h:779
ripple::DecayingSample
Sampling function using exponential decay to provide a continuous value.
Definition: DecayingSample.h:32
ripple::BEAST_DEFINE_TESTSUITE_MANUAL_PRIO
BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(digest, ripple_data, ripple, 20)
ripple::NoRippleCheckLimits_test
Definition: NoRippleCheck_test.cpp:247
ripple::NoRippleCheck_test
Definition: NoRippleCheck_test.cpp:34
ripple::TokenType::NodePrivate
@ NodePrivate
Json::Value
Represents a JSON value.
Definition: json_value.h:145
std::chrono