xrpld
Loading...
Searching...
No Matches
ServerStatus_test.cpp
1#include <test/jtx/Env.h>
2#include <test/jtx/JSONRPCClient.h>
3#include <test/jtx/WSClient.h>
4#include <test/jtx/envconfig.h>
5
6#include <xrpld/app/ledger/LedgerMaster.h>
7
8#include <xrpl/basics/base64.h>
9#include <xrpl/beast/test/yield_to.h>
10#include <xrpl/beast/unit_test/suite.h>
11#include <xrpl/config/Constants.h>
12#include <xrpl/json/json_reader.h>
13#include <xrpl/json/json_value.h>
14#include <xrpl/json/to_string.h>
15#include <xrpl/protocol/ErrorCodes.h>
16#include <xrpl/protocol/jss.h>
17#include <xrpl/server/LoadFeeTrack.h>
18#include <xrpl/server/NetworkOPs.h>
19
20#include <boost/algorithm/string/predicate.hpp>
21#include <boost/asio/buffer.hpp>
22#include <boost/asio/connect.hpp>
23#include <boost/asio/io_context.hpp>
24#include <boost/asio/ip/tcp.hpp>
25#include <boost/asio/spawn.hpp>
26#include <boost/asio/ssl/context.hpp>
27#include <boost/asio/ssl/stream.hpp>
28#include <boost/asio/ssl/stream_base.hpp>
29#include <boost/asio/ssl/verify_mode.hpp>
30#include <boost/asio/write.hpp>
31#include <boost/beast/core/make_printable.hpp>
32#include <boost/beast/core/multi_buffer.hpp>
33#include <boost/beast/http/field.hpp>
34#include <boost/beast/http/status.hpp>
35#include <boost/beast/http/verb.hpp>
36#include <boost/beast/websocket/stream.hpp>
37#include <boost/lexical_cast.hpp>
38
39#include <array>
40#include <cstdint>
41#include <memory>
42#include <random>
43#include <regex>
44#include <string>
45#include <utility>
46#include <vector>
47
48namespace xrpl::test {
49
51{
52 class MyFields : public boost::beast::http::fields
53 {
54 };
55
56 static auto
57 makeConfig(std::string const& proto, bool admin = true, bool credentials = false)
58 {
59 auto const sectionName = proto.starts_with("h") ? Sections::kPortRpc : Sections::kPortWs;
60 auto p = jtx::envconfig();
61
62 p->overwrite(sectionName, Keys::kProtocol, proto);
63 if (!admin)
64 p->overwrite(sectionName, Keys::kAdmin, "");
65
66 if (credentials)
67 {
68 (*p)[sectionName].set(Keys::kAdminPassword, "p");
69 (*p)[sectionName].set(Keys::kAdminUser, "u");
70 }
71
72 p->overwrite(
75 proto.starts_with("h") ? "ws" : "http");
76
77 if (proto == "https")
78 {
79 // this port is here to allow the env to create its internal client,
80 // which requires an http endpoint to talk to. In the connection
81 // failure test, this endpoint should never be used
82 (*p)[Sections::kServer].append("port_alt");
83 (*p)["port_alt"].set(Keys::kIp, getEnvLocalhostAddr());
84 (*p)["port_alt"].set(Keys::kPort, "7099");
85 (*p)["port_alt"].set(Keys::kProtocol, "http");
86 (*p)["port_alt"].set(Keys::kAdmin, getEnvLocalhostAddr());
87 }
88
89 return p;
90 }
91
92 static auto
93 makeWSUpgrade(std::string const& host, uint16_t port)
94 {
95 using namespace boost::asio;
96 using namespace boost::beast::http;
97 request<string_body> req;
98
99 req.target("/");
100 req.version(11);
101 req.insert("Host", host + ":" + std::to_string(port));
102 req.insert("User-Agent", "test");
103 req.method(boost::beast::http::verb::get);
104 req.insert("Upgrade", "websocket");
105 {
106 // not secure, but OK for a testing
108 std::mt19937 e{rd()};
111 for (auto& v : key)
112 v = d(e);
113 req.insert("Sec-WebSocket-Key", base64Encode(key.data(), key.size()));
114 };
115 req.insert("Sec-WebSocket-Version", "13");
116 req.insert(boost::beast::http::field::connection, "upgrade");
117 return req;
118 }
119
120 static auto
122 std::string const& host,
123 uint16_t port,
124 std::string const& body,
125 MyFields const& fields)
126 {
127 using namespace boost::asio;
128 using namespace boost::beast::http;
129 request<string_body> req;
130
131 req.target("/");
132 req.version(11);
133 for (auto const& f : fields)
134 req.insert(f.name(), f.value());
135 req.insert("Host", host + ":" + std::to_string(port));
136 req.insert("User-Agent", "test");
137 if (body.empty())
138 {
139 req.method(boost::beast::http::verb::get);
140 }
141 else
142 {
143 req.method(boost::beast::http::verb::post);
144 req.insert("Content-Type", "application/json; charset=UTF-8");
145 req.body() = body;
146 }
147 req.prepare_payload();
148
149 return req;
150 }
151
152 void
154 boost::asio::yield_context& yield,
155 boost::beast::http::request<boost::beast::http::string_body> const& req,
156 std::string const& host,
157 uint16_t port,
158 bool secure,
159 boost::beast::http::response<boost::beast::http::string_body>& resp,
160 boost::system::error_code& ec)
161 {
162 using namespace boost::asio;
163 using namespace boost::beast::http;
164 io_context& ios = getIoContext();
165 ip::tcp::resolver r{ios};
166 boost::beast::multi_buffer sb;
167
168 auto it = r.async_resolve(host, std::to_string(port), yield[ec]);
169 if (ec)
170 return;
171
172 resp.body().clear();
173 if (secure)
174 {
175 ssl::context ctx{ssl::context::sslv23};
176 ctx.set_verify_mode(ssl::verify_none);
177 ssl::stream<ip::tcp::socket> ss{ios, ctx};
178 async_connect(ss.next_layer(), it, yield[ec]);
179 if (ec)
180 return;
181 ss.async_handshake(ssl::stream_base::client, yield[ec]);
182 if (ec)
183 return;
184 boost::beast::http::async_write(ss, req, yield[ec]);
185 if (ec)
186 return;
187 async_read(ss, sb, resp, yield[ec]);
188 if (ec)
189 return;
190 }
191 else
192 {
193 ip::tcp::socket sock{ios};
194 async_connect(sock, it, yield[ec]);
195 if (ec)
196 return;
197 boost::beast::http::async_write(sock, req, yield[ec]);
198 if (ec)
199 return;
200 async_read(sock, sb, resp, yield[ec]);
201 if (ec)
202 return;
203 }
204 }
205
206 void
208 test::jtx::Env& env,
209 boost::asio::yield_context& yield,
210 bool secure,
211 boost::beast::http::response<boost::beast::http::string_body>& resp,
212 boost::system::error_code& ec)
213 {
214 auto const port = env.app().config()[Sections::kPortWs].get<std::uint16_t>(Keys::kPort);
215 auto ip = env.app().config()[Sections::kPortWs].get<std::string>(Keys::kIp);
216 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
217 doRequest(yield, makeWSUpgrade(*ip, *port), *ip, *port, secure, resp, ec);
218 }
219
220 void
222 test::jtx::Env& env,
223 boost::asio::yield_context& yield,
224 bool secure,
225 boost::beast::http::response<boost::beast::http::string_body>& resp,
226 boost::system::error_code& ec,
227 std::string const& body = "",
228 MyFields const& fields = {})
229 {
230 auto const port = env.app().config()[Sections::kPortRpc].get<std::uint16_t>(Keys::kPort);
231 auto const ip = env.app().config()[Sections::kPortRpc].get<std::string>(Keys::kIp);
232 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
233 doRequest(yield, makeHTTPRequest(*ip, *port, body, fields), *ip, *port, secure, resp, ec);
234 }
235
236 static auto
238 jtx::Env& env,
239 std::string const& proto,
240 std::string const& user,
241 std::string const& password,
242 bool subobject = false)
243 {
244 json::Value jrr;
245
247 if (!user.empty())
248 {
249 jp["admin_user"] = user;
250 if (subobject)
251 {
252 // special case of bad password..passed as object
254 jpi["admin_password"] = password;
255 jp["admin_password"] = jpi;
256 }
257 else
258 {
259 jp["admin_password"] = password;
260 }
261 }
262
263 if (proto.starts_with("h"))
264 {
265 auto jrc = makeJSONRPCClient(env.app().config());
266 jrr = jrc->invoke("ledger_accept", jp);
267 }
268 else
269 {
270 auto wsc = makeWSClient(env.app().config(), proto == "ws2");
271 jrr = wsc->invoke("ledger_accept", jp);
272 }
273
274 return jrr;
275 }
276
277 // ------------
278 // Test Cases
279 // ------------
280
281 void
282 testAdminRequest(std::string const& proto, bool admin, bool credentials)
283 {
284 testcase << "Admin request over " << proto << ", config "
285 << (admin ? "enabled" : "disabled") << ", credentials "
286 << (credentials ? "" : "not ") << "set";
287 using namespace jtx;
288 Env env{*this, makeConfig(proto, admin, credentials)};
289
290 json::Value jrr;
291 auto const protoWs = proto.starts_with("w");
292
293 // the set of checks we do are different depending
294 // on how the admin config options are set
295
296 if (admin && credentials)
297 {
298 auto const user = env.app()
301
302 auto const password = env.app()
305
306 // 1 - FAILS with wrong pass
307 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
308 jrr = makeAdminRequest(env, proto, *user, *password + "_")[jss::result];
309 BEAST_EXPECT(jrr["error"] == protoWs ? "forbidden" : "noPermission");
310 BEAST_EXPECT(
311 jrr["error_message"] == protoWs ? "Bad credentials."
312 : "You don't have permission for this command.");
313
314 // 2 - FAILS with password in an object
315 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
316 jrr = makeAdminRequest(env, proto, *user, *password, true)[jss::result];
317 BEAST_EXPECT(jrr["error"] == protoWs ? "forbidden" : "noPermission");
318 BEAST_EXPECT(
319 jrr["error_message"] == protoWs ? "Bad credentials."
320 : "You don't have permission for this command.");
321
322 // 3 - FAILS with wrong user
323 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
324 jrr = makeAdminRequest(env, proto, *user + "_", *password)[jss::result];
325 BEAST_EXPECT(jrr["error"] == protoWs ? "forbidden" : "noPermission");
326 BEAST_EXPECT(
327 jrr["error_message"] == protoWs ? "Bad credentials."
328 : "You don't have permission for this command.");
329
330 // 4 - FAILS no credentials
331 jrr = makeAdminRequest(env, proto, "", "")[jss::result];
332 BEAST_EXPECT(jrr["error"] == protoWs ? "forbidden" : "noPermission");
333 BEAST_EXPECT(
334 jrr["error_message"] == protoWs ? "Bad credentials."
335 : "You don't have permission for this command.");
336
337 // 5 - SUCCEEDS with proper credentials
338 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
339 jrr = makeAdminRequest(env, proto, *user, *password)[jss::result];
340 BEAST_EXPECT(jrr["status"] == "success");
341 }
342 else if (admin)
343 {
344 // 1 - SUCCEEDS with proper credentials
345 jrr = makeAdminRequest(env, proto, "u", "p")[jss::result];
346 BEAST_EXPECT(jrr["status"] == "success");
347
348 // 2 - SUCCEEDS without proper credentials
349 jrr = makeAdminRequest(env, proto, "", "")[jss::result];
350 BEAST_EXPECT(jrr["status"] == "success");
351 }
352 else
353 {
354 // 1 - FAILS - admin disabled
355 jrr = makeAdminRequest(env, proto, "", "")[jss::result];
356 BEAST_EXPECT(jrr["error"] == protoWs ? "forbidden" : "noPermission");
357 BEAST_EXPECT(
358 jrr["error_message"] == protoWs ? "Bad credentials."
359 : "You don't have permission for this command.");
360 }
361 }
362
363 void
364 testWSClientToHttpServer(boost::asio::yield_context& yield)
365 {
366 testcase("WS client to http server fails");
367 using namespace jtx;
368 Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
369 cfg->section(Sections::kPortWs).set(Keys::kProtocol, "http,https");
370 return cfg;
371 })};
372
373 // non-secure request
374 {
375 boost::system::error_code ec;
376 boost::beast::http::response<boost::beast::http::string_body> resp;
377 doWSRequest(env, yield, false, resp, ec);
378 if (!BEAST_EXPECTS(!ec, ec.message()))
379 return;
380 BEAST_EXPECT(resp.result() == boost::beast::http::status::unauthorized);
381 }
382
383 // secure request
384 {
385 boost::system::error_code ec;
386 boost::beast::http::response<boost::beast::http::string_body> resp;
387 doWSRequest(env, yield, true, resp, ec);
388 if (!BEAST_EXPECTS(!ec, ec.message()))
389 return;
390 BEAST_EXPECT(resp.result() == boost::beast::http::status::unauthorized);
391 }
392 }
393
394 void
395 testStatusRequest(boost::asio::yield_context& yield)
396 {
397 testcase("Status request");
398 using namespace jtx;
399 Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
400 cfg->section(Sections::kPortRpc).set(Keys::kProtocol, "ws2,wss2");
401 cfg->section(Sections::kPortWs).set(Keys::kProtocol, "http");
402 return cfg;
403 })};
404
405 // non-secure request
406 {
407 boost::system::error_code ec;
408 boost::beast::http::response<boost::beast::http::string_body> resp;
409 doHTTPRequest(env, yield, false, resp, ec);
410 if (!BEAST_EXPECTS(!ec, ec.message()))
411 return;
412 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
413 }
414
415 // secure request
416 {
417 boost::system::error_code ec;
418 boost::beast::http::response<boost::beast::http::string_body> resp;
419 doHTTPRequest(env, yield, true, resp, ec);
420 if (!BEAST_EXPECTS(!ec, ec.message()))
421 return;
422 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
423 }
424 }
425
426 void
427 testTruncatedWSUpgrade(boost::asio::yield_context& yield)
428 {
429 testcase("Partial WS upgrade request");
430 using namespace jtx;
431 using namespace boost::asio;
432 using namespace boost::beast::http;
433 Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
434 cfg->section(Sections::kPortWs).set(Keys::kProtocol, "ws2");
435 return cfg;
436 })};
437
438 auto const port = env.app().config()[Sections::kPortWs].get<std::uint16_t>(Keys::kPort);
439 auto const ip = env.app().config()[Sections::kPortWs].get<std::string>(Keys::kIp);
440
441 boost::system::error_code ec;
442 response<string_body> resp;
443 auto req = makeWSUpgrade(*ip, *port); // NOLINT(bugprone-unchecked-optional-access)
444
445 // truncate the request message to near the value of the version header
446 auto reqString = boost::lexical_cast<std::string>(req);
447 reqString.erase(reqString.find_last_of("13"), std::string::npos);
448
449 io_context& ios = getIoContext();
450 ip::tcp::resolver r{ios};
451 boost::beast::multi_buffer sb;
452
453 auto it = r.async_resolve(
454 *ip, std::to_string(*port), yield[ec]); // NOLINT(bugprone-unchecked-optional-access)
455 if (!BEAST_EXPECTS(!ec, ec.message()))
456 return;
457
458 ip::tcp::socket sock{ios};
459 async_connect(sock, it, yield[ec]);
460 if (!BEAST_EXPECTS(!ec, ec.message()))
461 return;
462 async_write(sock, boost::asio::buffer(reqString), yield[ec]);
463 if (!BEAST_EXPECTS(!ec, ec.message()))
464 return;
465 // since we've sent an incomplete request, the server will
466 // keep trying to read until it gives up (by timeout)
467 async_read(sock, sb, resp, yield[ec]);
468 BEAST_EXPECT(ec);
469 }
470
471 void
473 std::string const& clientProtocol,
474 std::string const& serverProtocol,
475 boost::asio::yield_context& yield)
476 {
477 // The essence of this test is to have a client and server configured
478 // out-of-phase with respect to ssl (secure client and insecure server
479 // or vice-versa)
480 testcase << "Connect fails: " << clientProtocol << " client to " << serverProtocol
481 << " server";
482 using namespace jtx;
483 Env env{*this, makeConfig(serverProtocol)};
484
485 boost::beast::http::response<boost::beast::http::string_body> resp;
486 boost::system::error_code ec;
487 if (clientProtocol.starts_with("h"))
488 {
489 doHTTPRequest(env, yield, clientProtocol == "https", resp, ec);
490 BEAST_EXPECT(ec);
491 }
492 else
493 {
494 doWSRequest(env, yield, clientProtocol == "wss" || clientProtocol == "wss2", resp, ec);
495 BEAST_EXPECT(ec);
496 }
497 }
498
499 void
500 testAuth(bool secure, boost::asio::yield_context& yield)
501 {
502 testcase << "Server with authorization, " << (secure ? "secure" : "non-secure");
503
504 using namespace test::jtx;
505 Env env{*this, envconfig([secure](std::unique_ptr<Config> cfg) {
506 (*cfg)[Sections::kPortRpc].set(Keys::kUser, "me");
507 (*cfg)[Sections::kPortRpc].set(Keys::kPassword, "secret");
508 (*cfg)[Sections::kPortRpc].set(Keys::kProtocol, secure ? "https" : "http");
509 if (secure)
510 (*cfg)[Sections::kPortWs].set(Keys::kProtocol, "http,ws");
511 return cfg;
512 })};
513
514 json::Value jr;
515 jr[jss::method] = "server_info";
516 boost::beast::http::response<boost::beast::http::string_body> resp;
517 boost::system::error_code ec;
518 doHTTPRequest(env, yield, secure, resp, ec, to_string(jr));
519 BEAST_EXPECT(resp.result() == boost::beast::http::status::forbidden);
520
521 MyFields auth;
522 auth.insert("Authorization", "");
523 doHTTPRequest(env, yield, secure, resp, ec, to_string(jr), auth);
524 BEAST_EXPECT(resp.result() == boost::beast::http::status::forbidden);
525
526 auth.set("Authorization", "Basic NOT-VALID");
527 doHTTPRequest(env, yield, secure, resp, ec, to_string(jr), auth);
528 BEAST_EXPECT(resp.result() == boost::beast::http::status::forbidden);
529
530 auth.set("Authorization", "Basic " + base64Encode("me:badpass"));
531 doHTTPRequest(env, yield, secure, resp, ec, to_string(jr), auth);
532 BEAST_EXPECT(resp.result() == boost::beast::http::status::forbidden);
533
534 auto const section = env.app().config().section(Sections::kPortRpc);
535 // NOLINTBEGIN(bugprone-unchecked-optional-access)
536 auto const user = section.get<std::string>(Keys::kUser).value();
537 auto const pass = section.get<std::string>(Keys::kPassword).value();
538 // NOLINTEND(bugprone-unchecked-optional-access)
539
540 // try with the correct user/pass, but not encoded
541 auth.set("Authorization", "Basic " + user + ":" + pass);
542 doHTTPRequest(env, yield, secure, resp, ec, to_string(jr), auth);
543 BEAST_EXPECT(resp.result() == boost::beast::http::status::forbidden);
544
545 // finally if we use the correct user/pass encoded, we should get a 200
546 auth.set("Authorization", "Basic " + base64Encode(user + ":" + pass));
547 doHTTPRequest(env, yield, secure, resp, ec, to_string(jr), auth);
548 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
549 BEAST_EXPECT(!resp.body().empty());
550 }
551
552 void
553 testLimit(boost::asio::yield_context& yield, int limit)
554 {
555 testcase << "Server with connection limit of " << limit;
556
557 using namespace test::jtx;
558 using namespace boost::asio;
559 using namespace boost::beast::http;
560 // Run the server with a single io thread so disconnectClient() below
561 // can deterministically drain the server's io_context (see its docs).
564 return cfg;
565 }))};
566
567 auto const section = env.app().config().section(Sections::kPortRpc);
568 // NOLINTBEGIN(bugprone-unchecked-optional-access)
569 auto const port = section.get<std::uint16_t>(Keys::kPort).value();
570 auto const ip = section.get<std::string>(Keys::kIp).value();
571 // NOLINTEND(bugprone-unchecked-optional-access)
572
573 boost::system::error_code ec;
574 io_context& ios = getIoContext();
575 ip::tcp::resolver r{ios};
576
577 json::Value jr;
578 jr[jss::method] = "server_info";
579
580 auto it = r.async_resolve(ip, std::to_string(port), yield[ec]);
581 BEAST_EXPECT(!ec);
582
584
585 // Env owns a persistent JSON-RPC HTTP client connection to port_rpc as
586 // part of startup, which counts against this port's connection limit.
587 // This test wants a known starting occupancy of zero, so for nonzero
588 // limits it deterministically drops that hidden client and waits for
589 // the server to register the disconnect before opening its own clients.
590 //
591 // Starting from zero is important because the port limit rejects once
592 // the incremented connection count reaches the configured limit. With a
593 // zero baseline and N = limit + 1 test-owned clients, exactly the last
594 // two requests should be rejected.
595 if (limit != 0)
596 BEAST_EXPECT(env.disconnectClient());
597
598 // For nonzero limits, go one past the limit. The port rejects at the
599 // limit, not only above it, so this yields the last two clients
600 // failing. For zero limit, pick an arbitrary nonzero number of clients
601 // and expect them all to succeed.
602
603 int const testTo = (limit == 0) ? 50 : limit + 1;
604 while (static_cast<int>(clients.size()) < testTo)
605 {
606 clients.emplace_back(ip::tcp::socket{ios}, boost::beast::multi_buffer{});
607 async_connect(clients.back().first, it, yield[ec]);
608 BEAST_EXPECT(!ec);
609 auto req = makeHTTPRequest(ip, port, to_string(jr), {});
610 async_write(clients.back().first, req, yield[ec]);
611 BEAST_EXPECT(!ec);
612 }
613
614 int successfulReads = 0;
615 for (auto& [soc, buf] : clients)
616 {
617 boost::beast::http::response<boost::beast::http::string_body> resp;
618 async_read(soc, buf, resp, yield[ec]);
619 if (!ec)
620 ++successfulReads;
621 }
622
623 // This test cares about the exact number of accepted requests, not which
624 // specific client observed the rejection. With a zero baseline (the
625 // hidden Env client dropped above), the server accepts until the
626 // connection count reaches the limit: all clients for limit 0, else
627 // limit - 1 of the limit + 1 clients (the last two are rejected).
628 int const expectedReads = (limit == 0) ? static_cast<int>(clients.size()) : limit - 1;
629 BEAST_EXPECT(successfulReads == expectedReads);
630 }
631
632 void
633 testWSHandoff(boost::asio::yield_context& yield)
634 {
635 testcase("Connection with WS handoff");
636
637 using namespace test::jtx;
638 Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
639 (*cfg)[Sections::kPortWs].set(Keys::kProtocol, "wss");
640 return cfg;
641 })};
642
643 auto const section = env.app().config().section(Sections::kPortWs);
644 // NOLINTBEGIN(bugprone-unchecked-optional-access)
645 auto const port = section.get<std::uint16_t>(Keys::kPort).value();
646 auto const ip = section.get<std::string>(Keys::kIp).value();
647 // NOLINTEND(bugprone-unchecked-optional-access)
648 boost::beast::http::response<boost::beast::http::string_body> resp;
649 boost::system::error_code ec;
650 doRequest(yield, makeWSUpgrade(ip, port), ip, port, true, resp, ec);
651 BEAST_EXPECT(resp.result() == boost::beast::http::status::switching_protocols);
652 BEAST_EXPECT(resp.contains("Upgrade") && resp["Upgrade"] == "websocket");
653 BEAST_EXPECT(resp.contains("Connection") && boost::iequals(resp["Connection"], "upgrade"));
654 }
655
656 void
657 testNoRPC(boost::asio::yield_context& yield)
658 {
659 testcase("Connection to port with no RPC enabled");
660
661 using namespace test::jtx;
662 Env env{*this};
663
664 auto const section = env.app().config().section(Sections::kPortWs);
665 // NOLINTBEGIN(bugprone-unchecked-optional-access)
666 auto const port = section.get<std::uint16_t>(Keys::kPort).value();
667 auto const ip = section.get<std::string>(Keys::kIp).value();
668 // NOLINTEND(bugprone-unchecked-optional-access)
669 boost::beast::http::response<boost::beast::http::string_body> resp;
670 boost::system::error_code ec;
671 // body content is required here to avoid being
672 // detected as a status request
673 doRequest(yield, makeHTTPRequest(ip, port, "foo", {}), ip, port, false, resp, ec);
674 BEAST_EXPECT(resp.result() == boost::beast::http::status::forbidden);
675 BEAST_EXPECT(resp.body() == "Forbidden\r\n");
676 }
677
678 void
679 testWSRequests(boost::asio::yield_context& yield)
680 {
681 testcase("WS client sends assorted input");
682
683 using namespace test::jtx;
684 using namespace boost::asio;
685 using namespace boost::beast::http;
686 Env env{*this};
687
688 auto const section = env.app().config().section(Sections::kPortWs);
689 // NOLINTBEGIN(bugprone-unchecked-optional-access)
690 auto const port = section.get<std::uint16_t>(Keys::kPort).value();
691 auto const ip = section.get<std::string>(Keys::kIp).value();
692 // NOLINTEND(bugprone-unchecked-optional-access)
693 boost::system::error_code ec;
694
695 io_context& ios = getIoContext();
696 ip::tcp::resolver r{ios};
697
698 auto it = r.async_resolve(ip, std::to_string(port), yield[ec]);
699 if (!BEAST_EXPECT(!ec))
700 return;
701
702 ip::tcp::socket sock{ios};
703 async_connect(sock, it, yield[ec]);
704 if (!BEAST_EXPECT(!ec))
705 return;
706
707 boost::beast::websocket::stream<boost::asio::ip::tcp::socket&> ws{sock};
708 ws.handshake(ip + ":" + std::to_string(port), "/");
709
710 // helper lambda, used below
711 auto sendAndParse = [&](std::string const& req) -> json::Value {
712 ws.async_write_some(true, buffer(req), yield[ec]);
713 if (!BEAST_EXPECT(!ec))
715
716 boost::beast::multi_buffer sb;
717 ws.async_read(sb, yield[ec]);
718 if (!BEAST_EXPECT(!ec))
720
721 json::Value resp;
722 json::Reader jr;
723 if (!BEAST_EXPECT(jr.parse(
724 boost::lexical_cast<std::string>(boost::beast::make_printable(sb.data())),
725 resp)))
727 sb.consume(sb.size());
728 return resp;
729 };
730
731 { // send invalid json
732 auto resp = sendAndParse("NOT JSON");
733 BEAST_EXPECT(resp.isMember(jss::error) && resp[jss::error] == "jsonInvalid");
734 BEAST_EXPECT(!resp.isMember(jss::status));
735 }
736
737 { // send incorrect json (method and command fields differ)
738 json::Value jv;
739 jv[jss::command] = "foo";
740 jv[jss::method] = "bar";
741 auto resp = sendAndParse(to_string(jv));
742 BEAST_EXPECT(resp.isMember(jss::error) && resp[jss::error] == "missingCommand");
743 BEAST_EXPECT(resp.isMember(jss::status) && resp[jss::status] == "error");
744 }
745
746 { // send a ping (not an error)
747 json::Value jv;
748 jv[jss::command] = "ping";
749 auto resp = sendAndParse(to_string(jv));
750 BEAST_EXPECT(resp.isMember(jss::status) && resp[jss::status] == "success");
751 BEAST_EXPECT(
752 resp.isMember(jss::result) && resp[jss::result].isMember(jss::role) &&
753 resp[jss::result][jss::role] == "admin");
754 }
755 }
756
757 void
758 testAmendmentWarning(boost::asio::yield_context& yield)
759 {
760 testcase("Status request over WS and RPC with/without Amendment Warning");
761 using namespace jtx;
762 using namespace boost::asio;
763 using namespace boost::beast::http;
764 Env env{
765 *this,
766 validator(
768 cfg->section(Sections::kPortRpc).set(Keys::kProtocol, "http");
769 return cfg;
770 }),
771 "")};
772
773 env.close();
774
775 // advance the ledger so that server status
776 // sees a published ledger -- without this, we get a status
777 // failure message about no published ledgers
779
780 // make an RPC server info request and look for
781 // amendment warning status
782 auto si = env.rpc("server_info")[jss::result];
783 BEAST_EXPECT(si.isMember(jss::info));
784 BEAST_EXPECT(!si[jss::info].isMember(jss::amendment_blocked));
785 BEAST_EXPECT(env.app().getOPs().getConsensusInfo()["validating"] == true);
786 BEAST_EXPECT(!si.isMember(jss::warnings));
787
788 // make an RPC server state request and look for
789 // amendment warning status
790 si = env.rpc("server_state")[jss::result];
791 BEAST_EXPECT(si.isMember(jss::state));
792 BEAST_EXPECT(!si[jss::state].isMember(jss::amendment_blocked));
793 BEAST_EXPECT(env.app().getOPs().getConsensusInfo()["validating"] == true);
794 BEAST_EXPECT(!si[jss::state].isMember(jss::warnings));
795
796 auto const portWs = env.app().config()[Sections::kPortWs].get<std::uint16_t>(Keys::kPort);
797 auto const ipWs = env.app().config()[Sections::kPortWs].get<std::string>(Keys::kIp);
798
799 boost::system::error_code ec;
800 response<string_body> resp;
801
802 doRequest(
803 yield,
804 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
805 makeHTTPRequest(*ipWs, *portWs, "", {}),
806 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
807 *ipWs,
808 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
809 *portWs,
810 false,
811 resp,
812 ec);
813
814 if (!BEAST_EXPECTS(!ec, ec.message()))
815 return;
816 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
817 BEAST_EXPECT(resp.body().contains("connectivity is working."));
818
819 // mark the Network as having an Amendment Warning, but won't fail
820 env.app().getOPs().setAmendmentWarned();
821 env.app().getOPs().beginConsensus(env.closed()->header().hash, {});
822
823 // consensus doesn't change
824 BEAST_EXPECT(env.app().getOPs().getConsensusInfo()["validating"] == true);
825
826 // RPC request server_info again, now unsupported majority should be
827 // returned
828 si = env.rpc("server_info")[jss::result];
829 BEAST_EXPECT(si.isMember(jss::info));
830 BEAST_EXPECT(!si[jss::info].isMember(jss::amendment_blocked));
831 BEAST_EXPECT(
832 si[jss::info].isMember(jss::warnings) && si[jss::info][jss::warnings].isArray() &&
833 si[jss::info][jss::warnings].size() == 1 &&
834 si[jss::info][jss::warnings][0u][jss::id].asInt() == WarnRpcUnsupportedMajority);
835
836 // RPC request server_state again, now unsupported majority should be
837 // returned
838 si = env.rpc("server_state")[jss::result];
839 BEAST_EXPECT(si.isMember(jss::state));
840 BEAST_EXPECT(!si[jss::state].isMember(jss::amendment_blocked));
841 BEAST_EXPECT(
842 si[jss::state].isMember(jss::warnings) && si[jss::state][jss::warnings].isArray() &&
843 si[jss::state][jss::warnings].size() == 1 &&
844 si[jss::state][jss::warnings][0u][jss::id].asInt() == WarnRpcUnsupportedMajority);
845
846 // but status does not indicate a problem
847 doRequest(
848 yield,
849 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
850 makeHTTPRequest(*ipWs, *portWs, "", {}),
851 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
852 *ipWs,
853 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
854 *portWs,
855 false,
856 resp,
857 ec);
858
859 if (!BEAST_EXPECTS(!ec, ec.message()))
860 return;
861 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
862 BEAST_EXPECT(resp.body().contains("connectivity is working."));
863
864 // with ELB_SUPPORT, status still does not indicate a problem
865 env.app().config().elbSupport = true;
866
867 doRequest(
868 yield,
869 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
870 makeHTTPRequest(*ipWs, *portWs, "", {}),
871 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
872 *ipWs,
873 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
874 *portWs,
875 false,
876 resp,
877 ec);
878
879 if (!BEAST_EXPECTS(!ec, ec.message()))
880 return;
881 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
882 BEAST_EXPECT(resp.body().contains("connectivity is working."));
883 }
884
885 void
886 testAmendmentBlock(boost::asio::yield_context& yield)
887 {
888 testcase("Status request over WS and RPC with/without Amendment Block");
889 using namespace jtx;
890 using namespace boost::asio;
891 using namespace boost::beast::http;
892 Env env{
893 *this,
894 validator(
896 cfg->section(Sections::kPortRpc).set(Keys::kProtocol, "http");
897 return cfg;
898 }),
899 "")};
900
901 env.close();
902
903 // advance the ledger so that server status
904 // sees a published ledger -- without this, we get a status
905 // failure message about no published ledgers
907
908 // make an RPC server info request and look for
909 // amendment_blocked status
910 auto si = env.rpc("server_info")[jss::result];
911 BEAST_EXPECT(si.isMember(jss::info));
912 BEAST_EXPECT(!si[jss::info].isMember(jss::amendment_blocked));
913 BEAST_EXPECT(env.app().getOPs().getConsensusInfo()["validating"] == true);
914 BEAST_EXPECT(!si.isMember(jss::warnings));
915
916 // make an RPC server state request and look for
917 // amendment_blocked status
918 si = env.rpc("server_state")[jss::result];
919 BEAST_EXPECT(si.isMember(jss::state));
920 BEAST_EXPECT(!si[jss::state].isMember(jss::amendment_blocked));
921 BEAST_EXPECT(env.app().getOPs().getConsensusInfo()["validating"] == true);
922 BEAST_EXPECT(!si[jss::state].isMember(jss::warnings));
923
924 auto const portWs = env.app().config()[Sections::kPortWs].get<std::uint16_t>(Keys::kPort);
925 auto const ipWs = env.app().config()[Sections::kPortWs].get<std::string>(Keys::kIp);
926
927 boost::system::error_code ec;
928 response<string_body> resp;
929
930 doRequest(
931 yield,
932 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
933 makeHTTPRequest(*ipWs, *portWs, "", {}),
934 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
935 *ipWs,
936 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
937 *portWs,
938 false,
939 resp,
940 ec);
941
942 if (!BEAST_EXPECTS(!ec, ec.message()))
943 return;
944 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
945 BEAST_EXPECT(resp.body().contains("connectivity is working."));
946
947 // mark the Network as Amendment Blocked, but still won't fail until
948 // ELB is enabled (next step)
950 env.app().getOPs().beginConsensus(env.closed()->header().hash, {});
951
952 // consensus now sees validation disabled
953 BEAST_EXPECT(env.app().getOPs().getConsensusInfo()["validating"] == false);
954
955 // RPC request server_info again, now AB should be returned
956 si = env.rpc("server_info")[jss::result];
957 BEAST_EXPECT(si.isMember(jss::info));
958 BEAST_EXPECT(
959 si[jss::info].isMember(jss::amendment_blocked) &&
960 si[jss::info][jss::amendment_blocked] == true);
961 BEAST_EXPECT(
962 si[jss::info].isMember(jss::warnings) && si[jss::info][jss::warnings].isArray() &&
963 si[jss::info][jss::warnings].size() == 1 &&
964 si[jss::info][jss::warnings][0u][jss::id].asInt() == WarnRpcAmendmentBlocked);
965
966 // RPC request server_state again, now AB should be returned
967 si = env.rpc("server_state")[jss::result];
968 BEAST_EXPECT(
969 si[jss::state].isMember(jss::amendment_blocked) &&
970 si[jss::state][jss::amendment_blocked] == true);
971 BEAST_EXPECT(
972 si[jss::state].isMember(jss::warnings) && si[jss::state][jss::warnings].isArray() &&
973 si[jss::state][jss::warnings].size() == 1 &&
974 si[jss::state][jss::warnings][0u][jss::id].asInt() == WarnRpcAmendmentBlocked);
975
976 // but status does not indicate because it still relies on ELB
977 // being enabled
978 doRequest(
979 yield,
980 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
981 makeHTTPRequest(*ipWs, *portWs, "", {}),
982 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
983 *ipWs,
984 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
985 *portWs,
986 false,
987 resp,
988 ec);
989
990 if (!BEAST_EXPECTS(!ec, ec.message()))
991 return;
992 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
993 BEAST_EXPECT(resp.body().contains("connectivity is working."));
994
995 env.app().config().elbSupport = true;
996
997 doRequest(
998 yield,
999 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
1000 makeHTTPRequest(*ipWs, *portWs, "", {}),
1001 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
1002 *ipWs,
1003 // NOLINTNEXTLINE(bugprone-unchecked-optional-access)
1004 *portWs,
1005 false,
1006 resp,
1007 ec);
1008
1009 if (!BEAST_EXPECTS(!ec, ec.message()))
1010 return;
1011 BEAST_EXPECT(resp.result() == boost::beast::http::status::internal_server_error);
1012 BEAST_EXPECT(resp.body().contains("cannot accept clients:"));
1013 BEAST_EXPECT(resp.body().contains("Server version too old"));
1014 }
1015
1016 void
1017 testRPCRequests(boost::asio::yield_context& yield)
1018 {
1019 testcase("RPC client sends assorted input");
1020
1021 using namespace test::jtx;
1022 Env env{*this};
1023
1024 boost::system::error_code ec;
1025 {
1026 boost::beast::http::response<boost::beast::http::string_body> resp;
1027 doHTTPRequest(env, yield, false, resp, ec, "{}");
1028 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1029 BEAST_EXPECT(resp.body() == "Unable to parse request: \r\n");
1030 }
1031
1032 {
1033 boost::beast::http::response<boost::beast::http::string_body> resp;
1034 json::Value jv;
1035 jv["invalid"] = 1;
1036 doHTTPRequest(env, yield, false, resp, ec, to_string(jv));
1037 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1038 BEAST_EXPECT(resp.body() == "Null method\r\n");
1039 }
1040
1041 {
1042 boost::beast::http::response<boost::beast::http::string_body> resp;
1044 jv.append("invalid");
1045 doHTTPRequest(env, yield, false, resp, ec, to_string(jv));
1046 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1047 BEAST_EXPECT(resp.body() == "Unable to parse request: \r\n");
1048 }
1049
1050 {
1051 boost::beast::http::response<boost::beast::http::string_body> resp;
1053 json::Value j;
1054 j["invalid"] = 1;
1055 jv.append(j);
1056 doHTTPRequest(env, yield, false, resp, ec, to_string(jv));
1057 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1058 BEAST_EXPECT(resp.body() == "Unable to parse request: \r\n");
1059 }
1060
1061 {
1062 boost::beast::http::response<boost::beast::http::string_body> resp;
1063 json::Value jv;
1064 jv[jss::method] = "batch";
1065 jv[jss::params] = 2;
1066 doHTTPRequest(env, yield, false, resp, ec, to_string(jv));
1067 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1068 BEAST_EXPECT(resp.body() == "Malformed batch request\r\n");
1069 }
1070
1071 {
1072 boost::beast::http::response<boost::beast::http::string_body> resp;
1073 json::Value jv;
1074 jv[jss::method] = "batch";
1075 jv[jss::params] = json::ValueType::Object;
1076 jv[jss::params]["invalid"] = 3;
1077 doHTTPRequest(env, yield, false, resp, ec, to_string(jv));
1078 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1079 BEAST_EXPECT(resp.body() == "Malformed batch request\r\n");
1080 }
1081
1082 json::Value jv;
1083 {
1084 boost::beast::http::response<boost::beast::http::string_body> resp;
1085 jv[jss::method] = json::ValueType::Null;
1086 doHTTPRequest(env, yield, false, resp, ec, to_string(jv));
1087 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1088 BEAST_EXPECT(resp.body() == "Null method\r\n");
1089 }
1090
1091 {
1092 boost::beast::http::response<boost::beast::http::string_body> resp;
1093 jv[jss::method] = 1;
1094 doHTTPRequest(env, yield, false, resp, ec, to_string(jv));
1095 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1096 BEAST_EXPECT(resp.body() == "method is not string\r\n");
1097 }
1098
1099 {
1100 boost::beast::http::response<boost::beast::http::string_body> resp;
1101 jv[jss::method] = "";
1102 doHTTPRequest(env, yield, false, resp, ec, to_string(jv));
1103 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1104 BEAST_EXPECT(resp.body() == "method is empty\r\n");
1105 }
1106
1107 {
1108 boost::beast::http::response<boost::beast::http::string_body> resp;
1109 jv[jss::method] = "some_method";
1110 jv[jss::params] = "params";
1111 doHTTPRequest(env, yield, false, resp, ec, to_string(jv));
1112 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1113 BEAST_EXPECT(resp.body() == "params unparsable\r\n");
1114 }
1115
1116 {
1117 boost::beast::http::response<boost::beast::http::string_body> resp;
1118 jv[jss::params] = json::ValueType::Array;
1119 jv[jss::params][0u] = "not an object";
1120 doHTTPRequest(env, yield, false, resp, ec, to_string(jv));
1121 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1122 BEAST_EXPECT(resp.body() == "params unparsable\r\n");
1123 }
1124 }
1125
1126 void
1127 testStatusNotOkay(boost::asio::yield_context& yield)
1128 {
1129 testcase("Server status not okay");
1130
1131 using namespace test::jtx;
1132 Env env{*this, envconfig([](std::unique_ptr<Config> cfg) {
1133 cfg->elbSupport = true;
1134 return cfg;
1135 })};
1136
1137 // raise the fee so that the server is considered overloaded
1138 env.app().getFeeTrack().raiseLocalFee();
1139
1140 boost::beast::http::response<boost::beast::http::string_body> resp;
1141 boost::system::error_code ec;
1142 doHTTPRequest(env, yield, false, resp, ec);
1143 BEAST_EXPECT(resp.result() == boost::beast::http::status::internal_server_error);
1144 std::regex const body{"Server cannot accept clients"};
1145 BEAST_EXPECT(std::regex_search(resp.body(), body));
1146 }
1147
1148public:
1149 void
1150 run() override
1151 {
1152 for (auto it : {"http", "ws", "ws2"})
1153 {
1154 testAdminRequest(it, true, true);
1155 testAdminRequest(it, true, false);
1156 testAdminRequest(it, false, false);
1157 }
1158
1159 yieldTo([&](boost::asio::yield_context& yield) {
1161 testStatusRequest(yield);
1163
1164 // these are secure/insecure protocol pairs, i.e. for
1165 // each item, the second value is the secure or insecure equivalent
1166 testCantConnect("ws", "wss", yield);
1167 testCantConnect("ws2", "wss2", yield);
1168 testCantConnect("http", "https", yield);
1169 testCantConnect("wss", "ws", yield);
1170 testCantConnect("wss2", "ws2", yield);
1171 testCantConnect("https", "http", yield);
1172
1173 testAmendmentWarning(yield);
1174 testAmendmentBlock(yield);
1175 testAuth(false, yield);
1176 testAuth(true, yield);
1177 testLimit(yield, 5);
1178 testLimit(yield, 0);
1179 testWSHandoff(yield);
1180 testNoRPC(yield);
1181 testWSRequests(yield);
1182 testRPCRequests(yield);
1183 testStatusNotOkay(yield);
1184 });
1185 }
1186};
1187
1188BEAST_DEFINE_TESTSUITE(ServerStatus, server, xrpl);
1189
1190} // namespace xrpl::test
T back(T... args)
Mix-in to support tests using asio coroutines.
Definition yield_to.h:30
boost::asio::io_context & getIoContext()
Return the io_context associated with the object.
Definition yield_to.h:67
void yieldTo(F0 &&f0, FN &&... fn)
Run one or more functions, each in a coroutine.
Definition yield_to.h:107
A testsuite class.
Definition suite.h:52
void pass()
Record a successful test condition.
Definition suite.h:532
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:155
Unserialize a JSON document into a Value.
Definition json_reader.h:20
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Represents a JSON value.
Definition json_value.h:117
Value & append(Value const &value)
Append value to array at the end.
virtual Config & config()=0
Section & section(std::string const &name)
Returns the section with the given name.
virtual void setAmendmentBlocked()=0
virtual json::Value getConsensusInfo()=0
virtual bool beginConsensus(uint256 const &netLCL, std::unique_ptr< std::stringstream > const &clog)=0
virtual void setAmendmentWarned()=0
std::optional< T > get(std::string const &name) const
virtual NetworkOPs & getOPs()=0
virtual LoadFeeTrack & getFeeTrack()=0
virtual LedgerMaster & getLedgerMaster()=0
void testWSClientToHttpServer(boost::asio::yield_context &yield)
void doWSRequest(test::jtx::Env &env, boost::asio::yield_context &yield, bool secure, boost::beast::http::response< boost::beast::http::string_body > &resp, boost::system::error_code &ec)
void doHTTPRequest(test::jtx::Env &env, boost::asio::yield_context &yield, bool secure, boost::beast::http::response< boost::beast::http::string_body > &resp, boost::system::error_code &ec, std::string const &body="", MyFields const &fields={})
void testAmendmentBlock(boost::asio::yield_context &yield)
static auto makeWSUpgrade(std::string const &host, uint16_t port)
void testNoRPC(boost::asio::yield_context &yield)
void testWSHandoff(boost::asio::yield_context &yield)
void testTruncatedWSUpgrade(boost::asio::yield_context &yield)
void testAdminRequest(std::string const &proto, bool admin, bool credentials)
void testRPCRequests(boost::asio::yield_context &yield)
void testStatusNotOkay(boost::asio::yield_context &yield)
static auto makeAdminRequest(jtx::Env &env, std::string const &proto, std::string const &user, std::string const &password, bool subobject=false)
void run() override
Runs the suite.
void testCantConnect(std::string const &clientProtocol, std::string const &serverProtocol, boost::asio::yield_context &yield)
void testAmendmentWarning(boost::asio::yield_context &yield)
void doRequest(boost::asio::yield_context &yield, boost::beast::http::request< boost::beast::http::string_body > const &req, std::string const &host, uint16_t port, bool secure, boost::beast::http::response< boost::beast::http::string_body > &resp, boost::system::error_code &ec)
void testAuth(bool secure, boost::asio::yield_context &yield)
void testLimit(boost::asio::yield_context &yield, int limit)
void testWSRequests(boost::asio::yield_context &yield)
static auto makeHTTPRequest(std::string const &host, uint16_t port, std::string const &body, MyFields const &fields)
static auto makeConfig(std::string const &proto, bool admin=true, bool credentials=false)
void testStatusRequest(boost::asio::yield_context &yield)
A transaction testing environment.
Definition Env.h:161
Application & app()
Definition Env.h:300
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:133
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
Definition Env.cpp:127
bool disconnectClient(std::chrono::steady_clock::duration timeout=std::chrono::seconds{1})
Disconnect the Env's built-in client and wait for the server to register the dropped connection.
Definition Env.h:514
json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition Env.h:1056
T data(T... args)
T emplace_back(T... args)
T empty(T... args)
@ 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
std::unique_ptr< Config > singleThreadIo(std::unique_ptr< Config >)
Definition envconfig.cpp:98
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition envconfig.h:37
std::unique_ptr< Config > validator(std::unique_ptr< Config >, std::string const &)
adjust configuration with params needed to be a validator
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
std::unique_ptr< AbstractClient > makeJSONRPCClient(Config const &cfg, unsigned rpcVersion)
Returns a client using JSON-RPC over HTTP/S.
char const * getEnvLocalhostAddr()
Definition envconfig.h:15
std::unique_ptr< WSClient > makeWSClient(Config const &cfg, bool v2, unsigned rpcVersion, std::unordered_map< std::string, std::string > const &headers)
Returns a client operating through WebSockets/S.
Definition WSClient.cpp:371
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ WarnRpcAmendmentBlocked
Definition ErrorCodes.h:159
@ WarnRpcUnsupportedMajority
Definition ErrorCodes.h:158
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:651
std::string base64Encode(std::uint8_t const *data, std::size_t len)
T regex_search(T... args)
T size(T... args)
T starts_with(T... args)
static constexpr auto kProtocol
Definition Constants.h:147
static constexpr auto kLimit
Definition Constants.h:118
static constexpr auto kAdminPassword
Definition Constants.h:87
static constexpr auto kUser
Definition Constants.h:177
static constexpr auto kAdmin
Definition Constants.h:86
static constexpr auto kPort
Definition Constants.h:145
static constexpr auto kIp
Definition Constants.h:114
static constexpr auto kAdminUser
Definition Constants.h:88
static constexpr auto kPassword
Definition Constants.h:142
static constexpr auto kPortWs
Definition Constants.h:48
static constexpr auto kServer
Definition Constants.h:56
static constexpr auto kPortRpc
Definition Constants.h:47
T to_string(T... args)