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