Replace the JavaScript mobile app with a native Swift iOS wallet
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Copyright (c) woodser
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
* Parts of this file are originally copyright (c) 2014-2019, The Monero Project
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are
|
||||
* permitted provided that the following conditions are met:
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
* conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
* of conditions and the following disclaimer in the documentation and/or other
|
||||
* materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without specific
|
||||
* prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <exception>
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* Public interface for libmonero-cpp library.
|
||||
*/
|
||||
namespace monero {
|
||||
|
||||
/**
|
||||
* Exception when interacting with a Monero wallet or daemon.
|
||||
*/
|
||||
class monero_error : public std::exception {
|
||||
public:
|
||||
std::string message;
|
||||
|
||||
monero_error() {}
|
||||
|
||||
/**
|
||||
* Construct the exception.
|
||||
*
|
||||
* @param msg is a human-readable description of the error
|
||||
*/
|
||||
monero_error(const std::string& msg) : message(msg) {}
|
||||
|
||||
const char* what() const noexcept override {
|
||||
return message.c_str();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Exception when interacting with the Monero daemon or wallet RPC API.
|
||||
*/
|
||||
class monero_rpc_error : public monero_error {
|
||||
public:
|
||||
int code;
|
||||
|
||||
/**
|
||||
* Construct the exception.
|
||||
*
|
||||
* @param error_code is the error code (optional)
|
||||
* @param msg is a human-readable description of the error
|
||||
*/
|
||||
monero_rpc_error(int error_code, const std::string& msg) : code(error_code) { message = msg; }
|
||||
|
||||
/**
|
||||
* Construct the exception.
|
||||
*
|
||||
* @param msg is a human-readable description of the error
|
||||
*/
|
||||
monero_rpc_error(const std::string& msg) : code(-1) { message = msg; }
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,439 @@
|
||||
#include "monero_rpc_connection.h"
|
||||
#include "utils/gen_utils.h"
|
||||
#include "utils/monero_utils.h"
|
||||
#include "daemon/monero_daemon_rpc_model.h"
|
||||
#include "monero_error.h"
|
||||
|
||||
static const uint32_t DEFAULT_TIMEOUT_MS = 3 * 60 * 1000; // 3 minutes
|
||||
|
||||
namespace monero {
|
||||
|
||||
// --------------------------- MONERO REQUEST PARAMS ---------------------------
|
||||
|
||||
rapidjson::Value monero_request_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const {
|
||||
rapidjson::Value root(rapidjson::kObjectType);
|
||||
return root;
|
||||
}
|
||||
|
||||
// --------------------------- MONERO RPC REQUEST ---------------------------
|
||||
|
||||
monero_rpc_request::monero_rpc_request(const std::string& method, const std::shared_ptr<serializable_struct>& params, bool json_rpc): m_method(method), m_params(params) {
|
||||
if (params == nullptr) m_params = std::make_shared<monero_request_params>();
|
||||
if (json_rpc) {
|
||||
m_id = "0";
|
||||
m_version = "2.0";
|
||||
}
|
||||
}
|
||||
|
||||
rapidjson::Value monero_rpc_request::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const {
|
||||
if (!is_json_rpc()) {
|
||||
if (m_params == nullptr) throw monero_error("No params provided");
|
||||
return m_params->to_rapidjson_val(allocator);
|
||||
}
|
||||
|
||||
// create root
|
||||
rapidjson::Value root(rapidjson::kObjectType);
|
||||
|
||||
// set string values
|
||||
rapidjson::Value value_str(rapidjson::kStringType);
|
||||
|
||||
if (m_version != boost::none) monero_utils::add_json_member("jsonrpc", m_version.get(), allocator, root, value_str);
|
||||
if (m_id != boost::none) monero_utils::add_json_member("id", m_id.get(), allocator, root, value_str);
|
||||
if (m_method != boost::none) monero_utils::add_json_member("method", m_method.get(), allocator, root, value_str);
|
||||
if (m_params != nullptr) root.AddMember("params", m_params->to_rapidjson_val(allocator), allocator);
|
||||
|
||||
// return root
|
||||
return root;
|
||||
}
|
||||
|
||||
// --------------------------- MONERO RPC RESPONSE ---------------------------
|
||||
|
||||
void raise_rpc_error(const boost::property_tree::ptree& error_node, const boost::string_ref uri, const std::string& method) {
|
||||
std::string err_message = "Unknown error";
|
||||
int err_code = -1;
|
||||
|
||||
for (auto it = error_node.begin(); it != error_node.end(); ++it) {
|
||||
std::string key_err = it->first;
|
||||
if (key_err == std::string("message")) {
|
||||
if (it->second.data().empty()) {
|
||||
err_message = "Received error response from RPC request with method '" + method + "' to " + uri.to_string();
|
||||
}
|
||||
else err_message = it->second.data();
|
||||
} else if (key_err == std::string("code")) {
|
||||
try {
|
||||
err_code = it->second.get_value<int>();
|
||||
} catch (...) {
|
||||
MERROR("non-numeric error code from a malformed/unusual RPC response");
|
||||
err_code = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw monero_rpc_error(err_code, err_message);
|
||||
}
|
||||
|
||||
// --------------------------- MONERO RPC CONNECTION ---------------------------
|
||||
|
||||
bool monero_rpc_connection::compare(int p1, int p2) {
|
||||
if (p1 == p2) return false;
|
||||
// 0 alway first
|
||||
if (p1 == 0) return true;
|
||||
if (p2 == 0) return false;
|
||||
return p1 > p2;
|
||||
}
|
||||
|
||||
monero_rpc_connection::monero_rpc_connection(const std::string& uri, const std::string& username, const std::string& password, const std::string& proxy_uri, const std::string& zmq_uri, int priority, const boost::optional<uint32_t>& timeout_ms) {
|
||||
if (!uri.empty()) m_uri = uri;
|
||||
else m_uri = boost::none;
|
||||
if (!proxy_uri.empty()) m_proxy_uri = proxy_uri;
|
||||
else m_proxy_uri = boost::none;
|
||||
if (!zmq_uri.empty()) m_zmq_uri = zmq_uri;
|
||||
else m_zmq_uri = boost::none;
|
||||
m_priority = priority;
|
||||
m_timeout_ms = timeout_ms;
|
||||
set_credentials(username, password);
|
||||
}
|
||||
|
||||
monero_rpc_connection::monero_rpc_connection(const monero_rpc_connection& rpc) {
|
||||
boost::lock_guard<boost::recursive_mutex> src_lock(rpc.m_mutex);
|
||||
m_uri = rpc.m_uri;
|
||||
m_proxy_uri = rpc.m_proxy_uri;
|
||||
m_zmq_uri = rpc.m_zmq_uri;
|
||||
m_priority = rpc.m_priority;
|
||||
m_timeout_ms = rpc.m_timeout_ms;
|
||||
set_credentials(rpc.m_username.value_or(""), rpc.m_password.value_or(""));
|
||||
m_is_online = rpc.m_is_online;
|
||||
m_is_authenticated = rpc.m_is_authenticated;
|
||||
m_response_time = rpc.m_response_time;
|
||||
m_attributes = rpc.m_attributes;
|
||||
}
|
||||
|
||||
rapidjson::Value monero_rpc_connection::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const {
|
||||
boost::lock_guard<boost::recursive_mutex> lock(m_mutex);
|
||||
|
||||
// create root
|
||||
rapidjson::Value root(rapidjson::kObjectType);
|
||||
|
||||
// set string values
|
||||
rapidjson::Value value_str(rapidjson::kStringType);
|
||||
if (m_uri != boost::none) monero_utils::add_json_member("uri", m_uri.get(), allocator, root, value_str);
|
||||
if (m_username != boost::none) monero_utils::add_json_member("username", m_username.get(), allocator, root, value_str);
|
||||
if (m_password != boost::none) monero_utils::add_json_member("password", m_password.get(), allocator, root, value_str);
|
||||
if (m_proxy_uri != boost::none) monero_utils::add_json_member("proxyUri", m_proxy_uri.get(), allocator, root, value_str);
|
||||
if (m_zmq_uri != boost::none) monero_utils::add_json_member("zmqUri", m_zmq_uri.get(), allocator, root, value_str);
|
||||
|
||||
// set num values
|
||||
rapidjson::Value value_num(rapidjson::kNumberType);
|
||||
monero_utils::add_json_member("priority", m_priority, allocator, root, value_num);
|
||||
if (m_timeout_ms != boost::none) monero_utils::add_json_member("timeoutMs", m_timeout_ms.get(), allocator, root, value_num);
|
||||
if (m_response_time != boost::none) monero_utils::add_json_member("responseTime", m_response_time.get(), allocator, root, value_num);
|
||||
|
||||
// set bool values
|
||||
if (m_is_online != boost::none) monero_utils::add_json_member("isOnline", m_is_online.get(), allocator, root);
|
||||
if (m_is_authenticated != boost::none) monero_utils::add_json_member("isAuthenticated", m_is_authenticated.get(), allocator, root);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
bool monero_rpc_connection::is_onion() const {
|
||||
boost::lock_guard<boost::recursive_mutex> lock(m_mutex);
|
||||
epee::net_utils::http::url_content parsed{};
|
||||
if (!epee::net_utils::parse_url(m_uri.value_or(""), parsed)) return false;
|
||||
return parsed.host.size() >= 6 && parsed.host.compare(parsed.host.size() - 6, 6, ".onion") == 0;
|
||||
}
|
||||
|
||||
bool monero_rpc_connection::is_i2p() const {
|
||||
boost::lock_guard<boost::recursive_mutex> lock(m_mutex);
|
||||
epee::net_utils::http::url_content parsed{};
|
||||
if (!epee::net_utils::parse_url(m_uri.value_or(""), parsed)) return false;
|
||||
return parsed.host.size() >= 8 && parsed.host.compare(parsed.host.size() - 8, 8, ".b32.i2p") == 0;
|
||||
}
|
||||
|
||||
void monero_rpc_connection::set_credentials(const std::string& username, const std::string& password) {
|
||||
boost::lock_guard<boost::recursive_mutex> lock(m_mutex);
|
||||
|
||||
// disconnect http client if connected
|
||||
if (m_http_client != nullptr && m_http_client->is_connected()) m_http_client->disconnect();
|
||||
|
||||
bool username_empty = username.empty();
|
||||
bool password_empty = password.empty();
|
||||
|
||||
// check username and password consistency
|
||||
if (!username_empty || !password_empty) {
|
||||
if (password_empty) {
|
||||
throw monero_error("password cannot be empty because username is not empty");
|
||||
}
|
||||
if (username_empty) {
|
||||
throw monero_error("username cannot be empty because password is not empty");
|
||||
}
|
||||
}
|
||||
|
||||
// check username and password changes
|
||||
bool username_equals = (m_username == boost::none && username_empty) || (m_username != boost::none && *m_username == username);
|
||||
bool password_equals = (m_password == boost::none && password_empty) || (m_password != boost::none && *m_password == password);
|
||||
|
||||
// connection reset values
|
||||
if (!username_equals || !password_equals) {
|
||||
m_is_online = boost::none;
|
||||
m_is_authenticated = boost::none;
|
||||
}
|
||||
|
||||
// setup username and password
|
||||
if (!username_empty && !password_empty) {
|
||||
m_username = username;
|
||||
m_password = password;
|
||||
} else {
|
||||
m_username = boost::none;
|
||||
m_password = boost::none;
|
||||
}
|
||||
}
|
||||
|
||||
void monero_rpc_connection::set_attribute(const std::string& key, const std::string& val) {
|
||||
boost::lock_guard<boost::recursive_mutex> lock(m_mutex);
|
||||
m_attributes[key] = val;
|
||||
}
|
||||
|
||||
std::string monero_rpc_connection::get_attribute(const std::string& key) const {
|
||||
boost::lock_guard<boost::recursive_mutex> lock(m_mutex);
|
||||
std::unordered_map<std::string, std::string>::const_iterator i = m_attributes.find(key);
|
||||
if (i == m_attributes.end()) {
|
||||
return std::string(""); // attribute not found
|
||||
}
|
||||
return i->second;
|
||||
}
|
||||
|
||||
boost::optional<bool> monero_rpc_connection::is_online() const {
|
||||
boost::lock_guard<boost::recursive_mutex> lock(m_mutex);
|
||||
return m_is_online;
|
||||
}
|
||||
|
||||
boost::optional<bool> monero_rpc_connection::is_authenticated() const {
|
||||
boost::lock_guard<boost::recursive_mutex> lock(m_mutex);
|
||||
return m_is_authenticated;
|
||||
}
|
||||
|
||||
boost::optional<bool> monero_rpc_connection::is_connected() const {
|
||||
boost::lock_guard<boost::recursive_mutex> lock(m_mutex);
|
||||
if (m_is_online == boost::none) return boost::none;
|
||||
return m_is_online.get() && (m_is_authenticated == boost::none || m_is_authenticated.get());
|
||||
}
|
||||
|
||||
bool monero_rpc_connection::check_connection(const boost::optional<uint32_t>& timeout_ms) {
|
||||
boost::lock_guard<boost::recursive_mutex> lock(m_mutex);
|
||||
boost::optional<bool> is_online_before = m_is_online;
|
||||
boost::optional<bool> is_authenticated_before = m_is_authenticated;
|
||||
boost::optional<std::chrono::high_resolution_clock::time_point> start_time = boost::none;
|
||||
|
||||
try {
|
||||
|
||||
// assume daemon connection
|
||||
monero_get_blocks_by_height_request request(100);
|
||||
start_time = std::chrono::high_resolution_clock::now();
|
||||
send_binary_request(request, timeout_ms);
|
||||
|
||||
// set response time
|
||||
m_response_time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start_time.get()).count();
|
||||
m_is_online = true;
|
||||
m_is_authenticated = true;
|
||||
}
|
||||
catch (const monero_rpc_error& ex) {
|
||||
m_is_online = false;
|
||||
m_is_authenticated = boost::none;
|
||||
m_response_time = boost::none;
|
||||
|
||||
if (ex.code == 401 || ex.code == 404) {
|
||||
|
||||
// fallback to latency check
|
||||
m_is_online = true;
|
||||
m_is_authenticated = ex.code == 404;
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
m_is_online = false;
|
||||
m_is_authenticated = boost::none;
|
||||
m_response_time = boost::none;
|
||||
}
|
||||
|
||||
if (gen_utils::bool_equals(true, m_is_online) && start_time != boost::none) {
|
||||
m_response_time = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - start_time.get()).count();
|
||||
}
|
||||
|
||||
return is_online_before != m_is_online || is_authenticated_before != m_is_authenticated;
|
||||
}
|
||||
|
||||
boost::property_tree::ptree monero_rpc_connection::send_json_request(const std::string& path, const std::shared_ptr<serializable_struct>& params, const boost::optional<uint32_t>& timeout_ms) const {
|
||||
|
||||
// send JSON-RPC request
|
||||
monero_rpc_request request(path, params);
|
||||
auto response = send_json_request(request, timeout_ms);
|
||||
|
||||
// assert JSON-RPC response is defined
|
||||
if (response.m_result == boost::none) throw monero_error("Invalid Monero JSONRPC response");
|
||||
return response.m_result.get();
|
||||
}
|
||||
|
||||
monero_rpc_response monero_rpc_connection::send_json_request(const monero_rpc_request &request, const boost::optional<uint32_t>& timeout_ms) const {
|
||||
|
||||
// invoke JSON-RPC method
|
||||
monero_rpc_response response;
|
||||
send_rpc_request("/json_rpc", request, response, timeout_ms);
|
||||
|
||||
// return JSON-RPC response
|
||||
return response;
|
||||
}
|
||||
|
||||
boost::property_tree::ptree monero_rpc_connection::send_path_request(const std::string& path, const std::shared_ptr<serializable_struct>& params, const boost::optional<uint32_t>& timeout_ms) const {
|
||||
|
||||
// send RPC request
|
||||
monero_rpc_request request(path, params, false);
|
||||
auto response = send_path_request(request, timeout_ms);
|
||||
|
||||
// assert RPC response is defined
|
||||
if (response.m_response == boost::none) throw monero_error("Invalid Monero RPC response");
|
||||
return response.m_response.get();
|
||||
}
|
||||
|
||||
monero_rpc_response monero_rpc_connection::send_path_request(const monero_rpc_request &request, const boost::optional<uint32_t>& timeout_ms) const {
|
||||
|
||||
// validate parameters
|
||||
if (request.m_method == boost::none || request.m_method->empty()) throw monero_error("No RPC method set in path request");
|
||||
|
||||
// invoke RPC method
|
||||
monero_rpc_response response;
|
||||
send_rpc_request(std::string("/") + request.m_method.get(), request, response, timeout_ms);
|
||||
|
||||
// return RPC response
|
||||
return response;
|
||||
}
|
||||
|
||||
monero_rpc_response monero_rpc_connection::send_binary_request(const monero_rpc_request &request, const boost::optional<uint32_t>& timeout_ms) const {
|
||||
|
||||
// validate parameters
|
||||
if (request.m_method == boost::none || request.m_method->empty()) throw monero_error("No RPC method set in binary request");
|
||||
|
||||
// invoke binary RPC method
|
||||
monero_rpc_response response;
|
||||
send_rpc_request(std::string("/") + request.m_method.get(), request, response, timeout_ms, true);
|
||||
|
||||
// return binary response
|
||||
return response;
|
||||
}
|
||||
|
||||
void monero_rpc_connection::ensure_configured() const {
|
||||
|
||||
// create http client on first use, so constructing a connection does not require network support
|
||||
if (m_http_client == nullptr) {
|
||||
auto factory = std::make_unique<net::http::client_factory>();
|
||||
m_http_client = factory->create();
|
||||
m_http_client->set_auto_connect(true);
|
||||
}
|
||||
|
||||
// only reconfigure when uri/creds/proxy changed since last apply
|
||||
std::string uri = m_uri.value_or("");
|
||||
std::string username = m_username.value_or("");
|
||||
std::string password = m_password.value_or("");
|
||||
std::string proxy_uri = m_proxy_uri.value_or("");
|
||||
auto key = std::make_tuple(uri, username, password, proxy_uri);
|
||||
|
||||
if (m_applied == key) return; // unchanged, so reuse live connection
|
||||
|
||||
// setup connection credentials
|
||||
boost::optional<epee::net_utils::http::login> login;
|
||||
if(!username.empty() && !password.empty()) {
|
||||
login = epee::net_utils::http::login();
|
||||
login->username = username;
|
||||
login->password = password;
|
||||
}
|
||||
|
||||
// detect ssl
|
||||
epee::net_utils::ssl_support_t ssl = uri.rfind("https", 0) == 0 ? epee::net_utils::ssl_support_t::e_ssl_support_enabled : epee::net_utils::ssl_support_t::e_ssl_support_disabled;
|
||||
|
||||
if (!m_http_client->set_proxy(proxy_uri)) throw monero_error("Could not set proxy uri: " + proxy_uri);
|
||||
if (!m_http_client->set_server(uri, login, std::move(ssl))) throw monero_error("Could not set uri: " + uri);
|
||||
if (!m_http_client->connect(std::chrono::milliseconds(DEFAULT_TIMEOUT_MS))) MWARNING("Could not connect to server: " + uri);
|
||||
|
||||
m_applied = key;
|
||||
}
|
||||
|
||||
std::string monero_rpc_connection::invoke_post(const boost::string_ref uri, const std::string& body, const boost::optional<uint32_t>& timeout_ms) const {
|
||||
boost::lock_guard<boost::recursive_mutex> lock(m_mutex);
|
||||
|
||||
// request/response timeout: user-configurable, defaults to 3 minutes when not set.
|
||||
uint32_t timeout = DEFAULT_TIMEOUT_MS;
|
||||
if (timeout_ms != boost::none) timeout = timeout_ms.get();
|
||||
else if (m_timeout_ms != boost::none) timeout = m_timeout_ms.get();
|
||||
if (timeout == 0) timeout = std::numeric_limits<uint32_t>::max(); // timeout of 0 is treated as indefinite
|
||||
|
||||
// ensure_configured() bounds only the first connect; epee reconnects with this request timeout
|
||||
ensure_configured();
|
||||
const epee::net_utils::http::http_response_info* pri = NULL;
|
||||
|
||||
// invoke http json
|
||||
const uint64_t received_before = m_http_client->get_bytes_received();
|
||||
if (!m_http_client->invoke_post(uri, body, std::chrono::milliseconds(timeout), std::addressof(pri))) {
|
||||
|
||||
// epee never surfaces the 401 status, but a rejected login means the server answered;
|
||||
// a dead or idle-closed socket yields no response bytes at all
|
||||
bool received = m_http_client->get_bytes_received() > received_before;
|
||||
if (received && m_http_client->is_connected()) {
|
||||
throw monero_rpc_error(401, "Unauthorized");
|
||||
}
|
||||
|
||||
// drop the socket: epee leaves EOF'd sockets marked connected, so without this the next
|
||||
// call reuses the poisoned socket and hits the same misdiagnosis again
|
||||
m_http_client->disconnect();
|
||||
if (m_uri == boost::none || m_uri->empty()) throw monero_error("Cannot send RPC request: uri not set.");
|
||||
throw monero_error("Network error: " + std::string(received ? "connection dropped mid-response" : "no response") + " from " + m_uri.get() + uri.to_string() + " (timeout: " + std::to_string(timeout) + "ms)");
|
||||
}
|
||||
if (!pri) throw monero_error("Could not get response info");
|
||||
if (pri->m_response_code < 200 || pri->m_response_code > 299) {
|
||||
std::string err_msg = std::to_string(pri->m_response_code) + " " + pri->m_response_comment + (pri->m_body.empty() ? "" : ": " + pri->m_body);
|
||||
throw monero_rpc_error(pri->m_response_code, err_msg);
|
||||
}
|
||||
|
||||
// return response info body
|
||||
return pri->m_body;
|
||||
}
|
||||
|
||||
void deserialize_rpc_response(const std::string& json, monero_rpc_response& response, const boost::string_ref uri, const std::string& method) {
|
||||
|
||||
// parse json to property node
|
||||
boost::property_tree::ptree node;
|
||||
gen_utils::deserialize(json, node);
|
||||
|
||||
// parse JSON-RPC response
|
||||
for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) {
|
||||
std::string key = it->first;
|
||||
if (key == std::string("error")) raise_rpc_error(it->second, uri, method);
|
||||
else if (key == std::string("jsonrpc")) response.m_jsonrpc = it->second.data();
|
||||
else if (key == std::string("result")) response.m_result = it->second;
|
||||
}
|
||||
|
||||
// if not JSON-RPC, set RPC response
|
||||
if (response.m_jsonrpc == boost::none) response.m_response = node;
|
||||
}
|
||||
|
||||
void monero_rpc_connection::send_rpc_request(const boost::string_ref uri, const monero_rpc_request& request, monero_rpc_response& response, const boost::optional<uint32_t>& timeout_ms, bool binary) const {
|
||||
std::string body;
|
||||
if (request.m_method == boost::none) throw monero_error("Must provide a valid request method");
|
||||
if (binary) monero_utils::json_to_binary(request.serialize(), body);
|
||||
else body = request.serialize();
|
||||
|
||||
std::string result = invoke_post(uri, body, timeout_ms);
|
||||
if (binary) response.m_binary = result;
|
||||
else deserialize_rpc_response(result, response, uri, request.m_method.get());
|
||||
}
|
||||
|
||||
std::shared_ptr<monero_rpc_connection> monero_rpc_connection::from_property_tree(const boost::property_tree::ptree& node) {
|
||||
std::shared_ptr<monero_rpc_connection> connection = std::make_shared<monero_rpc_connection>();
|
||||
for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) {
|
||||
std::string key = it->first;
|
||||
if (key == std::string("uri")) connection->m_uri = it->second.data();
|
||||
else if (key == std::string("username")) connection->m_username = it->second.data();
|
||||
else if (key == std::string("password")) connection->m_password = it->second.data();
|
||||
else if (key == std::string("proxyUri") || key == std::string("proxy_uri")) connection->m_proxy_uri = it->second.data();
|
||||
else if (key == std::string("zmqUri")) connection->m_zmq_uri = it->second.data();
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
/**
|
||||
* Copyright (c) woodser
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
* Parts of this file are originally copyright (c) 2014-2019, The Monero Project
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are
|
||||
* permitted provided that the following conditions are met:
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
* conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
* of conditions and the following disclaimer in the documentation and/or other
|
||||
* materials provided with the distribution.
|
||||
*
|
||||
* 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without specific
|
||||
* prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <boost/thread/mutex.hpp>
|
||||
#include <boost/thread/thread.hpp>
|
||||
#include <boost/thread/condition_variable.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include "rapidjson/document.h"
|
||||
#include "rapidjson/writer.h"
|
||||
#include "rapidjson/stringbuffer.h"
|
||||
#include "net/http.h"
|
||||
#include "daemon/monero_daemon_model.h"
|
||||
|
||||
/**
|
||||
* Public interface for libmonero-cpp library.
|
||||
*/
|
||||
namespace monero {
|
||||
|
||||
/**
|
||||
* Models parameters for a request to a RPC server.
|
||||
*/
|
||||
struct monero_request_params : public serializable_struct {
|
||||
monero_request_params() { }
|
||||
|
||||
rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Models a request to a RPC server.
|
||||
*/
|
||||
struct monero_rpc_request : public serializable_struct {
|
||||
boost::optional<std::string> m_id;
|
||||
boost::optional<std::string> m_version;
|
||||
boost::optional<std::string> m_method;
|
||||
std::shared_ptr<serializable_struct> m_params;
|
||||
|
||||
monero_rpc_request() { }
|
||||
monero_rpc_request(const std::string& method, const std::shared_ptr<serializable_struct>& params, bool json_rpc = true);
|
||||
|
||||
bool is_json_rpc() const { return m_id != boost::none && m_version != boost::none; }
|
||||
|
||||
rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Models a response from a RPC server.
|
||||
*/
|
||||
struct monero_rpc_response {
|
||||
boost::optional<std::string> m_jsonrpc;
|
||||
boost::optional<boost::property_tree::ptree> m_result;
|
||||
boost::optional<boost::property_tree::ptree> m_response;
|
||||
boost::optional<std::string> m_binary;
|
||||
};
|
||||
|
||||
/**
|
||||
* Maintains a connection and sends requests to a Monero RPC API.
|
||||
*/
|
||||
class monero_rpc_connection : public serializable_struct {
|
||||
public:
|
||||
boost::optional<std::string> m_uri;
|
||||
boost::optional<std::string> m_username;
|
||||
boost::optional<std::string> m_password;
|
||||
boost::optional<std::string> m_proxy_uri;
|
||||
boost::optional<std::string> m_zmq_uri; // TODO: implement zmq listener
|
||||
boost::optional<uint32_t> m_timeout_ms; // RPC request timeout in milliseconds
|
||||
boost::optional<uint64_t> m_response_time; // automatically set by calling check_connection()
|
||||
int m_priority; // priority relative to other connections where 1 is highest, then 2, etc, and 0 is lowest (default)
|
||||
|
||||
static std::shared_ptr<monero_rpc_connection> from_property_tree(const boost::property_tree::ptree& node);
|
||||
|
||||
/**
|
||||
* Checks RPC connection priority order.
|
||||
*
|
||||
* @param c1 first priority to compare
|
||||
* @param c2 second priority to compare
|
||||
*/
|
||||
static bool compare(int p1, int p2);
|
||||
|
||||
/**
|
||||
* Initialize a new RPC connection.
|
||||
*
|
||||
* @param uri RPC connection uri
|
||||
* @param username RPC connection authentication username
|
||||
* @param password RPC connection authentication password
|
||||
* @param proxy_uri RPC connection proxy uri
|
||||
* @param zmq_uri RPC connection zmq uri
|
||||
* @param priority RPC connection priority
|
||||
* @param timeout_ms RPC connection timeout in milliseconds
|
||||
*/
|
||||
monero_rpc_connection(const std::string& uri = "", const std::string& username = "", const std::string& password = "", const std::string& proxy_uri = "", const std::string& zmq_uri = "", int priority = 0, const boost::optional<uint32_t>& timeout_ms = boost::none);
|
||||
|
||||
/**
|
||||
* Copy a RPC connection.
|
||||
*
|
||||
* @param rpc RPC connection to copy
|
||||
*/
|
||||
monero_rpc_connection(const monero_rpc_connection& rpc);
|
||||
|
||||
/**
|
||||
* Indicates if the connection uri is a TOR server.
|
||||
*
|
||||
* @return true or false to indicate if connection uri is a TOR server
|
||||
*/
|
||||
bool is_onion() const;
|
||||
|
||||
/**
|
||||
* Indicates if the connection uri is a I2P server.
|
||||
*
|
||||
* @return true or false to indicate if connection uri is a I2P server
|
||||
*/
|
||||
bool is_i2p() const;
|
||||
|
||||
/**
|
||||
* Set connection credentials.
|
||||
*
|
||||
* @param username username to use in RPC authentication
|
||||
* @param password password to use in RPC authentication
|
||||
*/
|
||||
void set_credentials(const std::string& username, const std::string& password);
|
||||
|
||||
/**
|
||||
* Set connection attribute.
|
||||
*
|
||||
* @param key is the attribute key
|
||||
* @param val is the attribute value
|
||||
*/
|
||||
void set_attribute(const std::string& key, const std::string& val);
|
||||
|
||||
/**
|
||||
* Get connection attribute.
|
||||
*
|
||||
* @param key is the attribute to get the value of
|
||||
* @return key's value if set
|
||||
*/
|
||||
std::string get_attribute(const std::string& key) const;
|
||||
|
||||
/**
|
||||
* Indicates if the connection is online, which is set automatically by calling check_connection().
|
||||
*
|
||||
* @return true or false to indicate if online, or null if check_connection() has not been called
|
||||
*/
|
||||
boost::optional<bool> is_online() const;
|
||||
|
||||
/**
|
||||
* Indicates if the connection is authenticated, which is set automatically by calling check_connection().
|
||||
*
|
||||
* @return true if authenticated or no authentication, false if not authenticated, or null if not set
|
||||
*/
|
||||
boost::optional<bool> is_authenticated() const;
|
||||
|
||||
/**
|
||||
* Indicates if the connection is connected, which is set automatically by calling check_connection().
|
||||
*
|
||||
* @return true or false to indicate if connected, or null if check_connection() has not been called
|
||||
*/
|
||||
boost::optional<bool> is_connected() const;
|
||||
|
||||
/**
|
||||
* Check the connection and update online, authentication, and response time status.
|
||||
*
|
||||
* @param timeout_ms the maximum response time before considered offline
|
||||
* @return
|
||||
*/
|
||||
bool check_connection(const boost::optional<uint32_t>& timeout_ms = boost::none);
|
||||
|
||||
/**
|
||||
* Send a request to the RPC API.
|
||||
*
|
||||
* @param path specifies the method to request
|
||||
* @param params are the request's input parameters
|
||||
* @return the RPC API response as a map
|
||||
*/
|
||||
boost::property_tree::ptree send_json_request(const std::string& path, const std::shared_ptr<serializable_struct>& params = nullptr, const boost::optional<uint32_t>& timeout_ms = boost::none) const;
|
||||
|
||||
/**
|
||||
* Send a request to the RPC API.
|
||||
*
|
||||
* @param request specifies the method to request with parameters
|
||||
* @param timeout request timeout in milliseconds
|
||||
* @return the RPC API response as a map
|
||||
*/
|
||||
monero_rpc_response send_json_request(const monero_rpc_request &request, const boost::optional<uint32_t>& timeout_ms = boost::none) const;
|
||||
|
||||
/**
|
||||
* Send a RPC request to the given path and with the given paramters.
|
||||
*
|
||||
* E.g. "/get_transactions" with params
|
||||
*
|
||||
* @param path is the url path of the request to invoke
|
||||
* @param params are request parameters sent in the body
|
||||
* @return the RPC API response as a map
|
||||
*/
|
||||
boost::property_tree::ptree send_path_request(const std::string& path, const std::shared_ptr<serializable_struct>& params = nullptr, const boost::optional<uint32_t>& timeout_ms = boost::none) const;
|
||||
|
||||
/**
|
||||
* Send a RPC request to the given path and with the given paramters.
|
||||
*
|
||||
* @param request specifies the method to request with parameters
|
||||
* @param timeout request timeout in milliseconds
|
||||
* @return the request's deserialized response
|
||||
*/
|
||||
monero_rpc_response send_path_request(const monero_rpc_request &request, const boost::optional<uint32_t>& timeout_ms = boost::none) const;
|
||||
|
||||
/**
|
||||
* Send a binary RPC request.
|
||||
*
|
||||
* @param request specifies the method to request with paramesters
|
||||
* @param timeout request timeout in milliseconds
|
||||
* @return the request's deserialized response
|
||||
*/
|
||||
monero_rpc_response send_binary_request(const monero_rpc_request &request, const boost::optional<uint32_t>& timeout_ms = boost::none) const;
|
||||
|
||||
rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override;
|
||||
|
||||
protected:
|
||||
// instance variables
|
||||
mutable boost::recursive_mutex m_mutex;
|
||||
boost::optional<bool> m_is_online;
|
||||
boost::optional<bool> m_is_authenticated;
|
||||
mutable std::unique_ptr<epee::net_utils::http::abstract_http_client> m_http_client;
|
||||
std::unordered_map<std::string, std::string> m_attributes;
|
||||
mutable std::tuple<std::string, std::string, std::string, std::string> m_applied;
|
||||
|
||||
void ensure_configured() const;
|
||||
std::string invoke_post(const boost::string_ref uri, const std::string& body, const boost::optional<uint32_t>& timeout_ms = boost::none) const;
|
||||
void send_rpc_request(const boost::string_ref uri, const monero_rpc_request& request, monero_rpc_response& response, const boost::optional<uint32_t>& timeout_ms = boost::none, bool binary = false) const;
|
||||
};
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user