From 5bfd6856847350d6bb71655ef63cb945ba7ff1a3 Mon Sep 17 00:00:00 2001 From: serkixenos Date: Tue, 26 Jul 2022 13:47:16 +0000 Subject: [PATCH] Boost Beast based RPC client --- .../peerplays_sidechain/common/rpc_client.cpp | 1091 +++------------- .../peerplays_sidechain/common/rpc_client.hpp | 129 +- .../sidechain_net_handler_bitcoin.hpp | 24 +- .../sidechain_net_handler_bitcoin.cpp | 1143 +++-------------- 4 files changed, 346 insertions(+), 2041 deletions(-) diff --git a/libraries/plugins/peerplays_sidechain/common/rpc_client.cpp b/libraries/plugins/peerplays_sidechain/common/rpc_client.cpp index d08d337b..173019eb 100644 --- a/libraries/plugins/peerplays_sidechain/common/rpc_client.cpp +++ b/libraries/plugins/peerplays_sidechain/common/rpc_client.cpp @@ -1,883 +1,110 @@ #include +#include #include #include -//#include - -#include -#include -#include #include #include +#include + +#include +#include +#include +#include +#include + +#include #include #include -#include namespace graphene { namespace peerplays_sidechain { -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 > ((size_t)content_length)) - FC_THROW("invalid response body (content length mismatch)"); - - body.resize(content_length); - - if (avail) { - if (avail != ((size_t)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), +rpc_client::rpc_client(std::string _url, std::string _user, std::string _password, bool _debug_rpc_calls) : + url(_url), + user(_user), + password(_password), + debug_rpc_calls(_debug_rpc_calls), request_id(0), - client(url) + resolver(ioc) { -{ + std::string reg_expr = "^((?Phttps|http):\\/\\/)?(?P[a-zA-Z0-9\\-\\.]+)(:(?P\\d{1,5}))?(?P\\/.+)?"; + boost::xpressive::sregex sr = boost::xpressive::sregex::compile(reg_expr); - client.set_method("POST"); - client.set_headers("Authorization : Basic" + fc::base64_encode(user_name + ":" + password)); + boost::xpressive::smatch sm; + + if (boost::xpressive::regex_search(url, sm, sr)) { + protocol = sm["Protocol"]; + if (protocol.empty()) { + protocol = "http"; + } + + host = sm["Host"]; + if (host.empty()) { + host + "localhost"; + } + + port = sm["Port"]; + if (port.empty()) { + port = "80"; + } + + target = sm["Target"]; + if (target.empty()) { + target = "/"; + } + + authorization = "Basic " + fc::base64_encode(user + ":" + password); + results = resolver.resolve(host, port); + + } else { + elog("Invalid URL: ${url}", ("url", url)); + } } std::string rpc_client::retrieve_array_value_from_reply(std::string reply_str, std::string array_path, uint32_t idx) { - if (reply_str.empty()) - return std::string(); 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 std::string(); - } - auto json_result = json.get_child("result"); - if (json_result.find(array_path) == json_result.not_found()) { - return std::string(); + return ""; } - boost::property_tree::ptree array_ptree = json_result; - if (!array_path.empty()) { - array_ptree = json_result.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(); + 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(); + return ""; } std::string rpc_client::retrieve_value_from_reply(std::string reply_str, std::string value_path) { - if (reply_str.empty()) - return std::string(); 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 std::string(); + return ""; } - auto json_result = json.get_child("result"); - if (json_result.find(value_path) == json_result.not_found()) { - return std::string(); + + auto json_result = json.get_child_optional("result"); + if (json_result) { + return json_result.get().get(value_path); } - return json_result.get(value_path); + + return json.get("result"); } std::string rpc_client::send_post_request(std::string method, std::string params, bool show_log) { @@ -904,114 +131,98 @@ 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_code == 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())); } + + if (reply.status == 200) { + return ss.str(); + } + return ""; } -//fc::http::reply rpc_client::send_post_request(std::string body, bool show_log) { -// fc::http::connection conn; -// conn.connect_to(fc::ip::endpoint(fc::ip::address(ip), port)); -// -// std::string url = "http://" + ip + ":" + std::to_string(port); -// -// //if (wallet.length() > 0) { -// // url = url + "/wallet/" + wallet; -// //} -// -// fc::http::reply reply = conn.request("POST", url, body, fc::http::headers{authorization}); -// -// if (show_log) { -// 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; -//} +rpc_reply rpc_client::send_post_request(std::string body, bool show_log) { -//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; -//} + // These object is used as a context for ssl connection + boost::asio::ssl::context ctx(boost::asio::ssl::context::tlsv12_client); -//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; -//} + boost::beast::net::ssl::stream ssl_tcp_stream(ioc, ctx); + boost::beast::tcp_stream tcp_stream(ioc); -http_response rpc_client::send_post_request(const std::string &body, bool show_log) { - - http_request request(body, "application/json"); - http_response response; - - client.exec(request, &response); - - if (show_log) { - 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(response.body.begin(), response.body.end())); - ilog("### Response: ${ss}", ("ss", ss.str())); + // Set SNI Hostname (many hosts need this to handshake successfully) + if (protocol == "https") { + if (!SSL_set_tlsext_host_name(ssl_tcp_stream.native_handle(), host.c_str())) { + boost::beast::error_code ec{static_cast(::ERR_get_error()), boost::asio::error::get_ssl_category()}; + throw boost::beast::system_error{ec}; + } + ctx.set_default_verify_paths(); + ctx.set_verify_mode(boost::asio::ssl::verify_peer); } - return response; + // Make the connection on the IP address we get from a lookup + if (protocol == "https") { + boost::beast::get_lowest_layer(ssl_tcp_stream).connect(results); + ssl_tcp_stream.handshake(boost::beast::net::ssl::stream_base::client); + } else { + tcp_stream.connect(results); + } + + // 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::authorization, authorization); + 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; + + // Send the HTTP request to the remote host + if (protocol == "https") + boost::beast::http::write(ssl_tcp_stream, req); + else + boost::beast::http::write(tcp_stream, req); + + // 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 + if (protocol == "https") + boost::beast::http::read(ssl_tcp_stream, buffer, res); + else + boost::beast::http::read(tcp_stream, buffer, res); + + // Gracefully close the socket + boost::beast::error_code ec; + if (protocol == "https") { + boost::beast::get_lowest_layer(ssl_tcp_stream).close(); + } else { + tcp_stream.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); + } + + // not_connected happens sometimes. Also on ssl level some servers are managing + // connecntion close, so closing here will sometimes end up with error stream truncated + // so don't bother reporting it. + if (ec && ec != boost::beast::errc::not_connected && ec != boost::asio::ssl::error::stream_truncated) + throw boost::beast::system_error{ec}; + + std::string rbody{boost::asio::buffers_begin(res.body().data()), + boost::asio::buffers_end(res.body().data())}; + rpc_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/rpc_client.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/common/rpc_client.hpp index 63d218ee..1a797782 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,134 +3,43 @@ #include #include -#include -#include - -//#include +#include +#include namespace graphene { namespace peerplays_sidechain { -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 { - +struct rpc_reply { + uint16_t status; 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(const std::string &url, const std::string &user_name, const std::string &password, bool debug); + rpc_client(std::string _url, std::string _user, std::string _password, bool _debug_rpc_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 authorization; + + std::string user; + std::string password; bool debug_rpc_calls; uint32_t request_id; private: - http_call client; - http_response send_post_request(const std::string &body, bool show_log); + rpc_reply send_post_request(std::string body, bool show_log); + + boost::beast::net::io_context ioc; + boost::beast::net::ip::tcp::resolver resolver; + boost::asio::ip::basic_resolver_results results; }; }} // 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 6fd1fcfa..55af0c4e 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 @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -22,7 +23,7 @@ public: uint64_t amount_; }; -class bitcoin_rpc_client { +class bitcoin_rpc_client: public rpc_client { public: enum class multi_type { script, @@ -41,49 +42,30 @@ public: }; public: - bitcoin_rpc_client(std::string _ip, uint32_t _rpc, std::string _user, std::string _password, std::string _wallet, std::string _wallet_password, bool _debug_rpc_calls); + bitcoin_rpc_client(std::string _url, std::string _user, std::string _password, bool _debug_rpc_calls); - std::string addmultisigaddress(const uint32_t nrequired, const std::vector public_keys); - std::string combinepsbt(const vector &psbts); - std::string createmultisig(const uint32_t nrequired, const std::vector public_keys); - std::string createpsbt(const std::vector &ins, const fc::flat_map outs); - std::string createrawtransaction(const std::vector &ins, const fc::flat_map outs); std::string createwallet(const std::string &wallet_name); - std::string decodepsbt(std::string const &tx_psbt); - std::string decoderawtransaction(std::string const &tx_hex); - std::string encryptwallet(const std::string &passphrase); uint64_t estimatesmartfee(uint16_t conf_target = 128); - std::string finalizepsbt(std::string const &tx_psbt); - std::string getaddressinfo(const std::string &address); std::string getblock(const std::string &block_hash, int32_t verbosity = 2); std::string getrawtransaction(const std::string &txid, const bool verbose = false); std::string getnetworkinfo(); - std::string gettransaction(const std::string &txid, const bool include_watch_only = false); std::string getblockchaininfo(); - void importaddress(const std::string &address_or_script, const std::string &label = "", const bool rescan = true, const bool p2sh = false); void importmulti(const std::vector &address_or_script_array, const bool rescan = true); std::vector listunspent(const uint32_t minconf = 1, const uint32_t maxconf = 9999999); std::vector listunspent_by_address_and_amount(const std::string &address, double transfer_amount, const uint32_t minconf = 1, const uint32_t maxconf = 9999999); std::string loadwallet(const std::string &filename); std::string sendrawtransaction(const std::string &tx_hex); - std::string signrawtransactionwithwallet(const std::string &tx_hash); - std::string unloadwallet(const std::string &filename); std::string walletlock(); - std::string walletprocesspsbt(std::string const &tx_psbt); bool walletpassphrase(const std::string &passphrase, uint32_t timeout = 60); private: - fc::http::reply send_post_request(std::string body, bool show_log); - std::string ip; uint32_t rpc_port; std::string user; std::string password; std::string wallet; std::string wallet_password; - bool debug_rpc_calls; - fc::http::header authorization; }; // ============================================================================= diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp index 6203ff79..82511bed 100644 --- a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp @@ -25,1032 +25,230 @@ namespace graphene { namespace peerplays_sidechain { // ============================================================================= -bitcoin_rpc_client::bitcoin_rpc_client(std::string _ip, uint32_t _rpc, std::string _user, std::string _password, std::string _wallet, std::string _wallet_password, bool _debug_rpc_calls) : - ip(_ip), - rpc_port(_rpc), - user(_user), - password(_password), - wallet(_wallet), - wallet_password(_wallet_password), - debug_rpc_calls(_debug_rpc_calls) { - authorization.key = "Authorization"; - authorization.val = "Basic " + fc::base64_encode(user + ":" + password); -} - -std::string bitcoin_rpc_client::addmultisigaddress(const uint32_t nrequired, const std::vector public_keys) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"addmultisigaddress\", " - "\"method\": \"addmultisigaddress\", \"params\": ["); - try { - std::string params = std::to_string(nrequired) + ", ["; - std::string pubkeys = ""; - for (std::string pubkey : public_keys) { - if (!pubkeys.empty()) { - pubkeys = pubkeys + ","; - } - pubkeys = pubkeys + std::string("\"") + pubkey + std::string("\""); - } - params = params + pubkeys + std::string("]"); - body = body + params + std::string(", null, \"bech32\"] }"); - - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin 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("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } -} - -std::string bitcoin_rpc_client::combinepsbt(const vector &psbts) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"combinepsbt\", \"method\": " - "\"combinepsbt\", \"params\": [["); - try { - std::string params = ""; - for (std::string psbt : psbts) { - if (!params.empty()) { - params = params + ","; - } - params = params + std::string("\"") + psbt + std::string("\""); - } - body = body + params + std::string("]] }"); - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin 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) { - std::stringstream ss; - boost::property_tree::json_parser::write_json(ss, json); - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } -} - -std::string bitcoin_rpc_client::createmultisig(const uint32_t nrequired, const std::vector public_keys) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"createmultisig\", " - "\"method\": \"createmultisig\", \"params\": ["); - try { - std::string params = std::to_string(nrequired) + ", ["; - std::string pubkeys = ""; - for (std::string pubkey : public_keys) { - if (!pubkeys.empty()) { - pubkeys = pubkeys + ","; - } - pubkeys = pubkeys + std::string("\"") + pubkey + std::string("\""); - } - params = params + pubkeys + std::string("]"); - body = body + params + std::string(", \"p2sh-segwit\" ] }"); - - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin 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("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } -} - -std::string bitcoin_rpc_client::createpsbt(const std::vector &ins, const fc::flat_map outs) { - std::string body("{\"jsonrpc\": \"1.0\", \"id\":\"createpsbt\", " - "\"method\": \"createpsbt\", \"params\": ["); - try { - body += "["; - bool first = true; - for (const auto &entry : ins) { - if (!first) - body += ","; - body += "{\"txid\":\"" + entry.txid_ + "\",\"vout\":" + std::to_string(entry.out_num_) + "}"; - first = false; - } - body += "],["; - first = true; - for (const auto &entry : outs) { - if (!first) - body += ","; - body += "{\"" + entry.first + "\":" + std::to_string(entry.second) + "}"; - first = false; - } - body += std::string("]] }"); - - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin 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) { - if (json.find("result") != json.not_found()) { - return json.get("result"); - } - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } -} - -std::string bitcoin_rpc_client::createrawtransaction(const std::vector &ins, const fc::flat_map outs) { - std::string body("{\"jsonrpc\": \"1.0\", \"id\":\"createrawtransaction\", " - "\"method\": \"createrawtransaction\", \"params\": ["); - try { - body += "["; - bool first = true; - for (const auto &entry : ins) { - if (!first) - body += ","; - body += "{\"txid\":\"" + entry.txid_ + "\",\"vout\":" + std::to_string(entry.out_num_) + "}"; - first = false; - } - body += "],["; - first = true; - for (const auto &entry : outs) { - if (!first) - body += ","; - body += "{\"" + entry.first + "\":" + std::to_string(entry.second) + "}"; - first = false; - } - body += std::string("]] }"); - - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin 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) { - if (json.find("result") != json.not_found()) { - return json.get("result"); - } - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } +bitcoin_rpc_client::bitcoin_rpc_client(std::string _url, std::string _user, std::string _password, bool _debug_rpc_calls) : + rpc_client(_url, _user, _password, _debug_rpc_calls) { } std::string bitcoin_rpc_client::createwallet(const std::string &wallet_name) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"createwallet\", \"method\": " - "\"createwallet\", \"params\": [\"" + - wallet_name + "\"] }"); - try { - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin 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) { - std::stringstream ss; - boost::property_tree::json_parser::write_json(ss, json.get_child("result")); - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } -} - -std::string bitcoin_rpc_client::decodepsbt(std::string const &tx_psbt) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"decodepsbt\", \"method\": " - "\"decodepsbt\", \"params\": [\"" + - tx_psbt + "\"] }"); - try { - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin 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) { - std::stringstream ss; - boost::property_tree::json_parser::write_json(ss, json.get_child("result")); - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } -} - -std::string bitcoin_rpc_client::decoderawtransaction(std::string const &tx_hex) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"decoderawtransaction\", \"method\": " - "\"decoderawtransaction\", \"params\": [\"" + - tx_hex + "\"] }"); - try { - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin 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) { - std::stringstream ss; - boost::property_tree::json_parser::write_json(ss, json.get_child("result")); - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } -} - -std::string bitcoin_rpc_client::encryptwallet(const std::string &passphrase) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"encryptwallet\", \"method\": " - "\"encryptwallet\", \"params\": [\"" + - passphrase + "\"] }"); - try { - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin 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) { - std::stringstream ss; - boost::property_tree::json_parser::write_json(ss, json.get_child("result")); - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } + std::string params = std::string("[\"") + wallet_name + std::string("\"]"); + std::string str = send_post_request("createwallet", params, debug_rpc_calls); + return str; } uint64_t bitcoin_rpc_client::estimatesmartfee(uint16_t conf_target) { - const auto body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"estimatesmartfee\", " - "\"method\": \"estimatesmartfee\", \"params\": [" + - std::to_string(conf_target) + std::string("] }")); - try { - const auto reply = send_post_request(body, debug_rpc_calls); + std::string params = std::string("[") + std::to_string(conf_target) + std::string("]"); + std::string str = send_post_request("estimatesmartfee", params, debug_rpc_calls); - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return 0; - } - - 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) { - if (json.find("result") != json.not_found()) { - auto json_result = json.get_child("result"); - if (json_result.find("feerate") != json_result.not_found()) { - auto feerate_str = json_result.get("feerate"); - feerate_str.erase(std::remove(feerate_str.begin(), feerate_str.end(), '.'), feerate_str.end()); - return std::stoll(feerate_str); - } - - if (json_result.find("errors") != json_result.not_found()) { - wlog("Bitcoin RPC call ${function} with body ${body} executed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - } - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return 20000; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return 20000; + if (str.length() == 0) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return 0; } -} -std::string bitcoin_rpc_client::finalizepsbt(std::string const &tx_psbt) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"finalizepsbt\", \"method\": " - "\"finalizepsbt\", \"params\": [\"" + - tx_psbt + "\"] }"); - try { - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return ""; + std::stringstream ss(str); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + if (json.find("result") != json.not_found()) { + auto json_result = json.get_child("result"); + if (json_result.find("feerate") != json_result.not_found()) { + auto feerate_str = json_result.get("feerate"); + feerate_str.erase(std::remove(feerate_str.begin(), feerate_str.end(), '.'), feerate_str.end()); + return std::stoll(feerate_str); } - - 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("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; } -} -std::string bitcoin_rpc_client::getaddressinfo(const std::string &address) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"getaddressinfo\", \"method\": " - "\"getaddressinfo\", \"params\": [\"" + - address + "\"] }"); - try { - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin 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) { - std::stringstream ss; - boost::property_tree::json_parser::write_json(ss, json.get_child("result")); - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } + return 20000; } std::string bitcoin_rpc_client::getblock(const std::string &block_hash, int32_t verbosity) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"getblock\", \"method\": " - "\"getblock\", \"params\": [\"" + - block_hash + "\", " + std::to_string(verbosity) + "] }"); - try { - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin 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) { - std::stringstream ss; - boost::property_tree::json_parser::write_json(ss, json.get_child("result")); - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } + std::string params = std::string("[\"") + block_hash + std::string("\",") + std::to_string(verbosity) + std::string("]"); + std::string str = send_post_request("getblock", params, debug_rpc_calls); + return str; } std::string bitcoin_rpc_client::getnetworkinfo() { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"getnetworkinfo\", \"method\": " - "\"getnetworkinfo\", \"params\": [] }"); - try { - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin 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("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } + std::string params = std::string("[]"); + std::string str = send_post_request("getnetworkinfo", params, debug_rpc_calls); + return str; } std::string bitcoin_rpc_client::getrawtransaction(const std::string &txid, const bool verbose) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"getrawtransaction\", \"method\": " - "\"getrawtransaction\", \"params\": ["); - try { - std::string params = "\"" + txid + "\", " + (verbose ? "true" : "false"); - body = body + params + "] }"; - - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin 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("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } -} - -std::string bitcoin_rpc_client::gettransaction(const std::string &txid, const bool include_watch_only) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"gettransaction\", \"method\": " - "\"gettransaction\", \"params\": ["); - try { - std::string params = "\"" + txid + "\", " + (include_watch_only ? "true" : "false"); - body = body + params + "] }"; - - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin 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("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } + std::string params = std::string("[\"") + txid + std::string("\",") + (verbose ? "true" : "false") + std::string("]"); + std::string str = send_post_request("getrawtransaction", params, debug_rpc_calls); + return str; } std::string bitcoin_rpc_client::getblockchaininfo() { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"getblockchaininfo\", \"method\": " - "\"getblockchaininfo\", \"params\": [] }"); - try { - const auto reply = send_post_request(body, debug_rpc_calls); + std::string params = std::string("[]"); + std::string str = send_post_request("getblockchaininfo", params, debug_rpc_calls); - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return ""; - } + if (str.length() > 0) { - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + std::stringstream ss(str); boost::property_tree::ptree json; boost::property_tree::read_json(ss, json); - if (reply.status == 200) { - std::stringstream ss; - boost::property_tree::json_parser::write_json(ss, json.get_child("result")); - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; + boost::property_tree::json_parser::write_json(ss, json.get_child("result")); + return ss.str(); } -} -void bitcoin_rpc_client::importaddress(const std::string &address_or_script, const std::string &label, const bool rescan, const bool p2sh) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"importaddress\", " - "\"method\": \"importaddress\", \"params\": ["); - try { - std::string params = "\"" + address_or_script + "\", " + - "\"" + label + "\", " + - (rescan ? "true" : "false") + ", " + - (p2sh ? "true" : "false"); - body = body + params + "] }"; - - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin 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; - } else if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - } + return str; } void bitcoin_rpc_client::importmulti(const std::vector &address_or_script_array, const bool rescan) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"importmulti\", " - "\"method\": \"importmulti\", \"params\": ["); - try { - std::string argument_1 = "["; - for (const auto ¶m : address_or_script_array) { - argument_1 += "{\"scriptPubKey\": "; - if (param.type == multi_type::address) { - argument_1 += "{\"address\": \"" + param.address_or_script + "\"}, \"label\": \"" + param.label + "\""; - } else if (param.type == multi_type::script) { - argument_1 += "\"" + param.address_or_script + "\", \"internal\": true"; - } else { - FC_THROW("Invalid multi_type."); - } - argument_1 += ", \"timestamp\": " + std::to_string(fc::time_point_sec::from_iso_string("2022-01-01T00:00:00").sec_since_epoch()) + "}"; - - //! Note - /* Creation time of the key expressed in UNIX epoch time, - or the string "now" to substitute the current synced blockchain time. The timestamp of the oldest - key will determine how far back blockchain rescans need to begin for missing wallet transactions. - "now" can be specified to bypass scanning, for keys which are known to never have been used, and - 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest key - creation time of all keys being imported by the importmulti call will be scanned.*/ - - if (¶m != &address_or_script_array.back()) { - argument_1 += ", "; - } + std::string params = std::string("["); + std::string argument_1 = "["; + for (const auto ¶m : address_or_script_array) { + argument_1 += "{\"scriptPubKey\": "; + if (param.type == multi_type::address) { + argument_1 += "{\"address\": \"" + param.address_or_script + "\"}, \"label\": \"" + param.label + "\""; + } else if (param.type == multi_type::script) { + argument_1 += "\"" + param.address_or_script + "\", \"internal\": true"; + } else { + FC_THROW("Invalid multi_type."); } - argument_1 += "]"; + argument_1 += ", \"timestamp\": " + std::to_string(fc::time_point_sec::from_iso_string("2022-01-01T00:00:00").sec_since_epoch()) + "}"; - std::string argument_2 = std::string{"{\"rescan\": "} + (rescan ? "true" : "false") + "}"; - body += argument_1 + ", " + argument_2 + "]}"; + //! Note + /* Creation time of the key expressed in UNIX epoch time, + or the string "now" to substitute the current synced blockchain time. The timestamp of the oldest + key will determine how far back blockchain rescans need to begin for missing wallet transactions. + "now" can be specified to bypass scanning, for keys which are known to never have been used, and + 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest key + creation time of all keys being imported by the importmulti call will be scanned.*/ - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return; + if (¶m != &address_or_script_array.back()) { + argument_1 += ", "; } - - 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; - } else if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); } + argument_1 += "]"; + + std::string argument_2 = std::string{"{\"rescan\": "} + (rescan ? "true" : "false") + "}"; + params += argument_1 + "," + argument_2 + "]"; + + send_post_request("importmulti", params, debug_rpc_calls); } std::vector bitcoin_rpc_client::listunspent(const uint32_t minconf, const uint32_t maxconf) { - const auto body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"pp_plugin\", \"method\": " - "\"listunspent\", \"params\": [" + - std::to_string(minconf) + "," + std::to_string(maxconf) + "] }"); - std::vector result; - try { - const auto reply = send_post_request(body, debug_rpc_calls); + std::string params = std::string("[") + std::to_string(minconf) + "," + std::to_string(maxconf) + std::string("]"); + std::string str = send_post_request("listunspent", params, debug_rpc_calls); - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return result; - } - - 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) { - if (json.count("result")) { - for (auto &entry : json.get_child("result")) { - btc_txout txo; - txo.txid_ = entry.second.get_child("txid").get_value(); - txo.out_num_ = entry.second.get_child("vout").get_value(); - string amount = entry.second.get_child("amount").get_value(); - amount.erase(std::remove(amount.begin(), amount.end(), '.'), amount.end()); - txo.amount_ = std::stoll(amount); - result.push_back(txo); - } - } - } else if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return result; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); + if (str.length() == 0) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); return result; } + + std::stringstream ss(str); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (json.count("result")) { + for (auto &entry : json.get_child("result")) { + btc_txout txo; + txo.txid_ = entry.second.get_child("txid").get_value(); + txo.out_num_ = entry.second.get_child("vout").get_value(); + string amount = entry.second.get_child("amount").get_value(); + amount.erase(std::remove(amount.begin(), amount.end(), '.'), amount.end()); + txo.amount_ = std::stoll(amount); + result.push_back(txo); + } + } + + return result; } std::vector bitcoin_rpc_client::listunspent_by_address_and_amount(const std::string &address, double minimum_amount, const uint32_t minconf, const uint32_t maxconf) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"pp_plugin\", \"method\": " - "\"listunspent\", \"params\": [" + - std::to_string(minconf) + "," + std::to_string(maxconf) + ","); - body += std::string("[\""); - body += address; - body += std::string("\"],true,{\"minimumAmount\":"); - body += std::to_string(minimum_amount); - body += std::string("} ] }"); + + std::string params = std::string("[") + std::to_string(minconf) + "," + std::to_string(maxconf) + ","; + params += std::string("[\""); + params += address; + params += std::string("\"],true,{\"minimumAmount\":"); + params += std::to_string(minimum_amount); + params += std::string("} ]"); std::vector result; + std::string str = send_post_request("listunspent", params, debug_rpc_calls); - try { - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return result; - } - - 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) { - if (json.count("result")) { - for (auto &entry : json.get_child("result")) { - btc_txout txo; - txo.txid_ = entry.second.get_child("txid").get_value(); - txo.out_num_ = entry.second.get_child("vout").get_value(); - string amount = entry.second.get_child("amount").get_value(); - amount.erase(std::remove(amount.begin(), amount.end(), '.'), amount.end()); - txo.amount_ = std::stoll(amount); - result.push_back(txo); - } - } - } else if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return result; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); + if (str.length() == 0) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); return result; } + + std::stringstream ss(str); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (json.count("result")) { + for (auto &entry : json.get_child("result")) { + btc_txout txo; + txo.txid_ = entry.second.get_child("txid").get_value(); + txo.out_num_ = entry.second.get_child("vout").get_value(); + string amount = entry.second.get_child("amount").get_value(); + amount.erase(std::remove(amount.begin(), amount.end(), '.'), amount.end()); + txo.amount_ = std::stoll(amount); + result.push_back(txo); + } + } + + return result; } std::string bitcoin_rpc_client::loadwallet(const std::string &filename) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"loadwallet\", \"method\": " - "\"loadwallet\", \"params\": [\"" + - filename + "\"] }"); - try { - const auto reply = send_post_request(body, debug_rpc_calls); - if (reply.body.empty()) { - wlog("Bitcoin 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) { - std::stringstream ss; - boost::property_tree::json_parser::write_json(ss, json.get_child("result")); - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } + std::string params = std::string("[\"") + filename + std::string("\"]"); + std::string str = send_post_request("loadwallet", params, debug_rpc_calls); + return str; } std::string bitcoin_rpc_client::sendrawtransaction(const std::string &tx_hex) { - const auto body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"sendrawtransaction\", " - "\"method\": \"sendrawtransaction\", \"params\": [") + - std::string("\"") + tx_hex + std::string("\"") + std::string("] }"); - try { - const auto reply = send_post_request(body, debug_rpc_calls); - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return ""; - } + std::string params = std::string("[\"") + tx_hex + std::string("\"]"); + std::string str = send_post_request("sendrawtransaction", params, debug_rpc_calls); - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + if (str.length() > 0) { + std::stringstream ss(str); boost::property_tree::ptree json; boost::property_tree::read_json(ss, json); - if (reply.status == 200) { - return json.get("result"); - } - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + if (!json.count("error.code")) { + if (json.get_child("error.code").get_value() == -27) { + return tx_hex; + } + } } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; + + return json.get("result"); } -} -std::string bitcoin_rpc_client::signrawtransactionwithwallet(const std::string &tx_hash) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"signrawtransactionwithwallet\", " - "\"method\": \"signrawtransactionwithwallet\", \"params\": ["); - std::string params = "\"" + tx_hash + "\""; - body = body + params + std::string("]}"); - - try { - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin 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("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } -} - -std::string bitcoin_rpc_client::unloadwallet(const std::string &filename) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"unloadwallet\", \"method\": " - "\"unloadwallet\", \"params\": [\"" + - filename + "\"] }"); - try { - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin 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) { - std::stringstream ss; - boost::property_tree::json_parser::write_json(ss, json.get_child("result")); - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } + return str; } std::string bitcoin_rpc_client::walletlock() { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"walletlock\", \"method\": " - "\"walletlock\", \"params\": [] }"); - try { - const auto reply = send_post_request(body, debug_rpc_calls); + std::string params = std::string("[]"); + std::string str = send_post_request("walletlock", params, debug_rpc_calls); - if (reply.body.empty()) { - wlog("Bitcoin 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) { - std::stringstream ss; - boost::property_tree::json_parser::write_json(ss, json.get_child("result")); - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); + if (str.length() == 0) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); return ""; } -} -std::string bitcoin_rpc_client::walletprocesspsbt(std::string const &tx_psbt) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"walletprocesspsbt\", \"method\": " - "\"walletprocesspsbt\", \"params\": [\"" + - tx_psbt + "\"] }"); - try { - const auto reply = send_post_request(body, debug_rpc_calls); + std::stringstream ss(str); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); - if (reply.body.empty()) { - wlog("Bitcoin 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("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } + ss.clear(); + boost::property_tree::json_parser::write_json(ss, json.get_child("result")); + return ss.str(); } bool bitcoin_rpc_client::walletpassphrase(const std::string &passphrase, uint32_t timeout) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"walletpassphrase\", \"method\": " - "\"walletpassphrase\", \"params\": [\"" + - passphrase + "\", " + std::to_string(timeout) + "] }"); - try { - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return false; - } - - 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 true; - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } + std::string params = std::string("[\"") + passphrase + std::string("\",") + std::to_string(timeout) + std::string("]"); + std::string str = send_post_request("walletpassphrase", params, debug_rpc_calls); + if (str.length() == 0) return false; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return false; - } -} - -fc::http::reply bitcoin_rpc_client::send_post_request(std::string body, bool show_log) { - fc::http::connection conn; - conn.connect_to(fc::ip::endpoint(fc::ip::address(ip), rpc_port)); - - std::string url = "http://" + ip + ":" + std::to_string(rpc_port); - - if (wallet.length() > 0) { - url = url + "/wallet/" + wallet; - } - - fc::http::reply reply = conn.request("POST", url, body, fc::http::headers{authorization}); - - if (show_log) { - ilog("### Request URL: ${url}", ("url", url)); - ilog("### Request: ${body}", ("body", body)); - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - ilog("### Response status: ${status}", ("status", reply.status)); - ilog("### Response: ${ss}", ("ss", ss.str())); - } - - return reply; + else + return true; } // ============================================================================= @@ -1069,11 +267,11 @@ void zmq_listener::start() { FC_ASSERT(0 == rc); rc = zmq_setsockopt(socket, ZMQ_LINGER, &linger, sizeof(linger)); FC_ASSERT(0 == rc); - int timeout = 100; //millisec + int timeout = 100; // millisec rc = zmq_setsockopt(socket, ZMQ_RCVTIMEO, &timeout, sizeof(timeout)); - //socket.setsockopt( ZMQ_SUBSCRIBE, "hashtx", 6 ); - //socket.setsockopt( ZMQ_SUBSCRIBE, "rawblock", 8 ); - //socket.setsockopt( ZMQ_SUBSCRIBE, "rawtx", 5 ); + // socket.setsockopt( ZMQ_SUBSCRIBE, "hashtx", 6 ); + // socket.setsockopt( ZMQ_SUBSCRIBE, "rawblock", 8 ); + // socket.setsockopt( ZMQ_SUBSCRIBE, "rawtx", 5 ); socket.connect("tcp://" + ip + ":" + std::to_string(zmq_port)); thr = std::thread(&zmq_listener::handle_zmq, this); @@ -1157,15 +355,12 @@ sidechain_net_handler_bitcoin::sidechain_net_handler_bitcoin(peerplays_sidechain } } - fc::http::connection conn; - try { - conn.connect_to(fc::ip::endpoint(fc::ip::address(ip), rpc_port)); - } catch (fc::exception &e) { - elog("No BTC node running at ${ip} or wrong rpc port: ${port}", ("ip", ip)("port", rpc_port)); - FC_ASSERT(false); + std::string url = ip + ":" + std::to_string(rpc_port); + if (wallet.length() > 0) { + url = url + "/wallet/" + wallet; } - bitcoin_client = std::unique_ptr(new bitcoin_rpc_client(ip, rpc_port, rpc_user, rpc_password, wallet, wallet_password, debug_rpc_calls)); + bitcoin_client = std::unique_ptr(new bitcoin_rpc_client(url, rpc_user, rpc_password, debug_rpc_calls)); if (!wallet.empty()) { bitcoin_client->loadwallet(wallet); } @@ -1174,14 +369,18 @@ sidechain_net_handler_bitcoin::sidechain_net_handler_bitcoin(peerplays_sidechain std::stringstream bci_ss(std::string(blockchain_info.begin(), blockchain_info.end())); boost::property_tree::ptree bci_json; boost::property_tree::read_json(bci_ss, bci_json); + using namespace bitcoin; network_type = bitcoin_address::network::mainnet; + if (bci_json.count("chain")) { std::string chain = bci_json.get("chain"); - if (chain == "test") { - network_type = bitcoin_address::network::testnet; - } else if (chain == "regtest") { - network_type = bitcoin_address::network::regtest; + if (chain.length() > 0) { + if (chain == "test") { + network_type = bitcoin_address::network::testnet; + } else if (chain == "regtest") { + network_type = bitcoin_address::network::regtest; + } } } @@ -1210,7 +409,7 @@ sidechain_net_handler_bitcoin::~sidechain_net_handler_bitcoin() { on_changed_objects_task.cancel_and_wait(__FUNCTION__); } } catch (fc::canceled_exception &) { - //Expected exception. Move along. + // Expected exception. Move along. } catch (fc::exception &e) { edump((e.to_detail_string())); } @@ -1218,7 +417,7 @@ sidechain_net_handler_bitcoin::~sidechain_net_handler_bitcoin() { bool sidechain_net_handler_bitcoin::process_proposal(const proposal_object &po) { - //ilog("Proposal to process: ${po}, SON id ${son_id}", ("po", po.id)("son_id", plugin.get_current_son_id())); + // ilog("Proposal to process: ${po}, SON id ${son_id}", ("po", po.id)("son_id", plugin.get_current_son_id())); bool should_approve = false; @@ -1874,7 +1073,7 @@ std::string sidechain_net_handler_bitcoin::create_deposit_transaction(const son_ if (obj == idx.rend() || obj->addresses.find(sidechain_type::bitcoin) == obj->addresses.end()) { return ""; } - //Get redeem script for deposit address + // Get redeem script for deposit address std::string redeem_script = get_redeemscript_for_userdeposit(swdo.sidechain_from); std::string pw_address_json = obj->addresses.find(sidechain_type::bitcoin)->second; @@ -2026,8 +1225,8 @@ std::string sidechain_net_handler_bitcoin::send_transaction(const sidechain_tran uint32_t inputs_number = in_amounts.size(); vector dummy; dummy.resize(inputs_number); - //Organise weighted address signatures - //Add dummies for empty signatures + // Organise weighted address signatures + // Add dummies for empty signatures vector> signatures; for (unsigned idx = 0; idx < sto.signatures.size(); ++idx) { if (sto.signatures[idx].second.empty()) @@ -2035,13 +1234,13 @@ std::string sidechain_net_handler_bitcoin::send_transaction(const sidechain_tran else signatures.push_back(read_byte_arrays_from_string(sto.signatures[idx].second)); } - //Add empty sig for user signature for Deposit transaction + // Add empty sig for user signature for Deposit transaction if (sto.object_id.type() == son_wallet_deposit_object::type_id) { add_signatures_to_transaction_user_weighted_multisig(tx, signatures); } else { add_signatures_to_transaction_weighted_multisig(tx, signatures); } - //Add redeemscripts to vins and make tx ready for sending + // Add redeemscripts to vins and make tx ready for sending sign_witness_transaction_finalize(tx, redeem_scripts, false); std::string final_tx_hex = fc::to_hex(pack(tx)); std::string res = bitcoin_client->sendrawtransaction(final_tx_hex); @@ -2050,6 +1249,7 @@ 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.empty()) return; @@ -2119,12 +1319,14 @@ std::string sidechain_net_handler_bitcoin::get_redeemscript_for_userdeposit(cons std::vector sidechain_net_handler_bitcoin::extract_info_from_block(const std::string &_block) { std::stringstream ss(_block); - boost::property_tree::ptree block; - boost::property_tree::read_json(ss, block); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + auto json_result = json.get_child_optional("result"); std::vector result; - for (const auto &tx_child : block.get_child("tx")) { + for (const auto &tx_child : json_result.get().get_child("tx")) { const auto &tx = tx_child.second; for (const auto &o : tx.get_child("vout")) { @@ -2217,4 +1419,5 @@ void sidechain_net_handler_bitcoin::on_changed_objects_cb(const vector