xrpld
Loading...
Searching...
No Matches
NoRippleCheck_test.cpp
1#include <test/jtx/Env.h>
2#include <test/jtx/amount.h>
3#include <test/jtx/balance.h> // IWYU pragma: keep
4#include <test/jtx/envconfig.h>
5#include <test/jtx/fee.h>
6#include <test/jtx/flags.h>
7#include <test/jtx/pay.h>
8#include <test/jtx/seq.h>
9#include <test/jtx/sig.h>
10#include <test/jtx/tags.h>
11#include <test/jtx/trust.h>
12
13#include <xrpld/app/misc/TxQ.h>
14#include <xrpld/rpc/detail/Tuning.h>
15
16#include <xrpl/basics/DecayingSample.h>
17#include <xrpl/beast/clock/abstract_clock.h>
18#include <xrpl/beast/net/IPEndpoint.h>
19#include <xrpl/beast/unit_test/suite.h>
20#include <xrpl/json/json_value.h>
21#include <xrpl/json/to_string.h>
22#include <xrpl/protocol/SecretKey.h>
23#include <xrpl/protocol/TxFlags.h>
24#include <xrpl/protocol/jss.h>
25#include <xrpl/protocol/tokens.h>
26#include <xrpl/resource/ResourceManager.h>
27#include <xrpl/resource/detail/Entry.h>
28#include <xrpl/resource/detail/Tuning.h>
29
30#include <chrono>
31#include <string>
32
33namespace xrpl {
34
36{
37 void
39 {
40 testcase("Bad input to noripple_check");
41
42 using namespace test::jtx;
43 Env env{*this};
44
45 auto const alice = Account{"alice"};
46 env.fund(XRP(10000), alice);
47 env.close();
48
49 { // missing account field
50 auto const result = env.rpc("json", "noripple_check", "{}")[jss::result];
51 BEAST_EXPECT(result[jss::error] == "invalidParams");
52 BEAST_EXPECT(result[jss::error_message] == "Missing field 'account'.");
53 }
54
55 { // missing role field
56 json::Value params;
57 params[jss::account] = alice.human();
58 auto const result = env.rpc("json", "noripple_check", to_string(params))[jss::result];
59 BEAST_EXPECT(result[jss::error] == "invalidParams");
60 BEAST_EXPECT(result[jss::error_message] == "Missing field 'role'.");
61 }
62
63 // test account non-string
64 {
65 auto testInvalidAccountParam = [&](auto const& param) {
66 json::Value params;
67 params[jss::account] = param;
68 params[jss::role] = "user";
69 auto jrr = env.rpc("json", "noripple_check", to_string(params))[jss::result];
70 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
71 BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'account'.");
72 };
73
74 testInvalidAccountParam(1);
75 testInvalidAccountParam(1.1);
76 testInvalidAccountParam(true);
77 testInvalidAccountParam(json::Value(json::ValueType::Null));
78 testInvalidAccountParam(json::Value(json::ValueType::Object));
79 testInvalidAccountParam(json::Value(json::ValueType::Array));
80 }
81
82 { // invalid role field
83 json::Value params;
84 params[jss::account] = alice.human();
85 params[jss::role] = "not_a_role";
86 auto const result = env.rpc("json", "noripple_check", to_string(params))[jss::result];
87 BEAST_EXPECT(result[jss::error] == "invalidParams");
88 BEAST_EXPECT(result[jss::error_message] == "Invalid field 'role'.");
89 }
90
91 { // invalid limit
92 json::Value params;
93 params[jss::account] = alice.human();
94 params[jss::role] = "user";
95 params[jss::limit] = -1;
96 auto const result = env.rpc("json", "noripple_check", to_string(params))[jss::result];
97 BEAST_EXPECT(result[jss::error] == "invalidParams");
98 BEAST_EXPECT(
99 result[jss::error_message] == "Invalid field 'limit', not unsigned integer.");
100 }
101
102 { // invalid ledger (hash)
103 json::Value params;
104 params[jss::account] = alice.human();
105 params[jss::role] = "user";
106 params[jss::ledger_hash] = 1;
107 auto const result = env.rpc("json", "noripple_check", to_string(params))[jss::result];
108 BEAST_EXPECT(result[jss::error] == "invalidParams");
109 BEAST_EXPECT(
110 result[jss::error_message] == "Invalid field 'ledger_hash', not hex string.");
111 }
112
113 { // account not found
114 json::Value params;
115 params[jss::account] = Account{"nobody"}.human();
116 params[jss::role] = "user";
117 params[jss::ledger] = "current";
118 auto const result = env.rpc("json", "noripple_check", to_string(params))[jss::result];
119 BEAST_EXPECT(result[jss::error] == "actNotFound");
120 BEAST_EXPECT(result[jss::error_message] == "Account not found.");
121 }
122
123 { // passing an account private key will cause
124 // parsing as a seed to fail
125 json::Value params;
126 params[jss::account] = toBase58(TokenType::NodePrivate, alice.sk());
127 params[jss::role] = "user";
128 params[jss::ledger] = "current";
129 params[jss::transactions] = true;
130 auto const result = env.rpc("json", "noripple_check", to_string(params))[jss::result];
131 BEAST_EXPECT(result[jss::error] == "actMalformed");
132 BEAST_EXPECT(result[jss::error_message] == "Account malformed.");
133 // The changelog promises malformed-account responses carry
134 // neither `transactions` nor any ledger metadata.
135 BEAST_EXPECT(!result.isMember(jss::transactions));
136 BEAST_EXPECT(!result.isMember(jss::ledger_hash));
137 BEAST_EXPECT(!result.isMember(jss::ledger_index));
138 BEAST_EXPECT(!result.isMember(jss::validated));
139 }
140
141 {
142 // ledger and ledger_hash are included
143 json::Value params;
144 params[jss::account] = Account{"nobody"}.human();
145 params[jss::role] = "user";
146 params[jss::ledger] = "current";
147 params[jss::ledger_hash] = "ABCDEF";
148 auto const result = env.rpc("json", "noripple_check", to_string(params))[jss::result];
149 BEAST_EXPECT(result[jss::error] == "invalidParams");
150 BEAST_EXPECT(
151 result[jss::error_message] ==
152 "Exactly one of 'ledger', 'ledger_hash', or 'ledger_index' can "
153 "be specified.");
154 }
155
156 {
157 // invalid ledger
158 json::Value params;
159 params[jss::account] = Account{"nobody"}.human();
160 params[jss::role] = "user";
161 params[jss::ledger] = json::ValueType::Object;
162 auto const result = env.rpc("json", "noripple_check", to_string(params))[jss::result];
163 BEAST_EXPECT(result[jss::error] == "invalidParams");
164 BEAST_EXPECT(
165 result[jss::error_message] == "Invalid field 'ledger', not string or number.");
166 }
167 }
168
169 void
170 testBasic(bool user, bool problems)
171 {
172 testcase << "Request noripple_check for " << (user ? "user" : "gateway") << " role, expect"
173 << (problems ? "" : " no") << " problems";
174
175 using namespace test::jtx;
176 Env env{*this};
177
178 auto const gw = Account{"gw"};
179 auto const alice = Account{"alice"};
180
181 env.fund(XRP(10000), gw, alice);
182 if ((user && problems) || (!user && !problems))
183 {
184 env(fset(alice, asfDefaultRipple));
185 env(trust(alice, gw["USD"](100)));
186 }
187 else
188 {
189 env(fclear(alice, asfDefaultRipple));
190 env(trust(alice, gw["USD"](100), gw, tfSetNoRipple));
191 }
192 env.close();
193
194 json::Value params;
195 params[jss::account] = alice.human();
196 params[jss::role] = (user ? "user" : "gateway");
197 params[jss::ledger] = "current";
198 auto result = env.rpc("json", "noripple_check", to_string(params))[jss::result];
199
200 auto const pa = result["problems"];
201 if (!BEAST_EXPECT(pa.isArray()))
202 return;
203
204 BEAST_EXPECT(!result.isMember(jss::transactions));
205 if (problems)
206 {
207 if (!BEAST_EXPECT(pa.size() == 2))
208 return;
209
210 if (user)
211 {
212 BEAST_EXPECT(pa[0u].asString().starts_with("You appear to have set"));
213 BEAST_EXPECT(pa[1u].asString().starts_with("You should probably set"));
214 }
215 else
216 {
217 BEAST_EXPECT(pa[0u].asString().starts_with("You should immediately set"));
218 BEAST_EXPECT(pa[1u].asString().starts_with("You should clear"));
219 }
220 }
221 else
222 {
223 BEAST_EXPECT(pa.size() == 0);
224 }
225
226 // now make a second request asking for the relevant transactions this
227 // time.
228 params[jss::transactions] = true;
229 result = env.rpc("json", "noripple_check", to_string(params))[jss::result];
230
231 auto const txs = result[jss::transactions];
232 if (problems)
233 {
234 if (!BEAST_EXPECT(result[jss::transactions].isArray()))
235 return;
236 if (!BEAST_EXPECT(txs.size() == (user ? 1 : 2)))
237 return;
238
239 if (!user)
240 {
241 BEAST_EXPECT(txs[0u][jss::Account] == alice.human());
242 BEAST_EXPECT(txs[0u][jss::TransactionType] == jss::AccountSet);
243 }
244
245 BEAST_EXPECT(result[jss::transactions][txs.size() - 1][jss::Account] == alice.human());
246 BEAST_EXPECT(
247 result[jss::transactions][txs.size() - 1][jss::TransactionType] == jss::TrustSet);
248 BEAST_EXPECT(
249 result[jss::transactions][txs.size() - 1][jss::LimitAmount] ==
250 gw["USD"](100).value().getJson(JsonOptions::Values::None));
251 }
252 else
253 {
254 BEAST_EXPECT(txs.size() == 0);
255 }
256 }
257
258public:
259 void
260 run() override
261 {
262 testBadInput();
263 for (auto user : {true, false})
264 {
265 for (auto problem : {true, false})
266 testBasic(user, problem);
267 }
268 }
269};
270
272{
273 void
274 testLimits(bool admin)
275 {
276 testcase << "Check limits in returned data, " << (admin ? "admin" : "non-admin");
277
278 using namespace test::jtx;
279
280 Env env{*this, admin ? envconfig() : envconfig(noAdmin)};
281
282 auto const alice = Account{"alice"};
283 env.fund(XRP(100000), alice);
284 env(fset(alice, asfDefaultRipple));
285 env.close();
286
287 auto checkBalance = [&env]() {
288 // this is endpoint drop prevention. Non admin ports will drop
289 // requests if they are coming too fast, so we manipulate the
290 // resource manager here to reset the endpoint balance (for
291 // localhost) if we get too close to the drop limit. It would
292 // be better if we could add this functionality to Env somehow
293 // or otherwise disable endpoint charging for certain test
294 // cases.
295 using namespace xrpl::resource;
296 using namespace std::chrono;
297 using namespace beast::ip;
298 auto c = env.app().getResourceManager().newInboundEndpoint(
300
301 // if we go above the warning threshold, reset
302 if (c.balance() > kWarningThreshold)
303 {
305 c.entry().localBalance =
307 }
308 };
309
310 for (auto i = 0; i < xrpl::rpc::tuning::kNoRippleCheck.rmax + 5; ++i)
311 {
312 if (!admin)
313 checkBalance();
314
315 auto& txq = env.app().getTxQ();
316 auto const gw = Account{"gw" + std::to_string(i)};
317 env.memoize(gw);
318 auto const baseFee = env.current()->fees().base;
319 env(pay(env.master, gw, XRP(1000)),
320 Seq(kAutofill),
321 Fee(toDrops(txq.getMetrics(*env.current()).openLedgerFeeLevel, baseFee) + 1),
322 Sig(kAutofill));
323 env(fset(gw, asfDefaultRipple),
324 Seq(kAutofill),
325 Fee(toDrops(txq.getMetrics(*env.current()).openLedgerFeeLevel, baseFee) + 1),
326 Sig(kAutofill));
327 env(trust(alice, gw["USD"](10)),
328 Fee(toDrops(txq.getMetrics(*env.current()).openLedgerFeeLevel, baseFee) + 1));
329 env.close();
330 }
331
332 // default limit value
333 json::Value params;
334 params[jss::account] = alice.human();
335 params[jss::role] = "user";
336 params[jss::ledger] = "current";
337 auto result = env.rpc("json", "noripple_check", to_string(params))[jss::result];
338
339 BEAST_EXPECT(result["problems"].size() == 301);
340
341 // one below minimum
342 params[jss::limit] = 9;
343 result = env.rpc("json", "noripple_check", to_string(params))[jss::result];
344 BEAST_EXPECT(result["problems"].size() == (admin ? 10 : 11));
345
346 // at minimum
347 params[jss::limit] = 10;
348 result = env.rpc("json", "noripple_check", to_string(params))[jss::result];
349 BEAST_EXPECT(result["problems"].size() == 11);
350
351 // at max
352 params[jss::limit] = 400;
353 result = env.rpc("json", "noripple_check", to_string(params))[jss::result];
354 BEAST_EXPECT(result["problems"].size() == 401);
355
356 // at max+1
357 params[jss::limit] = 401;
358 result = env.rpc("json", "noripple_check", to_string(params))[jss::result];
359 BEAST_EXPECT(result["problems"].size() == (admin ? 402 : 401));
360 }
361
362public:
363 void
364 run() override
365 {
366 for (auto admin : {true, false})
367 testLimits(admin);
368 }
369};
370
372
373// These tests that deal with limit amounts are slow because of the
374// offer/account setup, so making them manual -- the additional coverage
375// provided by them is minimal
376
378
379} // namespace xrpl
Abstract interface to a clock.
static Endpoint fromString(std::string const &s)
A testsuite class.
Definition suite.h:52
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:155
Represents a JSON value.
Definition json_value.h:117
Sampling function using exponential decay to provide a continuous value.
void run() override
Runs the suite.
void run() override
Runs the suite.
void testBasic(bool user, bool problems)
@ Array
array value (ordered list)
Definition json_value.h:28
@ Object
object value (collection of name/value pairs).
Definition json_value.h:29
@ Null
'null' value
Definition json_value.h:22
static constexpr auto kWarningThreshold
Tunable constants.
static constexpr LimitRange kNoRippleCheck
Limits for the no_ripple_check command.
API version numbers used in later API versions.
Definition ApiVersion.h:36
char const * getEnvLocalhostAddr()
Definition envconfig.h:16
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
XRPAmount toDrops(FeeLevel< T > const &level, XRPAmount baseFee)
Definition TxQ.h:1004
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:657
BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(NoRippleCheckLimits, rpc, xrpl, 1)
BEAST_DEFINE_TESTSUITE(AccountTxPaging, app, xrpl)
T size(T... args)
T to_string(T... args)