1#include <test/jtx/Env.h>
2#include <test/jtx/JSONRPCClient.h>
3#include <test/jtx/WSClient.h>
4#include <test/jtx/envconfig.h>
6#include <xrpld/app/ledger/LedgerMaster.h>
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>
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>
52 class MyFields :
public boost::beast::http::fields
59 auto const sectionName =
76 boost::starts_with(proto,
"h") ?
"ws" :
"http");
97 using namespace boost::beast::http;
98 request<string_body> req;
103 req.insert(
"User-Agent",
"test");
104 req.method(boost::beast::http::verb::get);
105 req.insert(
"Upgrade",
"websocket");
116 req.insert(
"Sec-WebSocket-Version",
"13");
117 req.insert(boost::beast::http::field::connection,
"upgrade");
129 using namespace boost::beast::http;
130 request<string_body> req;
134 for (
auto const& f : fields)
135 req.insert(f.name(), f.value());
137 req.insert(
"User-Agent",
"test");
140 req.method(boost::beast::http::verb::get);
144 req.method(boost::beast::http::verb::post);
145 req.insert(
"Content-Type",
"application/json; charset=UTF-8");
148 req.prepare_payload();
155 boost::asio::yield_context& yield,
156 boost::beast::http::request<boost::beast::http::string_body>
const& req,
160 boost::beast::http::response<boost::beast::http::string_body>& resp,
161 boost::system::error_code& ec)
164 using namespace boost::beast::http;
166 ip::tcp::resolver r{ios};
167 boost::beast::multi_buffer sb;
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]);
182 ss.async_handshake(ssl::stream_base::client, yield[ec]);
185 boost::beast::http::async_write(ss, req, yield[ec]);
188 async_read(ss, sb, resp, yield[ec]);
194 ip::tcp::socket sock{ios};
195 async_connect(sock, it, yield[ec]);
198 boost::beast::http::async_write(sock, req, yield[ec]);
201 async_read(sock, sb, resp, yield[ec]);
212 boost::asio::yield_context& yield,
214 boost::beast::http::response<boost::beast::http::string_body>& resp,
215 boost::system::error_code& ec)
227 boost::asio::yield_context& yield,
229 boost::beast::http::response<boost::beast::http::string_body>& resp,
230 boost::system::error_code& ec,
247 bool subobject =
false)
254 jp[
"admin_user"] = user;
259 jpi[
"admin_password"] = password;
260 jp[
"admin_password"] = jpi;
264 jp[
"admin_password"] = password;
268 if (boost::starts_with(proto,
"h"))
271 jrr = jrc->invoke(
"ledger_accept", jp);
276 jrr = wsc->invoke(
"ledger_accept", jp);
289 testcase <<
"Admin request over " << proto <<
", config "
290 << (admin ?
"enabled" :
"disabled") <<
", credentials "
296 auto const protoWs = boost::starts_with(proto,
"w");
303 auto const user = env.
app()
307 auto const password = env.
app()
314 BEAST_EXPECT(jrr[
"error"] == protoWs ?
"forbidden" :
"noPermission");
316 jrr[
"error_message"] == protoWs ?
"Bad credentials."
317 :
"You don't have permission for this command.");
322 BEAST_EXPECT(jrr[
"error"] == protoWs ?
"forbidden" :
"noPermission");
324 jrr[
"error_message"] == protoWs ?
"Bad credentials."
325 :
"You don't have permission for this command.");
330 BEAST_EXPECT(jrr[
"error"] == protoWs ?
"forbidden" :
"noPermission");
332 jrr[
"error_message"] == protoWs ?
"Bad credentials."
333 :
"You don't have permission for this command.");
337 BEAST_EXPECT(jrr[
"error"] == protoWs ?
"forbidden" :
"noPermission");
339 jrr[
"error_message"] == protoWs ?
"Bad credentials."
340 :
"You don't have permission for this command.");
345 BEAST_EXPECT(jrr[
"status"] ==
"success");
351 BEAST_EXPECT(jrr[
"status"] ==
"success");
355 BEAST_EXPECT(jrr[
"status"] ==
"success");
361 BEAST_EXPECT(jrr[
"error"] == protoWs ?
"forbidden" :
"noPermission");
363 jrr[
"error_message"] == protoWs ?
"Bad credentials."
364 :
"You don't have permission for this command.");
371 testcase(
"WS client to http server fails");
380 boost::system::error_code ec;
381 boost::beast::http::response<boost::beast::http::string_body> resp;
383 if (!BEAST_EXPECTS(!ec, ec.message()))
385 BEAST_EXPECT(resp.result() == boost::beast::http::status::unauthorized);
390 boost::system::error_code ec;
391 boost::beast::http::response<boost::beast::http::string_body> resp;
393 if (!BEAST_EXPECTS(!ec, ec.message()))
395 BEAST_EXPECT(resp.result() == boost::beast::http::status::unauthorized);
412 boost::system::error_code ec;
413 boost::beast::http::response<boost::beast::http::string_body> resp;
415 if (!BEAST_EXPECTS(!ec, ec.message()))
417 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
422 boost::system::error_code ec;
423 boost::beast::http::response<boost::beast::http::string_body> resp;
425 if (!BEAST_EXPECTS(!ec, ec.message()))
427 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
434 testcase(
"Partial WS upgrade request");
437 using namespace boost::beast::http;
446 boost::system::error_code ec;
447 response<string_body> resp;
451 auto reqString = boost::lexical_cast<std::string>(req);
452 reqString.erase(reqString.find_last_of(
"13"), std::string::npos);
455 ip::tcp::resolver r{ios};
456 boost::beast::multi_buffer sb;
458 auto it = r.async_resolve(
460 if (!BEAST_EXPECTS(!ec, ec.message()))
463 ip::tcp::socket sock{ios};
464 async_connect(sock, it, yield[ec]);
465 if (!BEAST_EXPECTS(!ec, ec.message()))
467 async_write(sock, boost::asio::buffer(reqString), yield[ec]);
468 if (!BEAST_EXPECTS(!ec, ec.message()))
472 async_read(sock, sb, resp, yield[ec]);
480 boost::asio::yield_context& yield)
485 testcase <<
"Connect fails: " << clientProtocol <<
" client to " << serverProtocol
490 boost::beast::http::response<boost::beast::http::string_body> resp;
491 boost::system::error_code ec;
492 if (boost::starts_with(clientProtocol,
"h"))
494 doHTTPRequest(env, yield, clientProtocol ==
"https", resp, ec);
499 doWSRequest(env, yield, clientProtocol ==
"wss" || clientProtocol ==
"wss2", resp, ec);
505 testAuth(
bool secure, boost::asio::yield_context& yield)
507 testcase <<
"Server with authorization, " << (secure ?
"secure" :
"non-secure");
520 jr[jss::method] =
"server_info";
521 boost::beast::http::response<boost::beast::http::string_body> resp;
522 boost::system::error_code ec;
524 BEAST_EXPECT(resp.result() == boost::beast::http::status::forbidden);
527 auth.insert(
"Authorization",
"");
529 BEAST_EXPECT(resp.result() == boost::beast::http::status::forbidden);
531 auth.set(
"Authorization",
"Basic NOT-VALID");
533 BEAST_EXPECT(resp.result() == boost::beast::http::status::forbidden);
535 auth.set(
"Authorization",
"Basic " +
base64Encode(
"me:badpass"));
537 BEAST_EXPECT(resp.result() == boost::beast::http::status::forbidden);
546 auth.set(
"Authorization",
"Basic " + user +
":" +
pass);
548 BEAST_EXPECT(resp.result() == boost::beast::http::status::forbidden);
553 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
554 BEAST_EXPECT(!resp.body().empty());
560 testcase <<
"Server with connection limit of " << limit;
564 using namespace boost::beast::http;
576 boost::system::error_code ec;
578 ip::tcp::resolver r{ios};
581 jr[jss::method] =
"server_info";
587 int connectionCount{1};
595 int const testTo = (limit == 0) ? 50 : limit + 1;
596 while (connectionCount < testTo)
598 clients.
emplace_back(ip::tcp::socket{ios}, boost::beast::multi_buffer{});
599 async_connect(clients.
back().first, it, yield[ec]);
602 async_write(clients.
back().first, req, yield[ec]);
608 for (
auto& [soc, buf] : clients)
610 boost::beast::http::response<boost::beast::http::string_body> resp;
611 async_read(soc, buf, resp, yield[ec]);
615 BEAST_EXPECT((limit == 0 || readCount < limit - 1) ? (!ec) :
bool(ec));
622 testcase(
"Connection with WS handoff");
635 boost::beast::http::response<boost::beast::http::string_body> resp;
636 boost::system::error_code 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"));
646 testcase(
"Connection to port with no RPC enabled");
656 boost::beast::http::response<boost::beast::http::string_body> resp;
657 boost::system::error_code ec;
661 BEAST_EXPECT(resp.result() == boost::beast::http::status::forbidden);
662 BEAST_EXPECT(resp.body() ==
"Forbidden\r\n");
668 testcase(
"WS client sends assorted input");
672 using namespace boost::beast::http;
680 boost::system::error_code ec;
683 ip::tcp::resolver r{ios};
686 if (!BEAST_EXPECT(!ec))
689 ip::tcp::socket sock{ios};
690 async_connect(sock, it, yield[ec]);
691 if (!BEAST_EXPECT(!ec))
694 boost::beast::websocket::stream<boost::asio::ip::tcp::socket&> ws{sock};
699 ws.async_write_some(
true, buffer(req), yield[ec]);
700 if (!BEAST_EXPECT(!ec))
703 boost::beast::multi_buffer sb;
704 ws.async_read(sb, yield[ec]);
705 if (!BEAST_EXPECT(!ec))
710 if (!BEAST_EXPECT(jr.
parse(
711 boost::lexical_cast<std::string>(boost::beast::make_printable(sb.data())),
714 sb.consume(sb.size());
719 auto resp = sendAndParse(
"NOT JSON");
720 BEAST_EXPECT(resp.isMember(jss::error) && resp[jss::error] ==
"jsonInvalid");
721 BEAST_EXPECT(!resp.isMember(jss::status));
726 jv[jss::command] =
"foo";
727 jv[jss::method] =
"bar";
729 BEAST_EXPECT(resp.isMember(jss::error) && resp[jss::error] ==
"missingCommand");
730 BEAST_EXPECT(resp.isMember(jss::status) && resp[jss::status] ==
"error");
735 jv[jss::command] =
"ping";
737 BEAST_EXPECT(resp.isMember(jss::status) && resp[jss::status] ==
"success");
739 resp.isMember(jss::result) && resp[jss::result].isMember(jss::role) &&
740 resp[jss::result][jss::role] ==
"admin");
747 testcase(
"Status request over WS and RPC with/without Amendment Warning");
750 using namespace boost::beast::http;
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));
773 BEAST_EXPECT(!si.isMember(jss::warnings));
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));
781 BEAST_EXPECT(!si[jss::state].isMember(jss::warnings));
786 boost::system::error_code ec;
787 response<string_body> resp;
801 if (!BEAST_EXPECTS(!ec, ec.message()))
803 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
804 BEAST_EXPECT(resp.body().contains(
"connectivity is working."));
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));
819 si[jss::info].isMember(jss::warnings) && si[jss::info][jss::warnings].isArray() &&
820 si[jss::info][jss::warnings].size() == 1 &&
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));
829 si[jss::state].isMember(jss::warnings) && si[jss::state][jss::warnings].isArray() &&
830 si[jss::state][jss::warnings].size() == 1 &&
846 if (!BEAST_EXPECTS(!ec, ec.message()))
848 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
849 BEAST_EXPECT(resp.body().contains(
"connectivity is working."));
866 if (!BEAST_EXPECTS(!ec, ec.message()))
868 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
869 BEAST_EXPECT(resp.body().contains(
"connectivity is working."));
875 testcase(
"Status request over WS and RPC with/without Amendment Block");
878 using namespace boost::beast::http;
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));
901 BEAST_EXPECT(!si.isMember(jss::warnings));
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));
909 BEAST_EXPECT(!si[jss::state].isMember(jss::warnings));
914 boost::system::error_code ec;
915 response<string_body> resp;
929 if (!BEAST_EXPECTS(!ec, ec.message()))
931 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
932 BEAST_EXPECT(resp.body().contains(
"connectivity is working."));
943 si = env.
rpc(
"server_info")[jss::result];
944 BEAST_EXPECT(si.isMember(jss::info));
946 si[jss::info].isMember(jss::amendment_blocked) &&
947 si[jss::info][jss::amendment_blocked] ==
true);
949 si[jss::info].isMember(jss::warnings) && si[jss::info][jss::warnings].isArray() &&
950 si[jss::info][jss::warnings].size() == 1 &&
954 si = env.
rpc(
"server_state")[jss::result];
956 si[jss::state].isMember(jss::amendment_blocked) &&
957 si[jss::state][jss::amendment_blocked] ==
true);
959 si[jss::state].isMember(jss::warnings) && si[jss::state][jss::warnings].isArray() &&
960 si[jss::state][jss::warnings].size() == 1 &&
977 if (!BEAST_EXPECTS(!ec, ec.message()))
979 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
980 BEAST_EXPECT(resp.body().contains(
"connectivity is working."));
996 if (!BEAST_EXPECTS(!ec, ec.message()))
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"));
1006 testcase(
"RPC client sends assorted input");
1011 boost::system::error_code ec;
1013 boost::beast::http::response<boost::beast::http::string_body> resp;
1015 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1016 BEAST_EXPECT(resp.body() ==
"Unable to parse request: \r\n");
1020 boost::beast::http::response<boost::beast::http::string_body> resp;
1024 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1025 BEAST_EXPECT(resp.body() ==
"Null method\r\n");
1029 boost::beast::http::response<boost::beast::http::string_body> resp;
1033 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1034 BEAST_EXPECT(resp.body() ==
"Unable to parse request: \r\n");
1038 boost::beast::http::response<boost::beast::http::string_body> resp;
1044 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1045 BEAST_EXPECT(resp.body() ==
"Unable to parse request: \r\n");
1049 boost::beast::http::response<boost::beast::http::string_body> resp;
1051 jv[jss::method] =
"batch";
1052 jv[jss::params] = 2;
1054 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1055 BEAST_EXPECT(resp.body() ==
"Malformed batch request\r\n");
1059 boost::beast::http::response<boost::beast::http::string_body> resp;
1061 jv[jss::method] =
"batch";
1063 jv[jss::params][
"invalid"] = 3;
1065 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1066 BEAST_EXPECT(resp.body() ==
"Malformed batch request\r\n");
1071 boost::beast::http::response<boost::beast::http::string_body> resp;
1074 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1075 BEAST_EXPECT(resp.body() ==
"Null method\r\n");
1079 boost::beast::http::response<boost::beast::http::string_body> resp;
1080 jv[jss::method] = 1;
1082 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1083 BEAST_EXPECT(resp.body() ==
"method is not string\r\n");
1087 boost::beast::http::response<boost::beast::http::string_body> resp;
1088 jv[jss::method] =
"";
1090 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1091 BEAST_EXPECT(resp.body() ==
"method is empty\r\n");
1095 boost::beast::http::response<boost::beast::http::string_body> resp;
1096 jv[jss::method] =
"some_method";
1097 jv[jss::params] =
"params";
1099 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1100 BEAST_EXPECT(resp.body() ==
"params unparsable\r\n");
1104 boost::beast::http::response<boost::beast::http::string_body> resp;
1106 jv[jss::params][0u] =
"not an object";
1108 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1109 BEAST_EXPECT(resp.body() ==
"params unparsable\r\n");
1116 testcase(
"Server status not okay");
1120 cfg->elbSupport =
true;
1127 boost::beast::http::response<boost::beast::http::string_body> resp;
1128 boost::system::error_code ec;
1130 BEAST_EXPECT(resp.result() == boost::beast::http::status::internal_server_error);
1131 std::regex const body{
"Server cannot accept clients"};
1139 for (
auto it : {
"http",
"ws",
"ws2"})
1146 yieldTo([&](boost::asio::yield_context& yield) {
Mix-in to support tests using asio coroutines.
boost::asio::io_context & getIoContext()
Return the io_context associated with the object.
void yieldTo(F0 &&f0, FN &&... fn)
Run one or more functions, each in a coroutine.
void pass()
Record a successful test condition.
TestcaseT testcase
Memberspace for declaring test cases.
Unserialize a JSON document into a Value.
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
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.
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
T emplace_back(T... args)
@ Array
array value (ordered list)
@ Object
object value (collection of name/value pairs).
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
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()
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.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
@ WarnRpcAmendmentBlocked
@ WarnRpcUnsupportedMajority
std::string to_string(BaseUInt< Bits, Tag > const &a)
std::string base64Encode(std::uint8_t const *data, std::size_t len)
T regex_search(T... args)
static constexpr auto kProtocol
static constexpr auto kLimit
static constexpr auto kAdminPassword
static constexpr auto kUser
static constexpr auto kAdmin
static constexpr auto kPort
static constexpr auto kIp
static constexpr auto kAdminUser
static constexpr auto kPassword
static constexpr auto kPortWs
static constexpr auto kServer
static constexpr auto kPortRpc