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
96 using namespace boost::beast::http;
97 request<string_body> req;
102 req.insert(
"User-Agent",
"test");
103 req.method(boost::beast::http::verb::get);
104 req.insert(
"Upgrade",
"websocket");
115 req.insert(
"Sec-WebSocket-Version",
"13");
116 req.insert(boost::beast::http::field::connection,
"upgrade");
128 using namespace boost::beast::http;
129 request<string_body> req;
133 for (
auto const& f : fields)
134 req.insert(f.name(), f.value());
136 req.insert(
"User-Agent",
"test");
139 req.method(boost::beast::http::verb::get);
143 req.method(boost::beast::http::verb::post);
144 req.insert(
"Content-Type",
"application/json; charset=UTF-8");
147 req.prepare_payload();
154 boost::asio::yield_context& yield,
155 boost::beast::http::request<boost::beast::http::string_body>
const& req,
159 boost::beast::http::response<boost::beast::http::string_body>& resp,
160 boost::system::error_code& ec)
163 using namespace boost::beast::http;
165 ip::tcp::resolver r{ios};
166 boost::beast::multi_buffer sb;
175 ssl::context ctx{ssl::context::sslv23};
176 ctx.set_verify_mode(ssl::verify_none);
177 ssl::stream<ip::tcp::socket> ss{ios, ctx};
178 async_connect(ss.next_layer(), it, yield[ec]);
181 ss.async_handshake(ssl::stream_base::client, yield[ec]);
184 boost::beast::http::async_write(ss, req, yield[ec]);
187 async_read(ss, sb, resp, yield[ec]);
193 ip::tcp::socket sock{ios};
194 async_connect(sock, it, yield[ec]);
197 boost::beast::http::async_write(sock, req, yield[ec]);
200 async_read(sock, sb, resp, yield[ec]);
209 boost::asio::yield_context& yield,
211 boost::beast::http::response<boost::beast::http::string_body>& resp,
212 boost::system::error_code& ec)
223 boost::asio::yield_context& yield,
225 boost::beast::http::response<boost::beast::http::string_body>& resp,
226 boost::system::error_code& ec,
242 bool subobject =
false)
249 jp[
"admin_user"] = user;
254 jpi[
"admin_password"] = password;
255 jp[
"admin_password"] = jpi;
259 jp[
"admin_password"] = password;
266 jrr = jrc->invoke(
"ledger_accept", jp);
271 jrr = wsc->invoke(
"ledger_accept", jp);
284 testcase <<
"Admin request over " << proto <<
", config "
285 << (admin ?
"enabled" :
"disabled") <<
", credentials "
298 auto const user = env.
app()
302 auto const password = env.
app()
309 BEAST_EXPECT(jrr[
"error"] == protoWs ?
"forbidden" :
"noPermission");
311 jrr[
"error_message"] == protoWs ?
"Bad credentials."
312 :
"You don't have permission for this command.");
317 BEAST_EXPECT(jrr[
"error"] == protoWs ?
"forbidden" :
"noPermission");
319 jrr[
"error_message"] == protoWs ?
"Bad credentials."
320 :
"You don't have permission for this command.");
325 BEAST_EXPECT(jrr[
"error"] == protoWs ?
"forbidden" :
"noPermission");
327 jrr[
"error_message"] == protoWs ?
"Bad credentials."
328 :
"You don't have permission for this command.");
332 BEAST_EXPECT(jrr[
"error"] == protoWs ?
"forbidden" :
"noPermission");
334 jrr[
"error_message"] == protoWs ?
"Bad credentials."
335 :
"You don't have permission for this command.");
340 BEAST_EXPECT(jrr[
"status"] ==
"success");
346 BEAST_EXPECT(jrr[
"status"] ==
"success");
350 BEAST_EXPECT(jrr[
"status"] ==
"success");
356 BEAST_EXPECT(jrr[
"error"] == protoWs ?
"forbidden" :
"noPermission");
358 jrr[
"error_message"] == protoWs ?
"Bad credentials."
359 :
"You don't have permission for this command.");
366 testcase(
"WS client to http server fails");
375 boost::system::error_code ec;
376 boost::beast::http::response<boost::beast::http::string_body> resp;
378 if (!BEAST_EXPECTS(!ec, ec.message()))
380 BEAST_EXPECT(resp.result() == boost::beast::http::status::unauthorized);
385 boost::system::error_code ec;
386 boost::beast::http::response<boost::beast::http::string_body> resp;
388 if (!BEAST_EXPECTS(!ec, ec.message()))
390 BEAST_EXPECT(resp.result() == boost::beast::http::status::unauthorized);
407 boost::system::error_code ec;
408 boost::beast::http::response<boost::beast::http::string_body> resp;
410 if (!BEAST_EXPECTS(!ec, ec.message()))
412 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
417 boost::system::error_code ec;
418 boost::beast::http::response<boost::beast::http::string_body> resp;
420 if (!BEAST_EXPECTS(!ec, ec.message()))
422 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
429 testcase(
"Partial WS upgrade request");
432 using namespace boost::beast::http;
441 boost::system::error_code ec;
442 response<string_body> resp;
446 auto reqString = boost::lexical_cast<std::string>(req);
447 reqString.erase(reqString.find_last_of(
"13"), std::string::npos);
450 ip::tcp::resolver r{ios};
451 boost::beast::multi_buffer sb;
453 auto it = r.async_resolve(
455 if (!BEAST_EXPECTS(!ec, ec.message()))
458 ip::tcp::socket sock{ios};
459 async_connect(sock, it, yield[ec]);
460 if (!BEAST_EXPECTS(!ec, ec.message()))
462 async_write(sock, boost::asio::buffer(reqString), yield[ec]);
463 if (!BEAST_EXPECTS(!ec, ec.message()))
467 async_read(sock, sb, resp, yield[ec]);
475 boost::asio::yield_context& yield)
480 testcase <<
"Connect fails: " << clientProtocol <<
" client to " << serverProtocol
485 boost::beast::http::response<boost::beast::http::string_body> resp;
486 boost::system::error_code ec;
489 doHTTPRequest(env, yield, clientProtocol ==
"https", resp, ec);
494 doWSRequest(env, yield, clientProtocol ==
"wss" || clientProtocol ==
"wss2", resp, ec);
500 testAuth(
bool secure, boost::asio::yield_context& yield)
502 testcase <<
"Server with authorization, " << (secure ?
"secure" :
"non-secure");
515 jr[jss::method] =
"server_info";
516 boost::beast::http::response<boost::beast::http::string_body> resp;
517 boost::system::error_code ec;
519 BEAST_EXPECT(resp.result() == boost::beast::http::status::forbidden);
522 auth.insert(
"Authorization",
"");
524 BEAST_EXPECT(resp.result() == boost::beast::http::status::forbidden);
526 auth.set(
"Authorization",
"Basic NOT-VALID");
528 BEAST_EXPECT(resp.result() == boost::beast::http::status::forbidden);
530 auth.set(
"Authorization",
"Basic " +
base64Encode(
"me:badpass"));
532 BEAST_EXPECT(resp.result() == boost::beast::http::status::forbidden);
541 auth.set(
"Authorization",
"Basic " + user +
":" +
pass);
543 BEAST_EXPECT(resp.result() == boost::beast::http::status::forbidden);
548 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
549 BEAST_EXPECT(!resp.body().empty());
555 testcase <<
"Server with connection limit of " << limit;
559 using namespace boost::beast::http;
573 boost::system::error_code ec;
575 ip::tcp::resolver r{ios};
578 jr[jss::method] =
"server_info";
603 int const testTo = (limit == 0) ? 50 : limit + 1;
604 while (
static_cast<int>(clients.
size()) < testTo)
606 clients.
emplace_back(ip::tcp::socket{ios}, boost::beast::multi_buffer{});
607 async_connect(clients.
back().first, it, yield[ec]);
610 async_write(clients.
back().first, req, yield[ec]);
614 int successfulReads = 0;
615 for (
auto& [soc, buf] : clients)
617 boost::beast::http::response<boost::beast::http::string_body> resp;
618 async_read(soc, buf, resp, yield[ec]);
628 int const expectedReads = (limit == 0) ?
static_cast<int>(clients.
size()) : limit - 1;
629 BEAST_EXPECT(successfulReads == expectedReads);
635 testcase(
"Connection with WS handoff");
648 boost::beast::http::response<boost::beast::http::string_body> resp;
649 boost::system::error_code ec;
651 BEAST_EXPECT(resp.result() == boost::beast::http::status::switching_protocols);
652 BEAST_EXPECT(resp.contains(
"Upgrade") && resp[
"Upgrade"] ==
"websocket");
653 BEAST_EXPECT(resp.contains(
"Connection") && boost::iequals(resp[
"Connection"],
"upgrade"));
659 testcase(
"Connection to port with no RPC enabled");
669 boost::beast::http::response<boost::beast::http::string_body> resp;
670 boost::system::error_code ec;
674 BEAST_EXPECT(resp.result() == boost::beast::http::status::forbidden);
675 BEAST_EXPECT(resp.body() ==
"Forbidden\r\n");
681 testcase(
"WS client sends assorted input");
685 using namespace boost::beast::http;
693 boost::system::error_code ec;
696 ip::tcp::resolver r{ios};
699 if (!BEAST_EXPECT(!ec))
702 ip::tcp::socket sock{ios};
703 async_connect(sock, it, yield[ec]);
704 if (!BEAST_EXPECT(!ec))
707 boost::beast::websocket::stream<boost::asio::ip::tcp::socket&> ws{sock};
712 ws.async_write_some(
true, buffer(req), yield[ec]);
713 if (!BEAST_EXPECT(!ec))
716 boost::beast::multi_buffer sb;
717 ws.async_read(sb, yield[ec]);
718 if (!BEAST_EXPECT(!ec))
723 if (!BEAST_EXPECT(jr.
parse(
724 boost::lexical_cast<std::string>(boost::beast::make_printable(sb.data())),
727 sb.consume(sb.size());
732 auto resp = sendAndParse(
"NOT JSON");
733 BEAST_EXPECT(resp.isMember(jss::error) && resp[jss::error] ==
"jsonInvalid");
734 BEAST_EXPECT(!resp.isMember(jss::status));
739 jv[jss::command] =
"foo";
740 jv[jss::method] =
"bar";
742 BEAST_EXPECT(resp.isMember(jss::error) && resp[jss::error] ==
"missingCommand");
743 BEAST_EXPECT(resp.isMember(jss::status) && resp[jss::status] ==
"error");
748 jv[jss::command] =
"ping";
750 BEAST_EXPECT(resp.isMember(jss::status) && resp[jss::status] ==
"success");
752 resp.isMember(jss::result) && resp[jss::result].isMember(jss::role) &&
753 resp[jss::result][jss::role] ==
"admin");
760 testcase(
"Status request over WS and RPC with/without Amendment Warning");
763 using namespace boost::beast::http;
782 auto si = env.
rpc(
"server_info")[jss::result];
783 BEAST_EXPECT(si.isMember(jss::info));
784 BEAST_EXPECT(!si[jss::info].isMember(jss::amendment_blocked));
786 BEAST_EXPECT(!si.isMember(jss::warnings));
790 si = env.
rpc(
"server_state")[jss::result];
791 BEAST_EXPECT(si.isMember(jss::state));
792 BEAST_EXPECT(!si[jss::state].isMember(jss::amendment_blocked));
794 BEAST_EXPECT(!si[jss::state].isMember(jss::warnings));
799 boost::system::error_code ec;
800 response<string_body> resp;
814 if (!BEAST_EXPECTS(!ec, ec.message()))
816 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
817 BEAST_EXPECT(resp.body().contains(
"connectivity is working."));
828 si = env.
rpc(
"server_info")[jss::result];
829 BEAST_EXPECT(si.isMember(jss::info));
830 BEAST_EXPECT(!si[jss::info].isMember(jss::amendment_blocked));
832 si[jss::info].isMember(jss::warnings) && si[jss::info][jss::warnings].isArray() &&
833 si[jss::info][jss::warnings].size() == 1 &&
838 si = env.
rpc(
"server_state")[jss::result];
839 BEAST_EXPECT(si.isMember(jss::state));
840 BEAST_EXPECT(!si[jss::state].isMember(jss::amendment_blocked));
842 si[jss::state].isMember(jss::warnings) && si[jss::state][jss::warnings].isArray() &&
843 si[jss::state][jss::warnings].size() == 1 &&
859 if (!BEAST_EXPECTS(!ec, ec.message()))
861 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
862 BEAST_EXPECT(resp.body().contains(
"connectivity is working."));
879 if (!BEAST_EXPECTS(!ec, ec.message()))
881 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
882 BEAST_EXPECT(resp.body().contains(
"connectivity is working."));
888 testcase(
"Status request over WS and RPC with/without Amendment Block");
891 using namespace boost::beast::http;
910 auto si = env.
rpc(
"server_info")[jss::result];
911 BEAST_EXPECT(si.isMember(jss::info));
912 BEAST_EXPECT(!si[jss::info].isMember(jss::amendment_blocked));
914 BEAST_EXPECT(!si.isMember(jss::warnings));
918 si = env.
rpc(
"server_state")[jss::result];
919 BEAST_EXPECT(si.isMember(jss::state));
920 BEAST_EXPECT(!si[jss::state].isMember(jss::amendment_blocked));
922 BEAST_EXPECT(!si[jss::state].isMember(jss::warnings));
927 boost::system::error_code ec;
928 response<string_body> resp;
942 if (!BEAST_EXPECTS(!ec, ec.message()))
944 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
945 BEAST_EXPECT(resp.body().contains(
"connectivity is working."));
956 si = env.
rpc(
"server_info")[jss::result];
957 BEAST_EXPECT(si.isMember(jss::info));
959 si[jss::info].isMember(jss::amendment_blocked) &&
960 si[jss::info][jss::amendment_blocked] ==
true);
962 si[jss::info].isMember(jss::warnings) && si[jss::info][jss::warnings].isArray() &&
963 si[jss::info][jss::warnings].size() == 1 &&
967 si = env.
rpc(
"server_state")[jss::result];
969 si[jss::state].isMember(jss::amendment_blocked) &&
970 si[jss::state][jss::amendment_blocked] ==
true);
972 si[jss::state].isMember(jss::warnings) && si[jss::state][jss::warnings].isArray() &&
973 si[jss::state][jss::warnings].size() == 1 &&
990 if (!BEAST_EXPECTS(!ec, ec.message()))
992 BEAST_EXPECT(resp.result() == boost::beast::http::status::ok);
993 BEAST_EXPECT(resp.body().contains(
"connectivity is working."));
1009 if (!BEAST_EXPECTS(!ec, ec.message()))
1011 BEAST_EXPECT(resp.result() == boost::beast::http::status::internal_server_error);
1012 BEAST_EXPECT(resp.body().contains(
"cannot accept clients:"));
1013 BEAST_EXPECT(resp.body().contains(
"Server version too old"));
1019 testcase(
"RPC client sends assorted input");
1024 boost::system::error_code ec;
1026 boost::beast::http::response<boost::beast::http::string_body> resp;
1028 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1029 BEAST_EXPECT(resp.body() ==
"Unable to parse request: \r\n");
1033 boost::beast::http::response<boost::beast::http::string_body> resp;
1037 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1038 BEAST_EXPECT(resp.body() ==
"Null method\r\n");
1042 boost::beast::http::response<boost::beast::http::string_body> resp;
1046 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1047 BEAST_EXPECT(resp.body() ==
"Unable to parse request: \r\n");
1051 boost::beast::http::response<boost::beast::http::string_body> resp;
1057 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1058 BEAST_EXPECT(resp.body() ==
"Unable to parse request: \r\n");
1062 boost::beast::http::response<boost::beast::http::string_body> resp;
1064 jv[jss::method] =
"batch";
1065 jv[jss::params] = 2;
1067 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1068 BEAST_EXPECT(resp.body() ==
"Malformed batch request\r\n");
1072 boost::beast::http::response<boost::beast::http::string_body> resp;
1074 jv[jss::method] =
"batch";
1076 jv[jss::params][
"invalid"] = 3;
1078 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1079 BEAST_EXPECT(resp.body() ==
"Malformed batch request\r\n");
1084 boost::beast::http::response<boost::beast::http::string_body> resp;
1087 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1088 BEAST_EXPECT(resp.body() ==
"Null method\r\n");
1092 boost::beast::http::response<boost::beast::http::string_body> resp;
1093 jv[jss::method] = 1;
1095 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1096 BEAST_EXPECT(resp.body() ==
"method is not string\r\n");
1100 boost::beast::http::response<boost::beast::http::string_body> resp;
1101 jv[jss::method] =
"";
1103 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1104 BEAST_EXPECT(resp.body() ==
"method is empty\r\n");
1108 boost::beast::http::response<boost::beast::http::string_body> resp;
1109 jv[jss::method] =
"some_method";
1110 jv[jss::params] =
"params";
1112 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1113 BEAST_EXPECT(resp.body() ==
"params unparsable\r\n");
1117 boost::beast::http::response<boost::beast::http::string_body> resp;
1119 jv[jss::params][0u] =
"not an object";
1121 BEAST_EXPECT(resp.result() == boost::beast::http::status::bad_request);
1122 BEAST_EXPECT(resp.body() ==
"params unparsable\r\n");
1129 testcase(
"Server status not okay");
1133 cfg->elbSupport =
true;
1140 boost::beast::http::response<boost::beast::http::string_body> resp;
1141 boost::system::error_code ec;
1143 BEAST_EXPECT(resp.result() == boost::beast::http::status::internal_server_error);
1144 std::regex const body{
"Server cannot accept clients"};
1152 for (
auto it : {
"http",
"ws",
"ws2"})
1159 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.
bool disconnectClient(std::chrono::steady_clock::duration timeout=std::chrono::seconds{1})
Disconnect the Env's built-in client and wait for the server to register the dropped connection.
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 > singleThreadIo(std::unique_ptr< Config >)
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