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] == "actMalformed");
131  BEAST_EXPECT(result[jss::error_message] == "Account malformed.");
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  1),
302  sig(autofill));
303  env(fset(gw, asfDefaultRipple),
304  seq(autofill),
305  fee(toDrops(
306  txq.getMetrics(*env.current()).openLedgerFeeLevel,
307  baseFee) +
308  1),
309  sig(autofill));
310  env(trust(alice, gw["USD"](10)),
311  fee(toDrops(
312  txq.getMetrics(*env.current()).openLedgerFeeLevel,
313  baseFee) +
314  1));
315  env.close();
316  }
317 
318  // default limit value
319  Json::Value params;
320  params[jss::account] = alice.human();
321  params[jss::role] = "user";
322  params[jss::ledger] = "current";
323  auto result = env.rpc(
324  "json",
325  "noripple_check",
326  boost::lexical_cast<std::string>(params))[jss::result];
327 
328  BEAST_EXPECT(result["problems"].size() == 301);
329 
330  // one below minimum
331  params[jss::limit] = 9;
332  result = env.rpc(
333  "json",
334  "noripple_check",
335  boost::lexical_cast<std::string>(params))[jss::result];
336  BEAST_EXPECT(result["problems"].size() == (admin ? 10 : 11));
337 
338  // at minimum
339  params[jss::limit] = 10;
340  result = env.rpc(
341  "json",
342  "noripple_check",
343  boost::lexical_cast<std::string>(params))[jss::result];
344  BEAST_EXPECT(result["problems"].size() == 11);
345 
346  // at max
347  params[jss::limit] = 400;
348  result = env.rpc(
349  "json",
350  "noripple_check",
351  boost::lexical_cast<std::string>(params))[jss::result];
352  BEAST_EXPECT(result["problems"].size() == 401);
353 
354  // at max+1
355  params[jss::limit] = 401;
356  result = env.rpc(
357  "json",
358  "noripple_check",
359  boost::lexical_cast<std::string>(params))[jss::result];
360  BEAST_EXPECT(result["problems"].size() == (admin ? 402 : 401));
361  }
362 
363 public:
364  void
365  run() override
366  {
367  for (auto admin : {true, false})
368  testLimits(admin);
369  }
370 };
371 
372 BEAST_DEFINE_TESTSUITE(NoRippleCheck, app, ripple);
373 
374 // These tests that deal with limit amounts are slow because of the
375 // offer/account setup, so making them manual -- the additional coverage
376 // provided by them is minimal
377 
378 BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(NoRippleCheckLimits, app, ripple, 1);
379 
380 } // 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::tfSetNoRipple
constexpr std::uint32_t tfSetNoRipple
Definition: TxFlags.h:109
ripple::toBase58
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition: AccountID.cpp:104
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:40
ripple::BEAST_DEFINE_TESTSUITE_MANUAL_PRIO
BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(NoRippleCheckLimits, app, ripple, 1)
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:365
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::asfDefaultRipple
constexpr std::uint32_t asfDefaultRipple
Definition: TxFlags.h:81
ripple::DecayingSample
Sampling function using exponential decay to provide a continuous value.
Definition: DecayingSample.h:32
ripple::NoRippleCheckLimits_test
Definition: NoRippleCheck_test.cpp:247
ripple::NoRippleCheck_test
Definition: NoRippleCheck_test.cpp:34
ripple::toDrops
XRPAmount toDrops(FeeLevel< T > const &level, XRPAmount baseFee)
Definition: TxQ.h:863
ripple::TokenType::NodePrivate
@ NodePrivate
ripple::Resource::warningThreshold
@ warningThreshold
Definition: resource/impl/Tuning.h:31
Json::Value
Represents a JSON value.
Definition: json_value.h:145
std::chrono