diff --git a/libraries/plugins/peerplays_sidechain/common/rpc_client.cpp b/libraries/plugins/peerplays_sidechain/common/rpc_client.cpp index ce4081c2..70723f0e 100644 --- a/libraries/plugins/peerplays_sidechain/common/rpc_client.cpp +++ b/libraries/plugins/peerplays_sidechain/common/rpc_client.cpp @@ -3,25 +3,834 @@ #include #include +//#include + +#include +#include +#include #include #include -#include - #include #include +#include namespace graphene { namespace peerplays_sidechain { -rpc_client::rpc_client(std::string _ip, uint32_t _port, std::string _user, std::string _password, bool _debug_rpc_calls) : - ip(_ip), - port(_port), - user(_user), - password(_password), - debug_rpc_calls(_debug_rpc_calls), - request_id(0) { - authorization.key = "Authorization"; - authorization.val = "Basic " + fc::base64_encode(user + ":" + password); +constexpr auto http_port = 80; +constexpr auto https_port = 443; + +template +void make_trimmed(string *str) { + boost::algorithm::trim(*str); +} + +template +void make_lower(string *str) { + boost::algorithm::to_lower(*str); +} + +bool convert_hex_to_num_helper1(const std::string &str, uint32_t *value) { + try { + size_t idx; + auto v = stol(str, &idx, 16); + if (idx != str.size()) + return false; + if (value) + *value = v; + return true; + } catch (...) { + return false; + } +} + +bool convert_dec_to_num_helper1(const std::string &str, uint32_t *value) { + try { + size_t idx; + auto v = stol(str, &idx, 10); + if (idx != str.size()) + return false; + if (value) + *value = v; + return true; + } catch (...) { + return false; + } +} + +bool convert_dec_to_num_helper1(const std::string &str, uint16_t *value) { + try { + size_t idx; + auto v = stol(str, &idx, 10); + if (idx != str.size()) + return false; + if (v > std::numeric_limits::max()) + return false; + if (value) + *value = v; + return true; + } catch (...) { + return false; + } +} + +template +constexpr V ceilDiv(V value, D divider) { + return (value + divider - 1) / divider; +} + +template +constexpr V aligned(V value, A align) { + return ceilDiv(value, align) * align; +} + +template +void reserve( + Container *container, + typename Container::size_type freeSpaceRequired, + typename Container::size_type firstAlloc, + typename Container::size_type nextAlloc) { + //TSL_ASSERT(container); + auto &c = *container; + auto required = c.size() + freeSpaceRequired; + if (c.capacity() >= required) + return; + c.reserve((firstAlloc >= required) ? firstAlloc + : firstAlloc + aligned(required - firstAlloc, nextAlloc)); +} + +template +void reserve( + Container *container, + typename Container::size_type freeSpaceRequired, + typename Container::size_type alloc) { + //TSL_ASSERT(container); + auto &c = *container; + auto required = c.size() + freeSpaceRequired; + if (c.capacity() >= required) + return; + c.reserve(aligned(required, alloc)); +} + +bool is_valid(const boost::asio::ip::tcp::endpoint &ep) { + + if (ep.port() == 0) + return false; + + if (ep.address().is_unspecified()) + return false; + + return true; +} + +// utl + +url_schema_type identify_url_schema_type(const std::string &schema_name) { + // rework + auto temp = schema_name; + make_lower(&temp); + if (temp == "http") + return url_schema_type::http; + if (temp == "https") + return url_schema_type::https; + return url_schema_type::unknown; +} + +// url_data + +url_data::url_data(const std::string &url) { + if (!parse(url)) + FC_THROW("URL parse failed"); +} + +void url_data::clear() { + schema_type = url_schema_type::unknown; + schema = decltype(schema)(); + host = decltype(host)(); + port = 0; + path = decltype(path)(); +} + +bool url_data::parse(const std::string &url) { + + typedef std::string::size_type size_t; + constexpr auto npos = std::string::npos; + + size_t schema_end = url.find("://"); + size_t host_begin; + std::string temp_schema; + + if (schema_end == npos) + host_begin = 0; // no schema + else { + if (schema_end < 3) { // schema too short: less than 3 chars + return false; + } + if (schema_end > 5) { // schema too long: more than 5 chars + return false; + } + host_begin = schema_end + 3; + temp_schema = url.substr(0, schema_end); + } + + // ASSERT(url.size() >= host_begin); + + if (url.size() == host_begin) // host is empty + return false; + + size_t port_sep = url.find(':', host_begin); + + if (port_sep == host_begin) + return false; + + size_t path_sep = url.find('/', host_begin); + + if (path_sep == host_begin) + return false; + + if ((port_sep != npos) && (path_sep != npos) && (port_sep > path_sep)) + port_sep = npos; + + std::string temp_port; + + if (port_sep != npos) { + auto port_index = port_sep + 1; + if (path_sep == npos) + temp_port = url.substr(port_index); + else + temp_port = url.substr(port_index, path_sep - port_index); + } + + if (temp_port.empty()) + port = 0; + else { + if (!convert_dec_to_num_helper1(temp_port, &port)) + return false; + } + + std::string temp_path; + + if (path_sep != npos) + temp_path = url.substr(path_sep); + + std::string temp_host; + + if (port_sep != npos) { + temp_host = url.substr(host_begin, port_sep - host_begin); + } else { + if (path_sep != npos) + temp_host = url.substr(host_begin, path_sep - host_begin); + else + temp_host = url.substr(host_begin); + } + + schema = temp_schema; + host = temp_host; + path = temp_path; + schema_type = identify_url_schema_type(schema); + + return true; +} + +}} // namespace graphene::peerplays_sidechain + +namespace graphene { namespace peerplays_sidechain { + +using namespace boost::asio; +using error_code = boost::system::error_code; +using endpoint = ip::tcp::endpoint; + +namespace detail { + +// http_call_impl + +struct tcp_socket { + + typedef ip::tcp::socket underlying_type; + + underlying_type underlying; + + tcp_socket(http_call &call) : + underlying(call.m_service) { + } + + underlying_type &operator()() { + return underlying; + } + + void connect(const http_call &call, const endpoint &ep, error_code *ec) { + // TCP connect + underlying.connect(ep, *ec); + } + + void shutdown() { + error_code ec; + underlying.close(ec); + } +}; + +struct ssl_socket { + + typedef ssl::stream underlying_type; + + underlying_type underlying; + + ssl_socket(http_call &call) : + underlying(call.m_service, *call.m_context) { + } + + underlying_type &operator()() { + return underlying; + } + + void connect(const http_call &call, const endpoint &ep, error_code *ec) { + + auto &u = underlying; + + // TCP connect + u.lowest_layer().connect(ep, *ec); + + // SSL connect + if (!SSL_set_tlsext_host_name(u.native_handle(), call.m_host.c_str())) + FC_THROW("SSL_set_tlsext_host_name failed"); + + u.set_verify_mode(ssl::verify_peer, *ec); + u.handshake(ssl::stream_base::client, *ec); + } + + void shutdown() { + auto &u = underlying; + error_code ec; + u.shutdown(ec); + u.lowest_layer().close(ec); + } +}; + +template +class http_call_impl { +public: + http_call_impl(http_call &call, const void *body_data, size_t body_size, const std::string &content_type_, http_response &response); + void exec(); + +private: + http_call &call; + const void *body_data; + size_t body_size; + std::string content_type; + http_response &response; + + socket_type socket; + streambuf response_buf; + + int32_t content_length; + bool transfer_encoding_chunked; + +private: + void connect(); + void shutdown(); + void send_request(); + void on_header(std::string &name, std::string &value); + void on_header(); + void process_headers(); + void append_entity_body(std::istream *stream, size_t size); + void append_entity_body_2(std::istream *strm); + bool read_next_chunk(std::istream *strm); + void skip_footer(); + void read_body_chunked(); + void read_body_until_eof(); + void read_body_exact(); + void process_response(); +}; + +static const char cr = 0x0D; +static const char lf = 0x0A; +static const char *crlf = "\x0D\x0A"; +static const char *crlfcrlf = "\x0D\x0A\x0D\x0A"; +static const auto crlf_uint = (((uint16_t)lf) << 8) + cr; + +template +http_call_impl::http_call_impl(http_call &call_, const void *body_data_, size_t body_size_, const std::string &content_type_, http_response &response_) : + call(call_), + body_data(body_data_), + body_size(body_size_), + content_type(content_type_), + response(response_), + socket(call), + response_buf(http_call::response_size_limit_bytes) { +} + +template +void http_call_impl::exec() { + try { + connect(); + send_request(); + process_response(); + shutdown(); + } catch (...) { + shutdown(); + throw; + } +} + +template +void http_call_impl::connect() { + + { + error_code ec; + auto &ep = call.m_endpoint; + if (is_valid(ep)) { + socket.connect(call, ep, &ec); + if (!ec) + return; + } + } + + ip::tcp::resolver resolver(call.m_service); + + auto rng = resolver.resolve(call.m_host, std::string()); + + //ASSERT(rng.begin() != rng.end()); + + error_code ec; + + for (endpoint ep : rng) { + ep.port(call.m_port); + socket.connect(call, ep, &ec); + if (!ec) { + call.m_endpoint = ep; + return; // comment to test1 + } + } + // if (!ec) return; // uncomment to test1 + + //ASSERT(ec); + throw boost::system::system_error(ec); +} + +template +void http_call_impl::shutdown() { + socket.shutdown(); +} + +template +void http_call_impl::send_request() { + + streambuf request; + std::ostream stream(&request); + + // start string: HTTP/1.0 + + //ASSERT(!call.m_path.empty()); + + stream << call.m_method << " " << call.m_path << " HTTP/1.1" << crlf; + + // host + + stream << "Host: " << call.m_host << ":" << call.m_endpoint.port() << crlf; + + // content + + if (body_size) { + stream << "Content-Type: " << content_type << crlf; + stream << "Content-Length: " << body_size << crlf; + } + + // additional headers + + const auto &h = call.m_headers; + + if (!h.empty()) { + if (h.size() < 2) + FC_THROW("invalid headers data"); + stream << h; + // ensure headers finished correctly + if ((h.substr(h.size() - 2) != crlf)) + stream << crlf; + } + + // other + + // stream << "Accept: *\x2F*" << crlf; + stream << "Accept: text/html, application/json" << crlf; + stream << "Connection: close" << crlf; + + // end + + stream << crlf; + + // send headers + + write(socket(), request); + + // send body + + if (body_size) + write(socket(), buffer(body_data, body_size)); +} + +template +void http_call_impl::on_header(std::string &name, std::string &value) { + + if (name == "content-length") { + uint32_t u; + if (!convert_dec_to_num_helper1(value, &u)) + FC_THROW("invalid content-length header data"); + content_length = u; + return; + } + + if (name == "transfer-encoding") { + boost::algorithm::to_lower(value); + if (value == "chunked") + transfer_encoding_chunked = true; + return; + } +} + +template +void http_call_impl::process_headers() { + + std::istream stream(&response_buf); + + std::string http_version; + stream >> http_version; + stream >> response.status_code; + + make_trimmed(&http_version); + make_lower(&http_version); + + if (!stream || http_version.substr(0, 6) != "http/1") + FC_THROW("invalid response data"); + + // read/skip headers + + content_length = -1; + transfer_encoding_chunked = false; + + for (;;) { + std::string header; + if (!std::getline(stream, header, lf) || (header.size() == 1 && header[0] == cr)) + break; + auto pos = header.find(':'); + if (pos == std::string::npos) + continue; + auto name = header.substr(0, pos); + make_trimmed(&name); + boost::algorithm::to_lower(name); + auto value = header.substr(pos + 1); + make_trimmed(&value); + on_header(name, value); + } +} + +template +void http_call_impl::append_entity_body(std::istream *strm, size_t size) { + if (size == 0) + return; + auto &body = response.body; + reserve(&body, size, http_call::response_first_alloc_bytes, http_call::response_next_alloc_bytes); + auto cur = body.size(); + body.resize(cur + size); + auto p = &body[cur]; + if (!strm->read(p, size)) + FC_THROW("stream read failed"); +} + +template +void http_call_impl::append_entity_body_2(std::istream *strm) { + auto avail = response_buf.size(); + if (response.body.size() + avail > http_call::response_size_limit_bytes) + FC_THROW("response body size limit exceeded"); + append_entity_body(strm, avail); +} + +template +bool http_call_impl::read_next_chunk(std::istream *strm) { + + // content length info is used as pre-alloc hint only + // it is not used inside the reading logic + + auto &buf = response_buf; + auto &stream = *strm; + auto &body = response.body; + + read_until(socket(), buf, crlf); + + std::string chunk_header; + + if (!std::getline(stream, chunk_header, lf)) + FC_THROW("failed to read chunk size"); + + auto ext_index = chunk_header.find(':'); + + if (ext_index != std::string::npos) + chunk_header.resize(ext_index); + + make_trimmed(&chunk_header); + + uint32_t chink_size; + + if (!convert_hex_to_num_helper1(chunk_header, &chink_size)) + FC_THROW("invalid chunk size string"); + + if (body.size() + chink_size > http_call::response_size_limit_bytes) + FC_THROW("response body size limit exceeded"); + + auto avail = buf.size(); + if (avail < chink_size + 2) { + auto rest = chink_size + 2 - avail; + read(socket(), buf, transfer_at_least(rest)); + } + + append_entity_body(&stream, chink_size); + + uint16_t temp; + if (!stream.read((char *)(&temp), 2)) + FC_THROW("stream read failed"); + if (temp != crlf_uint) + FC_THROW("invalid chink end"); + + return chink_size != 0; +} + +template +void http_call_impl::skip_footer() { + // to be implemeted +} + +template +void http_call_impl::read_body_chunked() { + + std::istream stream(&response_buf); + + for (;;) { + if (!read_next_chunk(&stream)) + break; + } + + skip_footer(); +} + +template +void http_call_impl::read_body_until_eof() { + + auto &buf = response_buf; + std::istream stream(&buf); + + append_entity_body_2(&stream); + + error_code ec; + + for (;;) { + auto readed = read(socket(), buf, transfer_at_least(1), ec); + append_entity_body_2(&stream); + if (ec) + break; + if (!readed) { + //ASSERT(buf.size() == 0); + FC_THROW("logic error: read failed but no error conditon"); + } + } + if ((ec != error::eof) && + (ec != ssl::error::stream_truncated)) + throw boost::system::system_error(ec); +} + +template +void http_call_impl::read_body_exact() { + + auto &buf = response_buf; + auto &body = response.body; + + auto avail = buf.size(); + + if (avail > content_length) + FC_THROW("invalid response body (content length mismatch)"); + + body.resize(content_length); + + if (avail) { + if (avail != buf.sgetn(&body[0], avail)) + FC_THROW("stream read failed"); + } + + auto rest = content_length - avail; + + if (rest > 0) { + auto readed = read(socket(), buffer(&body[avail], rest), transfer_exactly(rest)); + //ASSERT(readed <= rest); + if (readed < rest) + FC_THROW("logic error: read failed but no error conditon"); + } +} + +template +void http_call_impl::process_response() { + + auto &buf = response_buf; + auto &body = response.body; + + read_until(socket(), buf, crlfcrlf); + + process_headers(); + + // check content length + + if (content_length >= 0) { + if (content_length < 2) { // minimum content is "{}" + FC_THROW("invalid response body (too short)"); + } + if (content_length > http_call::response_size_limit_bytes) + FC_THROW("response body size limit exceeded"); + body.reserve(content_length); + } + + if (transfer_encoding_chunked) { + read_body_chunked(); + } else { + if (content_length < 0) + read_body_until_eof(); + else { + if (content_length > 0) + read_body_exact(); + } + } +} + +} // namespace detail + +// https_call + +http_call::http_call(const url_data &url, const std::string &method, const std::string &headers) : + m_host(url.host), + m_method(method), + m_headers(headers) { + + if (url.schema_type == url_schema_type::https) { + m_context = new boost::asio::ssl::context(ssl::context::tlsv12_client); + } else { + m_context = 0; + } + + if (url.port) + m_port_default = url.port; + else { + if (url.schema_type == url_schema_type::https) + m_port_default = https_port; + else + m_port_default = http_port; + } + + m_port = m_port_default; + + set_path(url.path); + + try { + ctor_priv(); + } catch (...) { + if (m_context) + delete m_context; + throw; + } +} + +http_call::~http_call() { + if (m_context) + delete m_context; +} + +bool http_call::is_ssl() const { + return m_context != 0; +} + +const std::string &http_call::path() const { + return m_path; +} + +void http_call::set_path(const std::string &path) { + if (path.empty()) + m_path = "/"; + else + m_path = path; +} + +void http_call::set_method(const std::string &method) { + m_method = method; +} + +void http_call::set_headers(const std::string &headers) { + m_headers = headers; +} + +const std::string &http_call::host() const { + return m_host; +} + +void http_call::set_host(const std::string &host) { + m_host = host; +} + +uint16_t http_call::port() const { + return m_port; +} + +void http_call::set_port(uint16_t port) { + if (port) + m_port = port; + else + m_port = m_port_default; +} + +bool http_call::exec(const http_request &request, http_response *response) { + + //ASSERT(response); + auto &resp = *response; + m_error_what = decltype(m_error_what)(); + resp.clear(); + + try { + try { + using namespace detail; + if (!m_context) + http_call_impl(*this, request.body.data(), request.body.size(), request.content_type, resp).exec(); + else + http_call_impl(*this, request.body.data(), request.body.size(), request.content_type, resp).exec(); + return true; + } catch (const std::exception &e) { + m_error_what = e.what(); + } + } catch (...) { + m_error_what = "unknown exception"; + } + + resp.clear(); + return false; +} + +const std::string &http_call::error_what() const { + return m_error_what; +} + +void http_call::ctor_priv() { + if (m_context) { + m_context->set_default_verify_paths(); + m_context->set_options(ssl::context::default_workarounds); + } +} + +}} // namespace graphene::peerplays_sidechain + +namespace graphene { namespace peerplays_sidechain { + +rpc_client::rpc_client(const std::string &url, const std::string &user_name, const std::string &password, bool debug) : + debug_rpc_calls(debug), + request_id(0), + client(url) + +{ + + client.set_method("POST"); + client.set_headers("Authorization : Basic" + fc::base64_encode(user_name + ":" + password)); } std::string rpc_client::retrieve_array_value_from_reply(std::string reply_str, std::string array_path, uint32_t idx) { @@ -91,7 +900,7 @@ std::string rpc_client::send_post_request(std::string method, std::string params boost::property_tree::ptree json; boost::property_tree::read_json(ss, json); - if (reply.status == 200) { + if (reply.status_code == 200) { return ss.str(); } @@ -123,63 +932,82 @@ std::string rpc_client::send_post_request(std::string method, std::string params // return reply; //} -static size_t write_callback(char *ptr, size_t size, size_t nmemb, rpc_reply *reply) { - size_t retval = 0; - if (reply != nullptr) { - reply->body.append(ptr, size * nmemb); - retval = size * nmemb; - } - return retval; -} +//static size_t write_callback(char *ptr, size_t size, size_t nmemb, rpc_reply *reply) { +// size_t retval = 0; +// if (reply != nullptr) { +// reply->body.append(ptr, size * nmemb); +// retval = size * nmemb; +// } +// return retval; +//} -rpc_reply rpc_client::send_post_request(std::string body, bool show_log) { +//rpc_reply rpc_client::send_post_request(std::string body, bool show_log) { +// +// struct curl_slist *headers = nullptr; +// headers = curl_slist_append(headers, "Accept: application/json"); +// headers = curl_slist_append(headers, "Content-Type: application/json"); +// headers = curl_slist_append(headers, "charset: utf-8"); +// +// CURL *curl = curl_easy_init(); +// if (ip.find("https://", 0) != 0) { +// curl_easy_setopt(curl, CURLOPT_URL, ip.c_str()); +// curl_easy_setopt(curl, CURLOPT_PORT, port); +// } else { +// std::string full_address = ip + ":" + std::to_string(port); +// curl_easy_setopt(curl, CURLOPT_URL, full_address.c_str()); +// curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); +// curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false); +// } +// if (!user.empty()) { +// curl_easy_setopt(curl, CURLOPT_USERNAME, user.c_str()); +// curl_easy_setopt(curl, CURLOPT_PASSWORD, password.c_str()); +// } +// +// curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); +// curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str()); +// +// //curl_easy_setopt(curl, CURLOPT_VERBOSE, true); +// +// rpc_reply reply; +// +// curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); +// curl_easy_setopt(curl, CURLOPT_WRITEDATA, &reply); +// +// curl_easy_perform(curl); +// +// curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &reply.status); +// +// curl_easy_cleanup(curl); +// curl_slist_free_all(headers); +// +// if (show_log) { +// std::string url = ip + ":" + std::to_string(port); +// ilog("### Request URL: ${url}", ("url", url)); +// ilog("### Request: ${body}", ("body", body)); +// std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); +// ilog("### Response: ${ss}", ("ss", ss.str())); +// } +// +// return reply; +//} - struct curl_slist *headers = nullptr; - headers = curl_slist_append(headers, "Accept: application/json"); - headers = curl_slist_append(headers, "Content-Type: application/json"); - headers = curl_slist_append(headers, "charset: utf-8"); +http_response rpc_client::send_post_request(const std::string &body, bool show_log) { - CURL *curl = curl_easy_init(); - if (ip.find("https://", 0) != 0) { - curl_easy_setopt(curl, CURLOPT_URL, ip.c_str()); - curl_easy_setopt(curl, CURLOPT_PORT, port); - } else { - std::string full_address = ip + ":" + std::to_string(port); - curl_easy_setopt(curl, CURLOPT_URL, full_address.c_str()); - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false); - } - if (!user.empty()) { - curl_easy_setopt(curl, CURLOPT_USERNAME, user.c_str()); - curl_easy_setopt(curl, CURLOPT_PASSWORD, password.c_str()); - } + http_request request(body, "application/json"); + http_response response; - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str()); - - //curl_easy_setopt(curl, CURLOPT_VERBOSE, true); - - rpc_reply reply; - - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &reply); - - curl_easy_perform(curl); - - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &reply.status); - - curl_easy_cleanup(curl); - curl_slist_free_all(headers); + client.exec(request, &response); if (show_log) { - std::string url = ip + ":" + std::to_string(port); + std::string url = client.is_ssl() ? "https" : "http"; + url = url + "://" + client.host() + ":" + std::to_string(client.port()) + client.path(); ilog("### Request URL: ${url}", ("url", url)); ilog("### Request: ${body}", ("body", body)); - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + std::stringstream ss(std::string(response.body.begin(), response.body.end())); ilog("### Response: ${ss}", ("ss", ss.str())); } - return reply; + return response; } }} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/common/rpc_client.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/common/rpc_client.hpp index f61bdb3f..63d218ee 100644 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/common/rpc_client.hpp +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/common/rpc_client.hpp @@ -3,37 +3,134 @@ #include #include -#include +#include +#include + +//#include namespace graphene { namespace peerplays_sidechain { -struct rpc_reply { - uint16_t status; - std::string body; +enum class url_schema_type { unknown, + http, + https, }; +// utl + +url_schema_type identify_url_schema_type(const std::string &schema_name); + +struct url_data { + + url_schema_type schema_type; + std::string schema; + std::string host; + uint16_t port; + std::string path; + + url_data() : + schema_type(url_schema_type::unknown), + port(0) { + } + + url_data(const std::string &url); + + void clear(); + + bool parse(const std::string &url); +}; + +struct http_request { + + std::string body; + std::string content_type; + + http_request(const std::string &body_, const std::string &content_type_) : + body(body_), + content_type(content_type_) { + } +}; + +struct http_response { + + uint16_t status_code; + std::string body; + + void clear() { + status_code = 0; + body = decltype(body)(); + } +}; + +namespace detail { +template +class http_call_impl; +class tcp_socket; +class ssl_socket; +} // namespace detail + +class http_call { +public: + http_call(const url_data &url, const std::string &method = std::string(), const std::string &headers = std::string()); + ~http_call(); + + bool is_ssl() const; + + const std::string &path() const; + void set_path(const std::string &path); + void set_method(const std::string &method); + void set_headers(const std::string &headers); + const std::string &host() const; + void set_host(const std::string &host); + + uint16_t port() const; + void set_port(uint16_t port); + + bool exec(const http_request &request, http_response *response); + + const std::string &error_what() const; + +private: + template + friend class detail::http_call_impl; + friend detail::tcp_socket; + friend detail::ssl_socket; + static constexpr auto response_size_limit_bytes = 16 * 1024 * 1024; + static constexpr auto response_first_alloc_bytes = 32 * 1024; + static constexpr auto response_next_alloc_bytes = 256 * 1024; + std::string m_host; + uint16_t m_port_default; + uint16_t m_port; + std::string m_path; + std::string m_method; + std::string m_headers; + std::string m_error_what; + + boost::asio::io_service m_service; + boost::asio::ssl::context *m_context; + boost::asio::ip::tcp::endpoint m_endpoint; + + void ctor_priv(); +}; + +}} // namespace graphene::peerplays_sidechain + +namespace graphene { namespace peerplays_sidechain { + class rpc_client { public: - rpc_client(std::string _ip, uint32_t _port, std::string _user, std::string _password, bool _debug_rpc_calls); + rpc_client(const std::string &url, const std::string &user_name, const std::string &password, bool debug); 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 ip; - uint32_t port; - std::string user; - std::string password; bool debug_rpc_calls; - uint32_t request_id; - fc::http::header authorization; - private: - //fc::http::reply send_post_request(std::string body, bool show_log); - rpc_reply send_post_request(std::string body, bool show_log); + http_call client; + http_response send_post_request(const std::string &body, bool show_log); }; }} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_bitcoin.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_bitcoin.hpp index dd6ebb28..9b08bc15 100644 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_bitcoin.hpp +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_bitcoin.hpp @@ -7,6 +7,8 @@ #include +#include + #include #include @@ -114,6 +116,9 @@ private: fc::future on_changed_objects_task; bitcoin::bitcoin_address::network network_type; + std::mutex event_handler_mutex; + typedef std::lock_guard scoped_lock; + std::string create_primary_wallet_address(const std::vector &son_pubkeys); std::string create_primary_wallet_transaction(const son_wallet_object &prev_swo, std::string new_sw_address); diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_hive.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_hive.hpp index 675c3cdd..47e6bf90 100644 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_hive.hpp +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_hive.hpp @@ -14,7 +14,7 @@ namespace graphene { namespace peerplays_sidechain { class hive_node_rpc_client : public rpc_client { public: - hive_node_rpc_client(std::string _ip, uint32_t _port, std::string _user, std::string _password, bool _debug_rpc_calls); + hive_node_rpc_client(const std::string &url, const std::string &user_name, const std::string &password, bool debug_rpc_calls); std::string account_history_api_get_transaction(std::string transaction_id); std::string block_api_get_block(uint32_t block_number); @@ -48,8 +48,7 @@ public: bool settle_sidechain_transaction(const sidechain_transaction_object &sto, asset &settle_amount); private: - std::string node_ip; - uint32_t node_rpc_port; + std::string node_rpc_url; std::string node_rpc_user; std::string node_rpc_password; hive_node_rpc_client *node_rpc_client; diff --git a/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp b/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp index 02a046fc..f9433971 100644 --- a/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp +++ b/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp @@ -156,8 +156,8 @@ void peerplays_sidechain_plugin_impl::plugin_set_program_options( "Tuple of [Bitcoin public key, Bitcoin private key] (may specify multiple times)"); cli.add_options()("hive-sidechain-enabled", bpo::value()->default_value(false), "Hive sidechain handler enabled"); - cli.add_options()("hive-node-ip", bpo::value()->default_value("127.0.0.1"), "Hive node IP address"); - cli.add_options()("hive-node-rpc-port", bpo::value()->default_value(28090), "Hive node RPC port"); + cli.add_options()("hive-node-rpc-url", bpo::value()->default_value("127.0.0.1"), "Hive node URL (http(s)://host:port/)"); + //cli.add_options()("hive-node-rpc-port", bpo::value()->default_value(28090), "Deprecated: Hive node RPC port. See: hive-node-rpc-url"); cli.add_options()("hive-node-rpc-user", bpo::value(), "Hive node RPC user"); cli.add_options()("hive-node-rpc-password", bpo::value(), "Hive node RPC password"); cli.add_options()("hive-private-key", bpo::value>()->composing()->multitoken()->DEFAULT_VALUE_VECTOR(std::make_pair("TST6LLegbAgLAy28EHrffBVuANFWcFgmqRMW13wBmTExqFE9SCkg4", "5JNHfZYKGaomSFvd4NUdQ9qMcEAC43kujbfjueTHpVapX1Kzq2n")), diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp index ca4e9149..940977e4 100644 --- a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp @@ -1801,38 +1801,39 @@ std::string sidechain_net_handler_bitcoin::send_transaction(const sidechain_tran void sidechain_net_handler_bitcoin::handle_event(const std::string &event_data) { std::string block = bitcoin_client->getblock(event_data); - if (block != "") { - const auto &vins = extract_info_from_block(block); + if (block.empty()) + return; - const auto &sidechain_addresses_idx = database.get_index_type().indices().get(); + auto vins = extract_info_from_block(block); + scoped_lock interlock(event_handler_mutex); + const auto &sidechain_addresses_idx = database.get_index_type().indices().get(); - for (const auto &v : vins) { - // !!! EXTRACT DEPOSIT ADDRESS FROM SIDECHAIN ADDRESS OBJECT - const auto &addr_itr = sidechain_addresses_idx.find(std::make_tuple(sidechain, v.address, time_point_sec::maximum())); - if (addr_itr == sidechain_addresses_idx.end()) - continue; + for (const auto &v : vins) { + // !!! EXTRACT DEPOSIT ADDRESS FROM SIDECHAIN ADDRESS OBJECT + const auto &addr_itr = sidechain_addresses_idx.find(std::make_tuple(sidechain, v.address, time_point_sec::maximum())); + if (addr_itr == sidechain_addresses_idx.end()) + continue; - std::stringstream ss; - ss << "bitcoin" - << "-" << v.out.hash_tx << "-" << v.out.n_vout; - std::string sidechain_uid = ss.str(); + std::stringstream ss; + ss << "bitcoin" + << "-" << v.out.hash_tx << "-" << v.out.n_vout; + std::string sidechain_uid = ss.str(); - sidechain_event_data sed; - sed.timestamp = database.head_block_time(); - sed.block_num = database.head_block_num(); - sed.sidechain = addr_itr->sidechain; - sed.sidechain_uid = sidechain_uid; - sed.sidechain_transaction_id = v.out.hash_tx; - sed.sidechain_from = v.address; - sed.sidechain_to = ""; - sed.sidechain_currency = "BTC"; - sed.sidechain_amount = v.out.amount; - sed.peerplays_from = addr_itr->sidechain_address_account; - sed.peerplays_to = database.get_global_properties().parameters.son_account(); - price btc_price = database.get(database.get_global_properties().parameters.btc_asset()).options.core_exchange_rate; - sed.peerplays_asset = asset(sed.sidechain_amount * btc_price.base.amount / btc_price.quote.amount); - sidechain_event_data_received(sed); - } + sidechain_event_data sed; + sed.timestamp = database.head_block_time(); + sed.block_num = database.head_block_num(); + sed.sidechain = addr_itr->sidechain; + sed.sidechain_uid = sidechain_uid; + sed.sidechain_transaction_id = v.out.hash_tx; + sed.sidechain_from = v.address; + sed.sidechain_to = ""; + sed.sidechain_currency = "BTC"; + sed.sidechain_amount = v.out.amount; + sed.peerplays_from = addr_itr->sidechain_address_account; + sed.peerplays_to = database.get_global_properties().parameters.son_account(); + price btc_price = database.get(database.get_global_properties().parameters.btc_asset()).options.core_exchange_rate; + sed.peerplays_asset = asset(sed.sidechain_amount * btc_price.base.amount / btc_price.quote.amount); + sidechain_event_data_received(sed); } } diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_hive.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_hive.cpp index 3b3701dd..02287a95 100644 --- a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_hive.cpp +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_hive.cpp @@ -32,35 +32,8 @@ namespace graphene { namespace peerplays_sidechain { -std::string resolve_host_addr(const std::string &host_name) { - using namespace boost::asio; - io_service service; - ip::tcp::resolver resolver(service); - auto query = ip::tcp::resolver::query(host_name, std::string()); - auto iter = resolver.resolve(query); - auto endpoint = *iter; - auto addr = ((ip::tcp::endpoint)endpoint).address(); - return addr.to_string(); -} - -std::string strip_proto_name(const std::string &url, std::string *schema) { - auto index = url.find("://"); - if (index == std::string::npos) { - if (schema) - schema->clear(); - return url; - } - if (schema) - schema->assign(&url[0], &url[index + 3]); - return url.substr(index + 3); -} - -}} // namespace graphene::peerplays_sidechain - -namespace graphene { namespace peerplays_sidechain { - -hive_node_rpc_client::hive_node_rpc_client(std::string _ip, uint32_t _port, std::string _user, std::string _password, bool _debug_rpc_calls) : - rpc_client(_ip, _port, _user, _password, _debug_rpc_calls) { +hive_node_rpc_client::hive_node_rpc_client(const std::string &url, const std::string &user_name, const std::string &password, bool debug_rpc_calls) : + rpc_client(url, user_name, password, debug_rpc_calls) { } std::string hive_node_rpc_client::account_history_api_get_transaction(std::string transaction_id) { @@ -149,8 +122,14 @@ sidechain_net_handler_hive::sidechain_net_handler_hive(peerplays_sidechain_plugi debug_rpc_calls = options.at("debug-rpc-calls").as(); } - node_ip = options.at("hive-node-ip").as(); - node_rpc_port = options.at("hive-node-rpc-port").as(); + if (options.count("hive-node-rpc-url")) + node_rpc_url = options.at("hive-node-rpc-url").as(); + + // if (options.count("hive-node-rpc-port")) + // node_rpc_port = options.at("hive-node-rpc-port").as(); + // else + // node_rpc_port = 0; + if (options.count("hive-node-rpc-user")) { node_rpc_user = options.at("hive-node-rpc-user").as(); } else { @@ -174,11 +153,12 @@ sidechain_net_handler_hive::sidechain_net_handler_hive(peerplays_sidechain_plugi } } - node_rpc_client = new hive_node_rpc_client(node_ip, node_rpc_port, node_rpc_user, node_rpc_password, debug_rpc_calls); + node_rpc_client = new hive_node_rpc_client(node_rpc_url, node_rpc_user, node_rpc_password, debug_rpc_calls); std::string chain_id_str = node_rpc_client->get_chain_id(); if (chain_id_str.empty()) { - elog("No Hive node running at ${ip} or wrong rpc port: ${port}", ("ip", node_ip)("port", node_rpc_port)); + // elog("No Hive node running at ${url} or wrong rpc port: ${port}", ("url", node_rpc_url)("port", node_rpc_port)); + elog("No Hive node running at ${url}", ("url", node_rpc_url)); FC_ASSERT(false); } chain_id = chain_id_type(chain_id_str);