Replace the JavaScript mobile app with a native Swift iOS wallet

This commit is contained in:
2026-09-19 13:33:11 +08:00
parent d80b58b704
commit 3e1a39d4d7
79 changed files with 25482 additions and 4023 deletions
@@ -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;
};
}