diff --git a/libraries/plugins/peerplays_sidechain/CMakeLists.txt b/libraries/plugins/peerplays_sidechain/CMakeLists.txt index ca995c10..679539e3 100755 --- a/libraries/plugins/peerplays_sidechain/CMakeLists.txt +++ b/libraries/plugins/peerplays_sidechain/CMakeLists.txt @@ -18,6 +18,7 @@ add_library( peerplays_sidechain bitcoin/sign_bitcoin_transaction.cpp common/rpc_client.cpp common/utils.cpp + common/ws_client.cpp ethereum/encoders.cpp ethereum/decoders.cpp ethereum/transaction.cpp diff --git a/libraries/plugins/peerplays_sidechain/common/ws_client.cpp b/libraries/plugins/peerplays_sidechain/common/ws_client.cpp new file mode 100644 index 00000000..da751563 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/common/ws_client.cpp @@ -0,0 +1,210 @@ +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace graphene { namespace peerplays_sidechain { + +ws_client::ws_client(std::string _url, std::string _user, std::string _password, bool _debug_ws_calls) : + url(_url), + user(_user), + password(_password), + debug_ws_calls(_debug_ws_calls), + request_id(0) { + + std::string reg_expr = "^((?Pwss|ws):\\/\\/)?(?P[a-zA-Z0-9\\-\\.]+)(:(?P\\d{1,5}))?(?P\\/.+)?"; + boost::xpressive::sregex sr = boost::xpressive::sregex::compile(reg_expr); + + boost::xpressive::smatch sm; + + if (boost::xpressive::regex_search(url, sm, sr)) { + protocol = sm["Protocol"]; + if (protocol.empty()) { + protocol = "ws"; + } + + host = sm["Host"]; + if (host.empty()) { + host + "localhost"; + } + + port = sm["Port"]; + if (port.empty()) { + port = "80"; + } + + target = sm["Target"]; + if (target.empty()) { + target = "/"; + } + } else { + elog("Invalid URL: ${url}", ("url", url)); + } +} + +std::string ws_client::retrieve_array_value_from_reply(std::string reply_str, std::string array_path, uint32_t idx) { + std::stringstream ss(reply_str); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + if (json.find("result") == json.not_found()) { + return ""; + } + + auto json_result = json.get_child_optional("result"); + if (json_result) { + boost::property_tree::ptree array_ptree = json_result.get(); + if (!array_path.empty()) { + array_ptree = json_result.get().get_child(array_path); + } + uint32_t array_el_idx = -1; + for (const auto &array_el : array_ptree) { + array_el_idx = array_el_idx + 1; + if (array_el_idx == idx) { + std::stringstream ss_res; + boost::property_tree::json_parser::write_json(ss_res, array_el.second); + return ss_res.str(); + } + } + } + + return ""; +} + +std::string ws_client::retrieve_value_from_reply(std::string reply_str, std::string value_path) { + std::stringstream ss(reply_str); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + if (json.find("result") == json.not_found()) { + return ""; + } + + auto json_result = json.get_child_optional("result"); + if (json_result) { + return json_result.get().get(value_path); + } + + return json.get("result"); +} + +std::string ws_client::send_post_request(std::string method, std::string params, bool show_log) { + std::stringstream body; + + request_id = request_id + 1; + + body << "{ \"jsonrpc\": \"2.0\", \"id\": " << request_id << ", \"method\": \"" << method << "\""; + + if (!params.empty()) { + body << ", \"params\": " << params; + } + + body << " }"; + + const auto reply = send_post_request(body.str(), show_log); + + if (reply.body.empty()) { + wlog("RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body.str())("msg", ss.str())); + } + return ""; +} + +ws_reply ws_client::send_post_request(std::string body, bool show_log) { + + // The io_context is required for all I/O + boost::beast::net::io_context ioc; + + // These objects perform our I/O + boost::beast::net::ip::tcp::resolver resolver(ioc); + boost::beast::websocket::stream ws{ioc}; + + // Look up the domain name + auto const results = resolver.resolve(host, port); + + //// Make the connection on the IP address we get from a lookup + //boost::beast::net::connect(ws.next_layer(), results.begin(), results.end()); + // + //// Set a decorator to change the User-Agent of the handshake + //ws.set_option(websocket::stream_base::decorator( + // [](websocket::request_type &req) { + // req.set(http::field::user_agent, + // std::string(BOOST_BEAST_VERSION_STRING) + + // " websocket-client-coro"); + // })); + + //// Set up an HTTP GET request message + //boost::beast::http::request req{boost::beast::http::verb::post, target, 11}; + //req.set(boost::beast::http::field::host, host + ":" + port); + //req.set(boost::beast::http::field::accept, "application/json"); + //req.set(boost::beast::http::field::content_type, "application/json"); + //req.set(boost::beast::http::field::content_encoding, "utf-8"); + //req.set(boost::beast::http::field::content_length, body.length()); + //req.body() = body; + + // Perform the websocket handshake + ws.handshake(host, "/"); + + // Send the message + ws.write(boost::asio::buffer(body)); + + // This buffer is used for reading and must be persisted + boost::beast::flat_buffer buffer; + + // Declare a container to hold the response + boost::beast::http::response res; + + //// Receive the HTTP response + //boost::beast::http::read(stream, buffer, res); + // + ////// Write the message to standard out + ////std::cout << res << std::endl; + // + //// Gracefully close the socket + //boost::beast::error_code ec; + //stream.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); + // + //// not_connected happens sometimes + //// so don't bother reporting it. + //// + //if (ec && ec != boost::beast::errc::not_connected) + // throw boost::beast::system_error{ec}; + + std::string rbody{boost::asio::buffers_begin(res.body().data()), + boost::asio::buffers_end(res.body().data())}; + ws_reply reply; + reply.status = 200; + reply.body = rbody; + + if (show_log) { + ilog("### Request URL: ${url}", ("url", url)); + ilog("### Request: ${body}", ("body", body)); + ilog("### Response: ${rbody}", ("rbody", rbody)); + } + + return reply; +} + +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/common/ws_client.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/common/ws_client.hpp new file mode 100644 index 00000000..9a7cfca4 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/common/ws_client.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +namespace graphene { namespace peerplays_sidechain { + +struct ws_reply { + uint16_t status; + std::string body; +}; + +class ws_client { +public: + ws_client(std::string _url, std::string _user, std::string _password, bool _debug_ws_calls); + +protected: + std::string retrieve_array_value_from_reply(std::string reply_str, std::string array_path, uint32_t idx); + std::string retrieve_value_from_reply(std::string reply_str, std::string value_path); + std::string send_post_request(std::string method, std::string params, bool show_log); + + std::string url; + std::string protocol; + std::string host; + std::string port; + std::string target; + + std::string user; + std::string password; + bool debug_ws_calls; + + uint32_t request_id; + +private: + ws_reply send_post_request(std::string body, bool show_log); +}; + +}} // namespace graphene::peerplays_sidechain