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