diff --git a/CMakeLists.txt b/CMakeLists.txt index cd92815..f1baebc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,7 @@ set(SOURCES src/cpp/wallet/py_monero_wallet.cpp src/cpp/wallet/py_monero_wallet_full.cpp src/cpp/wallet/py_monero_wallet_rpc.cpp + src/cpp/utils/py_monero_utils.cpp src/cpp/py_monero.cpp ) @@ -43,6 +44,7 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/src/cpp/common ${CMAKE_CURRENT_SOURCE_DIR}/src/cpp/daemon ${CMAKE_CURRENT_SOURCE_DIR}/src/cpp/wallet + ${CMAKE_CURRENT_SOURCE_DIR}/src/cpp/utils ) link_directories( diff --git a/setup.py b/setup.py index 51a0742..a7d847e 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ coverage = os.environ.get("COVERAGE") == "1" this_dir = Path(__file__).parent.resolve() -extra_compile_args = ['/std:c++17'] if sys.platform == "win32" else ['-std=c++17'] +extra_compile_args: list[str] = ['/std:c++17'] if sys.platform == "win32" else ['-std=c++17'] extra_link_args: list[str] = [] if coverage: @@ -27,6 +27,7 @@ 'src/cpp/wallet/py_monero_wallet.cpp', 'src/cpp/wallet/py_monero_wallet_full.cpp', 'src/cpp/wallet/py_monero_wallet_rpc.cpp', + 'src/cpp/utils/py_monero_utils.cpp', 'src/cpp/py_monero.cpp' ], include_dirs=[ @@ -40,7 +41,8 @@ str(this_dir / 'src' / 'cpp'), str(this_dir / 'src' / 'cpp' / 'common'), str(this_dir / 'src' / 'cpp' / 'daemon'), - str(this_dir / 'src' / 'cpp' / 'wallet') + str(this_dir / 'src' / 'cpp' / 'wallet'), + str(this_dir / 'src' / 'cpp' / 'utils') ], library_dirs=[ str(this_dir / 'external' / 'monero-cpp' / 'build') diff --git a/src/cpp/common/py_boost_optional_caster.h b/src/cpp/common/py_boost_optional_caster.h deleted file mode 100644 index 3d23715..0000000 --- a/src/cpp/common/py_boost_optional_caster.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include -#include - -namespace pybind11 { namespace detail { - -template -struct type_caster> { -private: - using ValueCaster = make_caster; - -public: - PYBIND11_TYPE_CASTER(boost::optional, _("Optional[") + ValueCaster::name + _("]")); - - bool load(handle src, bool convert) { - if (src.is_none()) { - value = boost::none; - return true; - } - ValueCaster caster; - if (!caster.load(src, convert)) { - return false; - } - value = cast_op(std::move(caster)); - return true; - } - - static handle cast(const boost::optional& src, return_value_policy policy, handle parent) { - if (!src) { - return none().inc_ref(); - } - return ValueCaster::cast(*src, policy, parent); - } -}; - -}} // namespace pybind11::detail diff --git a/src/cpp/common/py_monero_common.cpp b/src/cpp/common/py_monero_common.cpp index af04fd0..03dd416 100644 --- a/src/cpp/common/py_monero_common.cpp +++ b/src/cpp/common/py_monero_common.cpp @@ -1,4 +1,5 @@ #include "py_monero_common.h" +#include "utils/monero_utils.h" int PyMoneroConnectionPriorityComparator::compare(int p1, int p2) { if (p1 == p2) return 0; @@ -7,23 +8,6 @@ int PyMoneroConnectionPriorityComparator::compare(int p1, int p2) { return p2 - p1; } -int PyMoneroTxHeightComparator::compare(const std::shared_ptr &tx1, const std::shared_ptr &tx2) { - if (tx1->get_height() == boost::none && tx2->get_height() == boost::none) return 0; // both unconfirmed - else if (tx1->get_height() == boost::none) return 1; // tx1 is unconfirmed - else if (tx2->get_height() == boost::none) return -1; // tx2 is unconfirmed - int diff = tx1->get_height().get() - tx2->get_height().get(); - if (diff != 0) return diff; - auto txs1 = tx1->m_block.get()->m_txs; - auto txs2 = tx2->m_block.get()->m_txs; - auto it1 = find(txs1.begin(), txs1.end(), tx1); - auto it2 = find(txs2.begin(), txs2.end(), tx2); - if (it1 == txs1.end() && it2 == txs2.end()) return 0; - else if (it1 == txs1.end()) return 1; - else if (it2 == txs2.end()) return -1; - - return std::distance(txs1.begin(), it1) - std::distance(txs2.begin(), it2); // txs are in the same block so retain their original order -} - py::object PyGenUtils::convert_value(const std::string& val) { if (val == "true") return py::bool_(true); if (val == "false") return py::bool_(false); @@ -117,246 +101,734 @@ boost::property_tree::ptree PyGenUtils::pyobject_to_ptree(const py::object& obj) return tree; } -int PyMoneroUtils::get_ring_size() { - return monero_utils::RING_SIZE; +std::string PyMoneroBinaryRequest::to_binary_val() const { + auto json_val = serialize(); + std::string binary_val; + monero_utils::json_to_binary(json_val, binary_val); + return binary_val; } -void PyMoneroUtils::set_log_level(int level) { - monero_utils::set_log_level(level); +std::string PyMoneroRequestParams::serialize() const { + if (m_py_params == boost::none) return PySerializableStruct::serialize(); + auto node = PyGenUtils::pyobject_to_ptree(m_py_params.get()); + return monero_utils::serialize(node); } -void PyMoneroUtils::configure_logging(const std::string& path, bool console) { - monero_utils::configure_logging(path, console); +boost::optional PyMoneroJsonResponse::get_result() const { + boost::optional res; + if (m_result != boost::none) res = PyGenUtils::ptree_to_pyobject(m_result.get()); + return res; } -monero_integrated_address PyMoneroUtils::get_integrated_address(monero_network_type network_type, const std::string& standard_address, const std::string& payment_id) { - return monero_utils::get_integrated_address(network_type, standard_address, payment_id); +std::shared_ptr PyMoneroJsonResponse::deserialize(const std::string& response_json) { + // deserialize json to property node + std::istringstream iss = response_json.empty() ? std::istringstream() : std::istringstream(response_json); + boost::property_tree::ptree node; + boost::property_tree::read_json(iss, node); + + auto response = std::make_shared(); + + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("error")) { + std::string err_message = "Unknown error"; + int err_code = -1; + for (auto it_err = it->second.begin(); it_err != it->second.end(); ++it_err) { + std::string key_err = it_err->first; + if (key_err == std::string("message")) { + err_message = it_err->second.data(); + } + else if (key_err == std::string("code")) { + err_code = it_err->second.get_value(); + } + } + + throw PyMoneroRpcError(err_code, err_message); + } + else if (key == std::string("jsonrpc")) { + response->m_jsonrpc = it->second.data(); + } + else if (key == std::string("id")) { + response->m_id = it->second.data(); + } + else if (key == std::string("result")) { + response->m_result = it->second; + } + else std::cout << std::string("WARNING MoneroJsonResponse::deserialize() unrecognized key: ") << key << std::endl; + } + + return response; } -bool PyMoneroUtils::is_valid_address(const std::string& address, monero_network_type network_type) { - return monero_utils::is_valid_address(address, network_type); +boost::optional PyMoneroPathResponse::get_response() const { + boost::optional res; + if (m_response != boost::none) res = PyGenUtils::ptree_to_pyobject(m_response.get()); + return res; } -bool PyMoneroUtils::is_valid_public_view_key(const std::string& public_view_key) { - return is_hex_64(public_view_key); +std::shared_ptr PyMoneroPathResponse::deserialize(const std::string& response_json) { + // deserialize json to property node + std::istringstream iss = response_json.empty() ? std::istringstream() : std::istringstream(response_json); + boost::property_tree::ptree node; + boost::property_tree::read_json(iss, node); + auto response = std::make_shared(); + response->m_response = node; + return response; } -bool PyMoneroUtils::is_valid_public_spend_key(const std::string& public_spend_key) { - return is_hex_64(public_spend_key); +std::shared_ptr PyMoneroBinaryResponse::deserialize(const std::string& response_binary) { + auto response = std::make_shared(); + response->m_binary = response_binary; + return response; } -bool PyMoneroUtils::is_valid_private_view_key(const std::string& private_view_key) { - return monero_utils::is_valid_private_view_key(private_view_key); +boost::optional PyMoneroBinaryResponse::get_response() const { + boost::optional res; + if (m_response != boost::none) res = PyGenUtils::ptree_to_pyobject(m_response.get()); + return res; } -bool PyMoneroUtils::is_valid_private_spend_key(const std::string& private_spend_key) { - return monero_utils::is_valid_private_spend_key(private_spend_key); +rapidjson::Value PyMoneroPathRequest::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + if (m_params != boost::none) return m_params.get()->to_rapidjson_val(allocator); + throw std::runtime_error("No params provided"); } -bool PyMoneroUtils::is_valid_payment_id(const std::string& payment_id) { - return payment_id.size() == 16 || payment_id.size() == 64; +rapidjson::Value PyMoneroJsonRequest::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + rapidjson::Value root(rapidjson::kObjectType); + rapidjson::Value value_str(rapidjson::kStringType); + + if (m_version != boost::none) monero_utils::add_json_member("version", 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 != boost::none) root.AddMember("params", m_params.get()->to_rapidjson_val(allocator), allocator); + + return root; } -bool PyMoneroUtils::is_valid_mnemonic(const std::string& mnemonic) { - try { - validate_mnemonic(mnemonic); - return true; +int PyMoneroRpcConnection::compare(std::shared_ptr c1, std::shared_ptr c2, std::shared_ptr current_connection) { + // current connection is first + if (c1 == current_connection) return -1; + if (c2 == current_connection) return 1; + + // order by availability then priority then by name + if (c1->is_online() == c2->is_online()) { + if (c1->m_priority == c2->m_priority) { + if (c1->m_uri == c2->m_uri) return 1; + else return -1; + } + return PyMoneroConnectionPriorityComparator::compare(c1->m_priority, c2->m_priority) * -1; // order by priority in descending order + } else { + if (c1->is_online()) return -1; + else if (c2->is_online()) return 1; + // TODO manage never connected + //else if (!c1->is_online()) return -1; + //else return 1; // c1 is offline + return -1; } - catch (...) { - return false; +} + +bool PyMoneroRpcConnection::is_onion() const { + if (m_uri == boost::none) return false; + if (m_uri && m_uri->size() >= 6 && m_uri->compare(m_uri->size() - 6, 6, ".onion") == 0) { + return true; } + return false; } -void PyMoneroUtils::validate_address(const std::string& address, monero_network_type network_type) { - monero_utils::validate_address(address, network_type); +bool PyMoneroRpcConnection::is_i2p() const { + if (m_uri == boost::none) return false; + if (m_uri && m_uri->size() >= 8 && m_uri->compare(m_uri->size() - 8, 8, ".b32.i2p") == 0) { + return true; + } + return false; } -void PyMoneroUtils::validate_public_view_key(const std::string& public_view_key) { - if(!is_hex_64(public_view_key)) throw std::runtime_error("Invalid public view key"); +void PyMoneroRpcConnection::set_credentials(const std::string& username, const std::string& password) { + if (m_http_client != nullptr) { + if (m_http_client->is_connected()) { + m_http_client->disconnect(); + } + } + else { + auto factory = new net::http::client_factory(); + m_http_client = factory->create(); + } + + if (username.empty()) { + m_username = boost::none; + } + + if (password.empty()) { + m_password = boost::none; + } + + if (!password.empty() || !username.empty()) { + if (password.empty()) { + throw PyMoneroError("username cannot be empty because password is not empty"); + } + + if (username.empty()) { + throw PyMoneroError("password cannot be empty because username is not empty"); + } + } + + if (m_username != username || m_password != password) { + m_is_online = boost::none; + m_is_authenticated = boost::none; + } + + m_username = username; + m_password = password; } -void PyMoneroUtils::validate_public_spend_key(const std::string& public_spend_key) { - if(!is_hex_64(public_spend_key)) throw std::runtime_error("Invalid public view key"); +void PyMoneroRpcConnection::set_attribute(const std::string& key, const std::string& val) { + m_attributes[key] = val; } -void PyMoneroUtils::validate_private_view_key(const std::string& private_view_key) { - monero_utils::validate_private_view_key(private_view_key); +std::string PyMoneroRpcConnection::get_attribute(const std::string& key) { + std::unordered_map::const_iterator i = m_attributes.find(key); + if (i == m_attributes.end()) + return std::string(""); + return i->second; } -void PyMoneroUtils::validate_private_spend_key(const std::string& private_spend_key) { - monero_utils::validate_private_spend_key(private_spend_key); +bool PyMoneroRpcConnection::is_online() const { + return m_is_online.value_or(false); } -void PyMoneroUtils::validate_payment_id(const std::string& payment_id) { - if (!is_valid_payment_id(payment_id)) throw std::runtime_error("Invalid payment id"); +bool PyMoneroRpcConnection::is_authenticated() const { + return m_is_authenticated.value_or(false); } -void PyMoneroUtils::validate_mnemonic(const std::string& mnemonic) { - if (mnemonic.empty()) throw std::runtime_error("Mnemonic phrase is empty"); +bool PyMoneroRpcConnection::is_connected() const { + if (!is_online()) return false; + if (m_is_authenticated != boost::none) { + return is_authenticated(); + } + return true; +} - size_t count = 0; - bool in_word = false; +bool PyMoneroRpcConnection::check_connection(int timeout_ms) { + boost::optional is_online_before = m_is_online; + boost::optional is_authenticated_before = m_is_authenticated; + boost::lock_guard lock(m_mutex); + try { + if (!m_http_client) throw std::runtime_error("http client not set"); + if (m_http_client->is_connected()) { + m_http_client->disconnect(); + } - for (char c : mnemonic) { - if (std::isspace(c)) { - if (in_word) { - ++count; - in_word = false; + if (m_proxy_uri != boost::none) { + if(!m_http_client->set_proxy(m_proxy_uri.get())) { + throw std::runtime_error("Could not set proxy"); } - } else { - in_word = true; } + + if(m_username != boost::none && !m_username->empty() && m_password != boost::none && !m_password->empty()) { + auto credentials = std::make_shared(); + + credentials->username = *m_username; + credentials->password = *m_password; + + m_credentials = *credentials; + } + + if (!m_http_client->set_server(m_uri.get(), m_credentials)) { + throw std::runtime_error("Could not set rpc connection: " + m_uri.get()); + } + + m_http_client->connect(std::chrono::milliseconds(timeout_ms)); + auto start = std::chrono::high_resolution_clock::now(); + PyMoneroJsonRequest request("get_version"); + auto response = send_json_request(request); + auto end = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(end - start); + if (response->m_result == boost::none) { + throw PyMoneroRpcError(-1, "Invalid JSON RPC response"); + } + + m_is_online = true; + m_is_authenticated = true; + m_response_time = duration.count(); + + return is_online_before != m_is_online || is_authenticated_before != m_is_authenticated; + } + catch (const PyMoneroRpcError& ex) { + m_is_online = false; + m_is_authenticated = boost::none; + m_response_time = boost::none; + + if (ex.code == 401) { + m_is_online = true; + m_is_authenticated = false; + } + else if (ex.code == 404) { + m_is_online = true; + m_is_authenticated = true; + } + + return false; + } + catch (const std::exception& ex) { + m_is_online = false; + m_is_authenticated = boost::none; + m_response_time = boost::none; + return false; + } +} + +void PyMoneroConnectionManager::add_listener(const std::shared_ptr &listener) { + boost::lock_guard lock(m_listeners_mutex); + m_listeners.push_back(listener); +} + +void PyMoneroConnectionManager::remove_listener(const std::shared_ptr &listener) { + boost::lock_guard lock(m_listeners_mutex); + m_listeners.erase(std::remove_if(m_listeners.begin(), m_listeners.end(), [&listener](std::shared_ptr iter){ return iter == listener; }), m_listeners.end()); +} + +void PyMoneroConnectionManager::remove_listeners() { + boost::lock_guard lock(m_listeners_mutex); + m_listeners.clear(); +} + +std::vector> PyMoneroConnectionManager::get_listeners() const { + return m_listeners; +} + +std::shared_ptr PyMoneroConnectionManager::get_connection_by_uri(const std::string &uri) { + boost::lock_guard lock(m_connections_mutex); + for(const auto &m_connection : m_connections) { + if (m_connection->m_uri == uri) return m_connection; } - if (in_word) ++count; + return nullptr; +} + +void PyMoneroConnectionManager::add_connection(std::shared_ptr connection) { + if (connection->m_uri == boost::none) throw std::runtime_error("Invalid connection uri"); + boost::lock_guard lock(m_connections_mutex); + for(const auto &m_connection : m_connections) { + if (m_connection->m_uri == connection->m_uri) throw std::runtime_error("Connection URI already exists with connection manager: " + connection->m_uri.get()); + } - if (count != 25) throw std::runtime_error("Mnemonic phared words must be 25"); + m_connections.push_back(connection); } -std::string PyMoneroUtils::json_to_binary(const std::string &json) { - std::string bin; - monero_utils::json_to_binary(json, bin); - return bin; +void PyMoneroConnectionManager::add_connection(const std::string &uri) { + std::shared_ptr connection = std::make_shared(); + connection->m_uri = uri; + add_connection(connection); } -std::string PyMoneroUtils::dict_to_binary(const py::dict &dictionary) { - py::object JSON = py::module_::import("json"); - py::object JSON_DUMPS = JSON.attr("dumps"); +void PyMoneroConnectionManager::remove_connection(const std::string &uri) { + boost::lock_guard lock(m_connections_mutex); - py::object result_py = JSON_DUMPS(dictionary); - std::string json = result_py.cast(); + std::shared_ptr connection = get_connection_by_uri(uri); - return json_to_binary(json); + if (connection == nullptr) throw std::runtime_error("Connection not found"); + + m_connections.erase(std::remove_if(m_connections.begin(), m_connections.end(), [&connection](std::shared_ptr iter){ return iter == connection; }), m_connections.end()); + + if (connection == m_current_connection) { + m_current_connection = nullptr; + on_connection_changed(m_current_connection); + } } -py::dict PyMoneroUtils::binary_to_dict(const std::string& bin) { - py::object JSON = py::module_::import("json"); - py::object JSON_LOADS = JSON.attr("loads"); +void PyMoneroConnectionManager::set_connection(std::shared_ptr connection) { + if (connection == m_current_connection) return; - std::string json = binary_to_json(bin); + if (connection == nullptr) { + m_current_connection = nullptr; + on_connection_changed(nullptr); + return; + } + + if (connection->m_uri == boost::none || connection->m_uri->empty()) throw std::runtime_error("Connection is missing URI"); - py::object result_py = JSON_LOADS(json); - py::dict result = result_py.cast(); + boost::lock_guard lock(m_connections_mutex); - return result; + auto prev_connection = get_connection_by_uri(connection->m_uri.get()); + if (prev_connection != nullptr) m_connections.erase(std::remove_if(m_connections.begin(), m_connections.end(), [&prev_connection](std::shared_ptr iter){ return iter == prev_connection; }), m_connections.end()); + add_connection(connection); + m_current_connection = connection; + on_connection_changed(connection); } -std::string PyMoneroUtils::binary_to_json(const std::string &bin) { - std::string json; - monero_utils::binary_to_json(bin, json); - return json; +void PyMoneroConnectionManager::set_connection(const std::string& uri) { + if (uri.empty()) { + set_connection(std::shared_ptr(nullptr)); + return; + } + + auto found = get_connection_by_uri(uri); + + if (found != nullptr) { + set_connection(found); + } + else { + auto connection = std::make_shared(); + connection->m_uri = uri; + set_connection(connection); + } } -void PyMoneroUtils::binary_blocks_to_json(const std::string &bin, std::string &json) { - monero_utils::binary_blocks_to_json(bin, json); +bool PyMoneroConnectionManager::has_connection(const std::string& uri) { + auto connection = get_connection_by_uri(uri); + + if (connection != nullptr) return true; + return false; } -void PyMoneroUtils::binary_blocks_to_property_tree(const std::string &bin, boost::property_tree::ptree &node) { - std::string response_json; - monero_utils::binary_blocks_to_json(bin, response_json); - std::istringstream iss = response_json.empty() ? std::istringstream() : std::istringstream(response_json); - boost::property_tree::read_json(iss, node); +bool PyMoneroConnectionManager::is_connected() const { + if (m_current_connection == nullptr) return false; + return m_current_connection->is_connected(); +} + +void PyMoneroConnectionManager::check_connection() { + bool connection_changed = false; + std::shared_ptr connection = get_connection(); + if (connection != nullptr) { + if (connection->check_connection(m_timeout)) connection_changed = true; + std::vector> cons; + cons.push_back(connection); + process_responses(cons); + } + if (m_auto_switch && !is_connected()) { + std::shared_ptr best_connection = get_best_available_connection(connection); + if (best_connection != nullptr) { + set_connection(best_connection); + return; + } + } + if (connection_changed) on_connection_changed(connection); +} + +void PyMoneroConnectionManager::set_auto_switch(bool auto_switch) { + m_auto_switch = auto_switch; } -bool PyMoneroUtils::is_valid_language(const std::string& language) { - return monero_utils::is_valid_language(language); +void PyMoneroConnectionManager::stop_polling() { + if (m_is_polling) { + m_is_polling = false; + if (m_thread.joinable()) m_thread.join(); + } +} + +void PyMoneroConnectionManager::start_polling(boost::optional period_ms, boost::optional auto_switch, boost::optional timeout_ms, boost::optional poll_type, boost::optional>> &excluded_connections) { + // apply defaults + if (period_ms == boost::none) period_ms = DEFAULT_POLL_PERIOD; + if (auto_switch != boost::none) set_auto_switch(auto_switch.get()); + if (timeout_ms != boost::none) set_timeout(timeout_ms.get()); + if (poll_type == boost::none) poll_type = PyMoneroConnectionPollType::PRIORITIZED; + + // stop polling + stop_polling(); + + // start polling + switch (poll_type.get()) { + case PyMoneroConnectionPollType::CURRENT: + start_polling_connection(period_ms.get()); + break; + case PyMoneroConnectionPollType::ALL: + start_polling_connections(period_ms.get()); + break; + case PyMoneroConnectionPollType::UNDEFINED: + case PyMoneroConnectionPollType::PRIORITIZED: + start_polling_prioritized_connections(period_ms.get(), excluded_connections); + break; + } +} + +std::shared_ptr PyMoneroConnectionManager::get_best_available_connection(const std::set>& excluded_connections) { + auto cons = get_connections_in_ascending_priority(); + for (const auto& prioritizedConnections : cons) { + try { + std::vector futures; + for (const auto& connection : prioritizedConnections) { + if (!connection) throw std::runtime_error("connection is nullptr"); + if (excluded_connections.count(connection)) continue; + std::thread thread = std::thread([this, connection]() { + connection->check_connection(m_timeout); + }); + thread.detach(); + futures.push_back(&thread); + } + + for (auto& fut : futures) { + if (fut->joinable()) fut->join(); + } + + std::shared_ptr best_connection = nullptr; + + for (const auto& conn : prioritizedConnections) { + try { + if (!conn) throw std::runtime_error("connection is nullptr"); + if (conn->is_connected()) { + if (best_connection == nullptr || best_connection->m_response_time == boost::none || best_connection->m_response_time < conn->m_response_time) best_connection = conn; + } + } catch (...) { + std::cout << "MoneroRpcConnection::get_best_available_connection(): error" << std::endl; + } + } + + if (best_connection != nullptr) return best_connection; + } catch (const std::exception& e) { + throw std::runtime_error(std::string("Connection check error: ") + e.what()); + } + } + + return std::shared_ptr(nullptr); +} + +std::shared_ptr PyMoneroConnectionManager::get_best_available_connection(std::shared_ptr& excluded_connection) { + const std::set>& excluded_connections = { excluded_connection }; + + return get_best_available_connection(excluded_connections); +} + +void PyMoneroConnectionManager::check_connections() { + check_connections(get_connections()); } -std::vector> PyMoneroUtils::get_blocks_from_txs(std::vector> txs) { - return monero_utils::get_blocks_from_txs(txs); +void PyMoneroConnectionManager::disconnect() { + set_connection(std::shared_ptr(nullptr)); } -std::vector> PyMoneroUtils::get_blocks_from_transfers(std::vector> transfers) { - return monero_utils::get_blocks_from_transfers(transfers); +void PyMoneroConnectionManager::clear() { + boost::lock_guard lock(m_connections_mutex); + + m_connections.clear(); + + if (m_current_connection != nullptr) { + m_current_connection = nullptr; + on_connection_changed(m_current_connection); + } } -std::vector> PyMoneroUtils::get_blocks_from_outputs(std::vector> outputs) { - return monero_utils::get_blocks_from_outputs(outputs); +void PyMoneroConnectionManager::reset() { + remove_listeners(); + stop_polling(); + clear(); + m_timeout = DEFAULT_TIMEOUT; + m_auto_switch = DEFAULT_AUTO_SWITCH; +} + +void PyMoneroConnectionManager::on_connection_changed(std::shared_ptr connection) { + boost::lock_guard lock(m_listeners_mutex); + + for (const auto &listener : m_listeners) { + listener->on_connection_changed(connection); + } } -std::string PyMoneroUtils::get_payment_uri(const monero_tx_config& config) { - // validate config - std::vector> destinations = config.get_normalized_destinations(); - if (destinations.size() != 1) throw std::runtime_error("Cannot make URI from supplied parameters: must provide exactly one destination to send funds"); - if (destinations.at(0)->m_address == boost::none) throw std::runtime_error("Cannot make URI from supplied parameters: must provide destination address"); - if (destinations.at(0)->m_amount == boost::none) throw std::runtime_error("Cannot make URI from supplied parameters: must provide destination amount"); +std::vector>> PyMoneroConnectionManager::get_connections_in_ascending_priority() { + boost::lock_guard lock(m_connections_mutex); + + std::map>> connection_priorities; + + for (const auto& connection : m_connections) { + int priority = connection->m_priority; + connection_priorities[priority].push_back(connection); + } + + std::vector>> prioritized_connections; + + for (auto& [priority, group] : connection_priorities) { + prioritized_connections.push_back(group); + } - // prepare wallet2 params - std::string address = destinations.at(0)->m_address.get(); - std::string payment_id = config.m_payment_id == boost::none ? "" : config.m_payment_id.get(); - uint64_t amount = destinations.at(0)->m_amount.get(); - std::string note = config.m_note == boost::none ? "" : config.m_note.get(); - std::string m_recipient_name = config.m_recipient_name == boost::none ? "" : config.m_recipient_name.get(); + if (!prioritized_connections.empty() && connection_priorities.count(0)) { + auto it = std::find_if(prioritized_connections.begin(), prioritized_connections.end(), + [](const auto& group) { + return !group.empty() && group[0]->m_priority == 0; + }); - // make uri - std::string uri = make_uri(address, payment_id, amount, note, m_recipient_name); - if (uri.empty()) throw std::runtime_error("Cannot make URI from supplied parameters"); - return uri; + if (it != prioritized_connections.end()) { + auto zero_priority_group = *it; + prioritized_connections.erase(it); + prioritized_connections.push_back(zero_priority_group); + } + } + + return prioritized_connections; +} + +void PyMoneroConnectionManager::start_polling_connection(uint64_t period_ms) { + m_is_polling = true; + + m_thread = std::thread([this, period_ms]() { + while (m_is_polling) { + try { + check_connection(); + } catch (const std::exception& e) { + std::cout << "ERROR " << e.what() << std::endl; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(period_ms)); + } + }); + m_thread.detach(); } -uint64_t PyMoneroUtils::xmr_to_atomic_units(double amount_xmr) { - if (amount_xmr < 0) throw std::invalid_argument("amount_xmr cannot be negative"); - return static_cast(std::round(amount_xmr * XMR_AU_MULTIPLIER)); +void PyMoneroConnectionManager::start_polling_connections(uint64_t period_ms) { + m_is_polling = true; + + m_thread = std::thread([this, period_ms]() { + while (m_is_polling) { + try { + check_connections(); + } catch (const std::exception& e) { + std::cout << "ERROR " << e.what() << std::endl; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(period_ms)); + } + }); + m_thread.detach(); +} + +void PyMoneroConnectionManager::start_polling_prioritized_connections(uint64_t period_ms, boost::optional>> excluded_connections) { + m_is_polling = true; + m_thread = std::thread([this, period_ms, &excluded_connections]() { + while (m_is_polling) { + try { + check_prioritized_connections(excluded_connections); + } catch (const std::exception& e) { + std::cout << "ERROR " << e.what() << std::endl; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(period_ms)); + } + }); + m_thread.detach(); } -double PyMoneroUtils::atomic_units_to_xmr(uint64_t amount_atomic_units) { - return static_cast(amount_atomic_units) / static_cast(XMR_AU_MULTIPLIER); +bool PyMoneroConnectionManager::check_connections(const std::vector>& connections, const std::set>& excluded_connections) { + boost::lock_guard lock(m_connections_mutex); + try { + auto timeout_ms = m_timeout; + + bool has_connection = false; + + for (const auto& connection : connections) { + if (excluded_connections.count(connection)) continue; + + bool changed = connection->check_connection(timeout_ms); + if (changed && connection == get_connection()) { + on_connection_changed(connection); + } + if (connection->is_connected() && !has_connection) { + has_connection = true; + if (!is_connected() && m_auto_switch) { + set_connection(connection); + } + } + } + + process_responses(connections); + return has_connection; + } + catch (const std::exception& e) { + throw std::runtime_error(std::string("check_connections failed: ") + e.what()); + } } -bool PyMoneroUtils::is_hex_64(const std::string& value) { - if (value.size() != 64) return false; - const std::regex hexRegex("^-?[0-9a-fA-F]+$"); - return std::regex_match(value, hexRegex); +void PyMoneroConnectionManager::check_prioritized_connections(boost::optional>> excluded_connections) { + for (const auto &prioritized_connections : get_connections_in_ascending_priority()) { + if (excluded_connections != boost::none) { + std::set> ex(excluded_connections.get().begin(), excluded_connections.get().end()); + check_connections(prioritized_connections, ex); + } + else { check_connections(prioritized_connections, {}); } + } } -std::string PyMoneroUtils::make_uri(const std::string &address, const std::string &payment_id, uint64_t amount, const std::string &tx_description, const std::string &recipient_name) { - cryptonote::address_parse_info info; +std::shared_ptr PyMoneroConnectionManager::process_responses(const std::vector>& responses) { + for (const auto& conn : responses) { + if (m_response_times.find(conn) == m_response_times.end()) { + m_response_times[conn] = {}; + } + } + + for (auto& [conn, times] : m_response_times) { + if (std::find(responses.begin(), responses.end(), conn) != responses.end()) { + times.insert(times.begin(), conn->m_response_time); + } else { + times.insert(times.begin(), boost::none); + } - if(!get_account_address_from_str(info, cryptonote::MAINNET, address)) - { - if(!get_account_address_from_str(info, cryptonote::TESTNET, address)) - { - if(!get_account_address_from_str(info, cryptonote::STAGENET, address)) - { - throw std::runtime_error(std::string("wrong address: ") + address); - } + if (times.size() > MIN_BETTER_RESPONSES) { + times.pop_back(); } } - - // we want only one payment id - if (info.has_payment_id && !payment_id.empty()) - { - throw std::runtime_error("A single payment id is allowed"); + + return update_best_connection_in_priority(); +} + +std::shared_ptr PyMoneroConnectionManager::get_best_connection_from_prioritized_responses(const std::vector>& responses) { + std::shared_ptr best_response = std::shared_ptr(nullptr); + + for (const auto& conn : responses) { + if (conn->is_connected()) { + if (!best_response || conn->m_response_time < best_response->m_response_time) { + best_response = conn; + } + } } - - if (!payment_id.empty()) - { - throw std::runtime_error("Standalone payment id deprecated, use integrated address instead"); + + if (!best_response) return std::shared_ptr(nullptr); + + auto best_connection = get_connection(); + if (!best_connection || !best_connection->is_connected()) { + return best_response; } - - std::string uri = "monero:" + address; - unsigned int n_fields = 0; - - if (!payment_id.empty()) - { - uri += (n_fields++ ? "&" : "?") + std::string("tx_payment_id=") + payment_id; + + if (PyMoneroConnectionPriorityComparator::compare(best_response->m_priority, best_connection->m_priority) != 0) { + return best_response; } - - if (amount > 0) - { - // URI encoded amount is in decimal units, not atomic units - uri += (n_fields++ ? "&" : "?") + std::string("tx_amount=") + cryptonote::print_money(amount); + + if (m_response_times.find(best_connection) == m_response_times.end()) { + return best_connection; } - - if (!recipient_name.empty()) - { - uri += (n_fields++ ? "&" : "?") + std::string("recipient_name=") + epee::net_utils::conver_to_url_format(recipient_name); + + for (const auto& conn : responses) { + if (conn == best_connection) continue; + + auto it_best = m_response_times.find(best_connection); + auto it_curr = m_response_times.find(conn); + if (it_curr == m_response_times.end()) continue; + if (it_curr->second.size() < MIN_BETTER_RESPONSES || it_best->second.size() < MIN_BETTER_RESPONSES) continue; + + bool consistently_better = true; + for (int i = 0; i < MIN_BETTER_RESPONSES; ++i) { + auto curr_time = it_curr->second[i]; + auto best_time = it_best->second[i]; + if (curr_time == boost::none || best_time == boost::none || curr_time.get() > best_time.get()) { + consistently_better = false; + break; + } + } + + if (consistently_better) { + best_connection = conn; + } } - - if (!tx_description.empty()) - { - uri += (n_fields++ ? "&" : "?") + std::string("tx_description=") + epee::net_utils::conver_to_url_format(tx_description); + + return best_connection; +} + +std::shared_ptr PyMoneroConnectionManager::update_best_connection_in_priority() { + if (!m_auto_switch) return std::shared_ptr(nullptr); + + for (const auto& prioritized_connections : get_connections_in_ascending_priority()) { + auto best_conn = get_best_connection_from_prioritized_responses(prioritized_connections); + if (best_conn != nullptr) { + set_connection(best_conn); + return best_conn; + } } - - return uri; + + return std::shared_ptr(nullptr); } diff --git a/src/cpp/common/py_monero_common.h b/src/cpp/common/py_monero_common.h index 428d005..d0f90f0 100644 --- a/src/cpp/common/py_monero_common.h +++ b/src/cpp/common/py_monero_common.h @@ -1,25 +1,56 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include +#include #include +#include -#include "py_boost_optional_caster.h" -#include "utils/monero_utils.h" +#include "net/http.h" #include "utils/gen_utils.h" #include "daemon/monero_daemon_model.h" -#include "wallet/monero_wallet_model.h" -#include "wallet/monero_wallet_full.h" -#include "wallet/monero_wallet_keys.h" namespace py = pybind11; +namespace pybind11 { namespace detail { + + template + struct type_caster> { + private: + using ValueCaster = make_caster; + + public: + PYBIND11_TYPE_CASTER(boost::optional, _("Optional[") + ValueCaster::name + _("]")); + + bool load(handle src, bool convert) { + if (src.is_none()) { + value = boost::none; + return true; + } + ValueCaster caster; + if (!caster.load(src, convert)) { + return false; + } + value = cast_op(std::move(caster)); + return true; + } + + static handle cast(const boost::optional& src, return_value_policy policy, handle parent) { + if (!src) { + return none().inc_ref(); + } + return ValueCaster::cast(*src, policy, parent); + } + }; + +}} + +class PySerializableStruct : public monero::serializable_struct { +public: + using serializable_struct::serializable_struct; + + virtual std::string serialize() const { return serializable_struct::serialize(); } + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override { throw std::runtime_error("PySerializableStruct::to_rapid_json_value(): not implemented"); }; +}; + class PyMoneroError : public std::exception { public: std::string message; @@ -74,12 +105,6 @@ class PyMoneroConnectionPriorityComparator { static int compare(int p1, int p2); }; -class PyMoneroTxHeightComparator { -public: - - static int compare(const std::shared_ptr &tx1, const std::shared_ptr &tx2); -}; - class PyGenUtils { public: PyGenUtils() {} @@ -89,56 +114,425 @@ class PyGenUtils { static boost::property_tree::ptree pyobject_to_ptree(const py::object& obj); }; -class PySerializableStruct : public serializable_struct { +class PyMoneroRequest : public PySerializableStruct { public: - using serializable_struct::serializable_struct; + boost::optional m_method; - virtual std::string serialize() const { return serializable_struct::serialize(); } - rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override { throw std::runtime_error("PySerializableStruct::to_rapid_json_value(): not implemented"); }; + PyMoneroRequest() { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override { throw std::runtime_error("PyMoneroRequest::to_rapid_json_value(): not implemented"); }; +}; + +class PyMoneroRequestParams : public PySerializableStruct { +public: + boost::optional m_py_params; + + PyMoneroRequestParams() { } + PyMoneroRequestParams(boost::optional py_params) { m_py_params = py_params; } + + std::string serialize() const override; + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override { throw std::runtime_error("PyMoneroRequestParams::to_rapid_json_value(): not implemented"); }; +}; + +class PyMoneroRequestEmptyParams : public PyMoneroRequestParams { + public: + PyMoneroRequestEmptyParams() {} + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override { rapidjson::Value root(rapidjson::kObjectType); return root; }; +}; + +class PyMoneroPathRequest : public PyMoneroRequest { +public: + boost::optional> m_params; + + PyMoneroPathRequest() { } + + PyMoneroPathRequest(std::string method, boost::optional params = boost::none) { + m_method = method; + if (params != boost::none) m_params = std::make_shared(params); + m_params = std::make_shared(); + } + + PyMoneroPathRequest(std::string method, std::shared_ptr params) { + m_method = method; + m_params = params; + } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; +}; + +class PyMoneroBinaryRequest : public PyMoneroPathRequest { +public: + PyMoneroBinaryRequest() {} + + PyMoneroBinaryRequest(std::string method, boost::optional params = boost::none) { + m_method = method; + if (params != boost::none) m_params = std::make_shared(params); + m_params = std::make_shared(); + } + + PyMoneroBinaryRequest(std::string method, std::shared_ptr params) { + m_method = method; + m_params = params; + } + + std::string to_binary_val() const; +}; + +class PyMoneroJsonRequestParams : public PyMoneroRequestParams { +public: + PyMoneroJsonRequestParams() { } + PyMoneroJsonRequestParams(boost::optional py_params) { m_py_params = py_params; } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override { throw std::runtime_error("PyMoneroJsonRequestParams::to_rapid_json_value(): not implemented"); }; +}; + +class PyMoneroJsonRequestEmptyParams : public PyMoneroJsonRequestParams { +public: + PyMoneroJsonRequestEmptyParams() {} + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override { rapidjson::Value root(rapidjson::kObjectType); return root; }; +}; + +class PyMoneroJsonRequest : public PyMoneroRequest { +public: + boost::optional m_version; + boost::optional m_id; + boost::optional> m_params; + + PyMoneroJsonRequest() { + m_version = "2.0"; + m_id = "0"; + m_params = std::make_shared(); + } + + PyMoneroJsonRequest(const PyMoneroJsonRequest& request) { + m_version = request.m_version; + m_id = request.m_id; + m_method = request.m_method; + m_params = request.m_params; + } + + PyMoneroJsonRequest(std::string method, boost::optional params = boost::none) { + m_version = "2.0"; + m_id = "0"; + m_method = method; + if (params != boost::none) { + m_params = std::make_shared(params); + } + else m_params = std::make_shared(); + } + + PyMoneroJsonRequest(std::string method, std::shared_ptr params) { + m_version = "2.0"; + m_id = "0"; + m_method = method; + m_params = params; + } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; +}; + +class PyMoneroJsonResponse { +public: + boost::optional m_jsonrpc; + boost::optional m_id; + boost::optional m_result; + + static std::shared_ptr deserialize(const std::string& response_json); + + PyMoneroJsonResponse() { + m_jsonrpc = "2.0"; + m_id = "0"; + } + + PyMoneroJsonResponse(const PyMoneroJsonResponse& response) { + m_jsonrpc = response.m_jsonrpc; + m_id = response.m_id; + m_result = response.m_result; + } + + PyMoneroJsonResponse(boost::optional &result) { + m_jsonrpc = "2.0"; + m_id = "0"; + m_result = result; + } + + boost::optional get_result() const; +}; + +class PyMoneroPathResponse { +public: + boost::optional m_response; + + PyMoneroPathResponse() { } + + PyMoneroPathResponse(const PyMoneroPathResponse& response) { + m_response = response.m_response; + } + + PyMoneroPathResponse(boost::optional &response) { + m_response = response; + } + + boost::optional get_response() const; + static std::shared_ptr deserialize(const std::string& response_json); +}; + +class PyMoneroBinaryResponse { +public: + boost::optional m_binary; + boost::optional m_response; + + PyMoneroBinaryResponse() {} + + PyMoneroBinaryResponse(const std::string &binary) { + m_binary = binary; + } + + PyMoneroBinaryResponse(const PyMoneroBinaryResponse& response) { + m_binary = response.m_binary; + m_response = response.m_response; + } + + static std::shared_ptr deserialize(const std::string& response_binary); + boost::optional get_response() const; +}; + +class PyMoneroRpcConnection : public monero::monero_rpc_connection { +public: + boost::optional m_zmq_uri; + int m_priority; + uint64_t m_timeout; + boost::optional m_response_time; + + static int compare(std::shared_ptr c1, std::shared_ptr c2, std::shared_ptr current_connection); + + PyMoneroRpcConnection(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, uint64_t timeout = 0) { + m_uri = uri; + m_username = username; + m_password = password; + m_zmq_uri = zmq_uri; + m_priority = priority; + m_timeout = timeout; + m_proxy_uri = proxy_uri; + set_credentials(username, password); + } + + PyMoneroRpcConnection(const PyMoneroRpcConnection& rpc) { + m_uri = rpc.m_uri; + m_username = rpc.m_username; + m_password = rpc.m_password; + m_zmq_uri = rpc.m_zmq_uri; + m_proxy_uri = rpc.m_proxy_uri; + m_is_authenticated = rpc.m_is_authenticated; + set_credentials(m_username.value_or(""), m_password.value_or("")); + } + + PyMoneroRpcConnection(const monero::monero_rpc_connection& rpc) { + m_uri = rpc.m_uri; + m_username = rpc.m_username; + m_password = rpc.m_password; + m_proxy_uri = rpc.m_proxy_uri; + set_credentials(m_username.value_or(""), m_password.value_or("")); + } + + bool is_onion() const; + bool is_i2p() const; + void set_credentials(const std::string& username, const std::string& password); + void set_attribute(const std::string& key, const std::string& val); + std::string get_attribute(const std::string& key); + bool is_online() const; + bool is_authenticated() const; + bool is_connected() const; + bool check_connection(int timeout_ms = 2000); + + template + inline int invoke_post(const boost::string_ref uri, const t_request& request, t_response& res, std::chrono::milliseconds timeout = std::chrono::seconds(15)) const { + if (!m_http_client) throw std::runtime_error("http client not set"); + + rapidjson::Document document(rapidjson::Type::kObjectType); + rapidjson::Value req = request.to_rapidjson_val(document.GetAllocator()); + rapidjson::StringBuffer sb; + rapidjson::Writer writer(sb); + req.Accept(writer); + std::string body = sb.GetString(); + + const epee::net_utils::http::http_response_info* response = invoke_post(uri, body, timeout); + + int status_code = response->m_response_code; + + if (status_code == 200) { + res = *t_response::deserialize(response->m_body); + } + + return status_code; + } + + inline const epee::net_utils::http::http_response_info* invoke_post(const boost::string_ref uri, const std::string& body, std::chrono::milliseconds timeout = std::chrono::seconds(15)) const { + if (!m_http_client) throw std::runtime_error("http client not set"); + + std::shared_ptr _res = std::make_shared(); + const epee::net_utils::http::http_response_info* response = _res.get(); + boost::lock_guard lock(m_mutex); + + if (!m_http_client->invoke_post(uri, body, timeout, &response)) throw std::runtime_error("Network error"); + + return response; + } + + inline const std::shared_ptr send_json_request(const PyMoneroJsonRequest &request, std::chrono::milliseconds timeout = std::chrono::seconds(15)) { + PyMoneroJsonResponse response; + + int result = invoke_post("/json_rpc", request, response, timeout); + + if (result != 200) throw std::runtime_error("HTTP error: code " + std::to_string(result)); + + return std::make_shared(response); + } + + inline const std::shared_ptr send_path_request(const PyMoneroPathRequest &request, std::chrono::milliseconds timeout = std::chrono::seconds(15)) { + PyMoneroPathResponse response; + + if (request.m_method == boost::none || request.m_method->empty()) throw std::runtime_error("No RPC method set in path request"); + int result = invoke_post(std::string("/") + request.m_method.get(), request, response, timeout); + + if (result != 200) throw std::runtime_error("HTTP error: code " + std::to_string(result)); + + return std::make_shared(response); + } + + inline const std::shared_ptr send_binary_request(const PyMoneroBinaryRequest &request, std::chrono::milliseconds timeout = std::chrono::seconds(15)) { + if (request.m_method == boost::none || request.m_method->empty()) throw std::runtime_error("No RPC method set in binary request"); + if (!m_http_client) throw std::runtime_error("http client not set"); + + std::string uri = std::string("/") + request.m_method.get(); + std::string body = request.to_binary_val(); + + const epee::net_utils::http::http_response_info* response = invoke_post(uri, body, timeout); + int result = response->m_response_code; + if (result != 200) throw std::runtime_error("HTTP error: code " + std::to_string(result)); + + auto res = std::make_shared(); + res->m_binary = response->m_body; + + return res; + } + + // exposed python methods + + inline boost::optional send_json_request(const std::string method, boost::optional parameters) { + PyMoneroJsonRequest request(method, parameters); + auto response = send_json_request(request); + + return response->get_result(); + } + + inline boost::optional send_path_request(const std::string method, boost::optional parameters) { + PyMoneroPathRequest request(method, parameters); + auto response = send_path_request(request); + + return response->get_response(); + } + + inline boost::optional send_binary_request(const std::string method, boost::optional parameters) { + PyMoneroBinaryRequest request(method, parameters); + auto response = send_binary_request(request); + + return response->get_response(); + } + +protected: + mutable boost::recursive_mutex m_mutex; + std::string m_server; + boost::optional m_credentials; + std::unique_ptr m_http_client; + std::unordered_map m_attributes; + boost::optional m_is_online; + boost::optional m_is_authenticated; +}; + +struct monero_connection_manager_listener { +public: + virtual void on_connection_changed(std::shared_ptr &connection) { + throw std::runtime_error("monero_connection_manager_listener::on_connection_changed(): not implemented"); + } +}; + +class PyMoneroConnectionManagerListener : public monero_connection_manager_listener { +public: + void on_connection_changed(std::shared_ptr &connection) override { + PYBIND11_OVERRIDE_PURE(void, monero_connection_manager_listener, on_connection_changed, connection); + } }; -class PyMoneroUtils { -public: - inline static const uint64_t NUM_MNEMONIC_WORDS = 25; - inline static const uint64_t XMR_AU_MULTIPLIER = 1000000000000ULL; - - PyMoneroUtils() {}; - static std::string get_version() { return std::string("0.0.1"); }; - static int get_ring_size(); - static void set_log_level(int level); - static void configure_logging(const std::string& path, bool console); - static monero_integrated_address get_integrated_address(monero_network_type network_type, const std::string& standard_address, const std::string& payment_id = ""); - static bool is_valid_address(const std::string& address, monero_network_type network_type); - static bool is_valid_public_view_key(const std::string& public_view_key); - static bool is_valid_public_spend_key(const std::string& public_spend_key); - static bool is_valid_private_view_key(const std::string& private_view_key); - static bool is_valid_private_spend_key(const std::string& private_spend_key); - static bool is_valid_payment_id(const std::string& payment_id); - static bool is_valid_mnemonic(const std::string& mnemonic); - static void validate_address(const std::string& address, monero_network_type network_type); - static void validate_public_view_key(const std::string& public_view_key); - static void validate_public_spend_key(const std::string& public_spend_key); - static void validate_private_view_key(const std::string& private_view_key); - static void validate_private_spend_key(const std::string& private_spend_key); - static void validate_payment_id(const std::string& payment_id); - static void validate_mnemonic(const std::string& mnemonic); - - static std::string json_to_binary(const std::string &json); - static std::string dict_to_binary(const py::dict &dictionary); - static py::dict binary_to_dict(const std::string& bin); - static std::string binary_to_json(const std::string &bin); - static void binary_blocks_to_json(const std::string &bin, std::string &json); - static void binary_blocks_to_property_tree(const std::string &bin, boost::property_tree::ptree &node); - static bool is_valid_language(const std::string& language); - static std::vector> get_blocks_from_txs(std::vector> txs); - static std::vector> get_blocks_from_transfers(std::vector> transfers); - static std::vector> get_blocks_from_outputs(std::vector> outputs); - static std::string get_payment_uri(const monero_tx_config& config); - static uint64_t xmr_to_atomic_units(double amount_xmr); - static double atomic_units_to_xmr(uint64_t amount_atomic_units); +class PyMoneroConnectionManager { +public: + + PyMoneroConnectionManager() { } + + PyMoneroConnectionManager(const PyMoneroConnectionManager &connection_manager) { + m_listeners = connection_manager.get_listeners(); + m_connections = connection_manager.get_connections(); + m_current_connection = connection_manager.get_connection(); + m_auto_switch = connection_manager.get_auto_switch(); + m_timeout = connection_manager.get_timeout(); + } + + void add_listener(const std::shared_ptr &listener); + void remove_listener(const std::shared_ptr &listener); + void remove_listeners(); + std::vector> get_listeners() const; + std::shared_ptr get_connection_by_uri(const std::string &uri); + void add_connection(std::shared_ptr connection); + void add_connection(const std::string &uri); + void remove_connection(const std::string &uri); + void set_connection(std::shared_ptr connection); + void set_connection(const std::string& uri); + bool has_connection(const std::string& uri); + std::shared_ptr get_connection() const { return m_current_connection; } + std::vector> get_connections() const { return m_connections; } + bool get_auto_switch() const { return m_auto_switch; } + void set_timeout(uint64_t timeout_ms) { m_timeout = timeout_ms; } + uint64_t get_timeout() const { return m_timeout; } + bool is_connected() const; + void check_connection(); + void set_auto_switch(bool auto_switch); + void stop_polling(); + void start_polling(boost::optional period_ms, boost::optional auto_switch, boost::optional timeout_ms, boost::optional poll_type, boost::optional>> &excluded_connections); + std::vector> get_peer_connections() const { throw std::runtime_error("PyMoneroConnectionManager::get_peer_connections(): not implemented"); } + std::shared_ptr get_best_available_connection(const std::set>& excluded_connections = {}); + std::shared_ptr get_best_available_connection(std::shared_ptr& excluded_connection); + void check_connections(); + void disconnect(); + void clear(); + void reset(); private: + // static variables + inline static const uint64_t DEFAULT_TIMEOUT = 5000; + inline static const uint64_t DEFAULT_POLL_PERIOD = 20000; + inline static const bool DEFAULT_AUTO_SWITCH = true; + inline static const int MIN_BETTER_RESPONSES = 3; + mutable boost::recursive_mutex m_listeners_mutex; + mutable boost::recursive_mutex m_connections_mutex; + std::vector> m_listeners; + std::vector> m_connections; + std::shared_ptr m_current_connection; + bool m_auto_switch = true; + uint64_t m_timeout = 5000; + std::map, std::vector>> m_response_times; + bool m_is_polling = false; + std::thread m_thread; - static bool is_hex_64(const std::string& value); - static std::string make_uri(const std::string &address, const std::string &payment_id, uint64_t amount, const std::string &tx_description, const std::string &recipient_name); + void on_connection_changed(std::shared_ptr connection); + std::vector>> get_connections_in_ascending_priority(); + void start_polling_connection(uint64_t period_ms); + void start_polling_connections(uint64_t period_ms); + void start_polling_prioritized_connections(uint64_t period_ms, boost::optional>> excluded_connections); + bool check_connections(const std::vector>& connections, const std::set>& excluded_connections = {}); + void check_prioritized_connections(boost::optional>> excluded_connections); + std::shared_ptr process_responses(const std::vector>& responses); + std::shared_ptr get_best_connection_from_prioritized_responses(const std::vector>& responses); + std::shared_ptr update_best_connection_in_priority(); }; diff --git a/src/cpp/daemon/py_monero_daemon.h b/src/cpp/daemon/py_monero_daemon.h index 2fac192..1067da7 100644 --- a/src/cpp/daemon/py_monero_daemon.h +++ b/src/cpp/daemon/py_monero_daemon.h @@ -15,12 +15,7 @@ class monero_daemon_listener { class PyMoneroDaemonListener : public monero_daemon_listener { public: virtual void on_block_header(const std::shared_ptr &header) { - PYBIND11_OVERRIDE( - void, - monero_daemon_listener, - on_block_header, - header - ); + PYBIND11_OVERRIDE(void, monero_daemon_listener, on_block_header, header); } }; diff --git a/src/cpp/daemon/py_monero_daemon_model.cpp b/src/cpp/daemon/py_monero_daemon_model.cpp index 2a8c4f8..805df74 100644 --- a/src/cpp/daemon/py_monero_daemon_model.cpp +++ b/src/cpp/daemon/py_monero_daemon_model.cpp @@ -1,264 +1,5 @@ #include "py_monero_daemon_model.h" - - -std::string PyMoneroBinaryRequest::to_binary_val() const { - auto json_val = serialize(); - std::string binary_val; - monero_utils::json_to_binary(json_val, binary_val); - return binary_val; -} - -std::string PyMoneroRequestParams::serialize() const { - if (m_py_params == boost::none) return PySerializableStruct::serialize(); - auto node = PyGenUtils::pyobject_to_ptree(m_py_params.get()); - return monero_utils::serialize(node); -} - -boost::optional PyMoneroJsonResponse::get_result() const { - boost::optional res; - if (m_result != boost::none) res = PyGenUtils::ptree_to_pyobject(m_result.get()); - return res; -} - -std::shared_ptr PyMoneroJsonResponse::deserialize(const std::string& response_json) { - // deserialize json to property node - std::istringstream iss = response_json.empty() ? std::istringstream() : std::istringstream(response_json); - boost::property_tree::ptree node; - boost::property_tree::read_json(iss, node); - - auto response = std::make_shared(); - - for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { - std::string key = it->first; - if (key == std::string("error")) { - std::string err_message = "Unknown error"; - int err_code = -1; - for (auto it_err = it->second.begin(); it_err != it->second.end(); ++it_err) { - std::string key_err = it_err->first; - if (key_err == std::string("message")) { - err_message = it_err->second.data(); - } - else if (key_err == std::string("code")) { - err_code = it_err->second.get_value(); - } - } - - throw PyMoneroRpcError(err_code, err_message); - } - else if (key == std::string("jsonrpc")) { - response->m_jsonrpc = it->second.data(); - } - else if (key == std::string("id")) { - response->m_id = it->second.data(); - } - else if (key == std::string("result")) { - response->m_result = it->second; - } - else std::cout << std::string("WARNING MoneroJsonResponse::deserialize() unrecognized key: ") << key << std::endl; - } - - return response; -} - -boost::optional PyMoneroPathResponse::get_response() const { - boost::optional res; - if (m_response != boost::none) res = PyGenUtils::ptree_to_pyobject(m_response.get()); - return res; -} - -std::shared_ptr PyMoneroPathResponse::deserialize(const std::string& response_json) { - // deserialize json to property node - std::istringstream iss = response_json.empty() ? std::istringstream() : std::istringstream(response_json); - boost::property_tree::ptree node; - boost::property_tree::read_json(iss, node); - auto response = std::make_shared(); - response->m_response = node; - return response; -} - -std::shared_ptr PyMoneroBinaryResponse::deserialize(const std::string& response_binary) { - auto response = std::make_shared(); - response->m_binary = response_binary; - return response; -} - -boost::optional PyMoneroBinaryResponse::get_response() const { - boost::optional res; - if (m_response != boost::none) res = PyGenUtils::ptree_to_pyobject(m_response.get()); - return res; -} - -int PyMoneroRpcConnection::compare(std::shared_ptr c1, std::shared_ptr c2, std::shared_ptr current_connection) { - // current connection is first - if (c1 == current_connection) return -1; - if (c2 == current_connection) return 1; - - // order by availability then priority then by name - if (c1->is_online() == c2->is_online()) { - if (c1->m_priority == c2->m_priority) { - if (c1->m_uri == c2->m_uri) return 1; - else return -1; - } - return PyMoneroConnectionPriorityComparator::compare(c1->m_priority, c2->m_priority) * -1; // order by priority in descending order - } else { - if (c1->is_online()) return -1; - else if (c2->is_online()) return 1; - // TODO manage never connected - //else if (!c1->is_online()) return -1; - //else return 1; // c1 is offline - return -1; - } -} - -bool PyMoneroRpcConnection::is_onion() const { - if (m_uri == boost::none) return false; - if (m_uri && m_uri->size() >= 6 && m_uri->compare(m_uri->size() - 6, 6, ".onion") == 0) { - return true; - } - return false; -} - -bool PyMoneroRpcConnection::is_i2p() const { - if (m_uri == boost::none) return false; - if (m_uri && m_uri->size() >= 8 && m_uri->compare(m_uri->size() - 8, 8, ".b32.i2p") == 0) { - return true; - } - return false; -} - -void PyMoneroRpcConnection::set_credentials(const std::string& username, const std::string& password) { - if (m_http_client != nullptr) { - if (m_http_client->is_connected()) { - m_http_client->disconnect(); - } - } - else { - auto factory = new net::http::client_factory(); - m_http_client = factory->create(); - } - - if (username.empty()) { - m_username = boost::none; - } - - if (password.empty()) { - m_password = boost::none; - } - - if (!password.empty() || !username.empty()) { - if (password.empty()) { - throw PyMoneroError("username cannot be empty because password is not empty"); - } - - if (username.empty()) { - throw PyMoneroError("password cannot be empty because username is not empty"); - } - } - - if (m_username != username || m_password != password) { - m_is_online = boost::none; - m_is_authenticated = boost::none; - } - - m_username = username; - m_password = password; -} - -void PyMoneroRpcConnection::set_attribute(const std::string& key, const std::string& val) { - m_attributes[key] = val; -} - -std::string PyMoneroRpcConnection::get_attribute(const std::string& key) { - std::unordered_map::const_iterator i = m_attributes.find(key); - if (i == m_attributes.end()) - return std::string(""); - return i->second; -} - -bool PyMoneroRpcConnection::is_online() const { - return m_is_online.value_or(false); -} - -bool PyMoneroRpcConnection::is_authenticated() const { - return m_is_authenticated.value_or(false); -} - -bool PyMoneroRpcConnection::is_connected() const { - if (!is_online()) return false; - if (m_is_authenticated != boost::none) { - return is_authenticated(); - } - return true; -} - -bool PyMoneroRpcConnection::check_connection(int timeout_ms) { - boost::optional is_online_before = m_is_online; - boost::optional is_authenticated_before = m_is_authenticated; - boost::lock_guard lock(m_mutex); - try { - if (!m_http_client) throw std::runtime_error("http client not set"); - if (m_http_client->is_connected()) { - m_http_client->disconnect(); - } - - if (m_proxy_uri != boost::none) { - if(!m_http_client->set_proxy(m_proxy_uri.get())) { - throw std::runtime_error("Could not set proxy"); - } - } - - if(m_username != boost::none && !m_username->empty() && m_password != boost::none && !m_password->empty()) { - auto credentials = std::make_shared(); - - credentials->username = *m_username; - credentials->password = *m_password; - - m_credentials = *credentials; - } - - if (!m_http_client->set_server(m_uri.get(), m_credentials)) { - throw std::runtime_error("Could not set rpc connection: " + m_uri.get()); - } - - m_http_client->connect(std::chrono::milliseconds(timeout_ms)); - auto start = std::chrono::high_resolution_clock::now(); - PyMoneroJsonRequest request("get_version"); - auto response = send_json_request(request); - auto end = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration_cast(end - start); - if (response->m_result == boost::none) { - throw PyMoneroRpcError(-1, "Invalid JSON RPC response"); - } - - m_is_online = true; - m_is_authenticated = true; - m_response_time = duration.count(); - - return is_online_before != m_is_online || is_authenticated_before != m_is_authenticated; - } - catch (const PyMoneroRpcError& ex) { - m_is_online = false; - m_is_authenticated = boost::none; - m_response_time = boost::none; - - if (ex.code == 401) { - m_is_online = true; - m_is_authenticated = false; - } - else if (ex.code == 404) { - m_is_online = true; - m_is_authenticated = true; - } - - return false; - } - catch (const std::exception& ex) { - m_is_online = false; - m_is_authenticated = boost::none; - m_response_time = boost::none; - return false; - } -} +#include "utils/monero_utils.h" void PyMoneroBlockHeader::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& header) { for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { @@ -1091,23 +832,6 @@ rapidjson::Value PyMoneroStartMiningParams::to_rapidjson_val(rapidjson::Document return root; } -rapidjson::Value PyMoneroPathRequest::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { - if (m_params != boost::none) return m_params.get()->to_rapidjson_val(allocator); - throw std::runtime_error("No params provided"); -} - -rapidjson::Value PyMoneroJsonRequest::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { - rapidjson::Value root(rapidjson::kObjectType); - rapidjson::Value value_str(rapidjson::kStringType); - - if (m_version != boost::none) monero_utils::add_json_member("version", 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 != boost::none) root.AddMember("params", m_params.get()->to_rapidjson_val(allocator), allocator); - - return root; -} - rapidjson::Value PyMoneroPruneBlockchainParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); if (m_check != boost::none) monero_utils::add_json_member("check", m_check.get(), allocator, root); @@ -1249,459 +973,3 @@ rapidjson::Value PyMoneroIsKeyImageSpentParams::to_rapidjson_val(rapidjson::Docu if (!m_key_images.empty()) root.AddMember("key_images", monero_utils::to_rapidjson_val(allocator, m_key_images), allocator); return root; } - -void PyMoneroConnectionManager::add_listener(const std::shared_ptr &listener) { - boost::lock_guard lock(m_listeners_mutex); - m_listeners.push_back(listener); -} - -void PyMoneroConnectionManager::remove_listener(const std::shared_ptr &listener) { - boost::lock_guard lock(m_listeners_mutex); - m_listeners.erase(std::remove_if(m_listeners.begin(), m_listeners.end(), [&listener](std::shared_ptr iter){ return iter == listener; }), m_listeners.end()); -} - -void PyMoneroConnectionManager::remove_listeners() { - boost::lock_guard lock(m_listeners_mutex); - m_listeners.clear(); -} - -std::vector> PyMoneroConnectionManager::get_listeners() const { - return m_listeners; -} - -std::shared_ptr PyMoneroConnectionManager::get_connection_by_uri(const std::string &uri) { - boost::lock_guard lock(m_connections_mutex); - for(const auto &m_connection : m_connections) { - if (m_connection->m_uri == uri) return m_connection; - } - - return nullptr; -} - -void PyMoneroConnectionManager::add_connection(std::shared_ptr connection) { - if (connection->m_uri == boost::none) throw std::runtime_error("Invalid connection uri"); - boost::lock_guard lock(m_connections_mutex); - for(const auto &m_connection : m_connections) { - if (m_connection->m_uri == connection->m_uri) throw std::runtime_error("Connection URI already exists with connection manager: " + connection->m_uri.get()); - } - - m_connections.push_back(connection); -} - -void PyMoneroConnectionManager::add_connection(const std::string &uri) { - std::shared_ptr connection = std::make_shared(); - connection->m_uri = uri; - add_connection(connection); -} - -void PyMoneroConnectionManager::remove_connection(const std::string &uri) { - boost::lock_guard lock(m_connections_mutex); - - std::shared_ptr connection = get_connection_by_uri(uri); - - if (connection == nullptr) throw std::runtime_error("Connection not found"); - - m_connections.erase(std::remove_if(m_connections.begin(), m_connections.end(), [&connection](std::shared_ptr iter){ return iter == connection; }), m_connections.end()); - - if (connection == m_current_connection) { - m_current_connection = nullptr; - on_connection_changed(m_current_connection); - } -} - -void PyMoneroConnectionManager::set_connection(std::shared_ptr connection) { - if (connection == m_current_connection) return; - - if (connection == nullptr) { - m_current_connection = nullptr; - on_connection_changed(nullptr); - return; - } - - if (connection->m_uri == boost::none || connection->m_uri->empty()) throw std::runtime_error("Connection is missing URI"); - - boost::lock_guard lock(m_connections_mutex); - - auto prev_connection = get_connection_by_uri(connection->m_uri.get()); - if (prev_connection != nullptr) m_connections.erase(std::remove_if(m_connections.begin(), m_connections.end(), [&prev_connection](std::shared_ptr iter){ return iter == prev_connection; }), m_connections.end()); - add_connection(connection); - m_current_connection = connection; - on_connection_changed(connection); -} - -void PyMoneroConnectionManager::set_connection(const std::string& uri) { - if (uri.empty()) { - set_connection(std::shared_ptr(nullptr)); - return; - } - - auto found = get_connection_by_uri(uri); - - if (found != nullptr) { - set_connection(found); - } - else { - auto connection = std::make_shared(); - connection->m_uri = uri; - set_connection(connection); - } -} - -bool PyMoneroConnectionManager::has_connection(const std::string& uri) { - auto connection = get_connection_by_uri(uri); - - if (connection != nullptr) return true; - return false; -} - -bool PyMoneroConnectionManager::is_connected() const { - if (m_current_connection == nullptr) return false; - return m_current_connection->is_connected(); -} - -void PyMoneroConnectionManager::check_connection() { - bool connection_changed = false; - std::shared_ptr connection = get_connection(); - if (connection != nullptr) { - if (connection->check_connection(m_timeout)) connection_changed = true; - std::vector> cons; - cons.push_back(connection); - process_responses(cons); - } - if (m_auto_switch && !is_connected()) { - std::shared_ptr best_connection = get_best_available_connection(connection); - if (best_connection != nullptr) { - set_connection(best_connection); - return; - } - } - if (connection_changed) on_connection_changed(connection); -} - -void PyMoneroConnectionManager::set_auto_switch(bool auto_switch) { - m_auto_switch = auto_switch; -} - -void PyMoneroConnectionManager::stop_polling() { - if (m_is_polling) { - m_is_polling = false; - if (m_thread.joinable()) m_thread.join(); - } -} - -void PyMoneroConnectionManager::start_polling(boost::optional period_ms, boost::optional auto_switch, boost::optional timeout_ms, boost::optional poll_type, boost::optional>> &excluded_connections) { - // apply defaults - if (period_ms == boost::none) period_ms = DEFAULT_POLL_PERIOD; - if (auto_switch != boost::none) set_auto_switch(auto_switch.get()); - if (timeout_ms != boost::none) set_timeout(timeout_ms.get()); - if (poll_type == boost::none) poll_type = PyMoneroConnectionPollType::PRIORITIZED; - - // stop polling - stop_polling(); - - // start polling - switch (poll_type.get()) { - case PyMoneroConnectionPollType::CURRENT: - start_polling_connection(period_ms.get()); - break; - case PyMoneroConnectionPollType::ALL: - start_polling_connections(period_ms.get()); - break; - case PyMoneroConnectionPollType::UNDEFINED: - case PyMoneroConnectionPollType::PRIORITIZED: - start_polling_prioritized_connections(period_ms.get(), excluded_connections); - break; - } -} - -std::shared_ptr PyMoneroConnectionManager::get_best_available_connection(const std::set>& excluded_connections) { - auto cons = get_connections_in_ascending_priority(); - for (const auto& prioritizedConnections : cons) { - try { - std::vector futures; - for (const auto& connection : prioritizedConnections) { - if (!connection) throw std::runtime_error("connection is nullptr"); - if (excluded_connections.count(connection)) continue; - std::thread thread = std::thread([this, connection]() { - connection->check_connection(m_timeout); - }); - thread.detach(); - futures.push_back(&thread); - } - - for (auto& fut : futures) { - if (fut->joinable()) fut->join(); - } - - std::shared_ptr best_connection = nullptr; - - for (const auto& conn : prioritizedConnections) { - try { - if (!conn) throw std::runtime_error("connection is nullptr"); - if (conn->is_connected()) { - if (best_connection == nullptr || best_connection->m_response_time == boost::none || best_connection->m_response_time < conn->m_response_time) best_connection = conn; - } - } catch (...) { - std::cout << "MoneroRpcConnection::get_best_available_connection(): error" << std::endl; - } - } - - if (best_connection != nullptr) return best_connection; - } catch (const std::exception& e) { - throw std::runtime_error(std::string("Connection check error: ") + e.what()); - } - } - - return std::shared_ptr(nullptr); -} - -std::shared_ptr PyMoneroConnectionManager::get_best_available_connection(std::shared_ptr& excluded_connection) { - const std::set>& excluded_connections = { excluded_connection }; - - return get_best_available_connection(excluded_connections); -} - -void PyMoneroConnectionManager::check_connections() { - check_connections(get_connections()); -} - -void PyMoneroConnectionManager::disconnect() { - set_connection(std::shared_ptr(nullptr)); -} - -void PyMoneroConnectionManager::clear() { - boost::lock_guard lock(m_connections_mutex); - - m_connections.clear(); - - if (m_current_connection != nullptr) { - m_current_connection = nullptr; - on_connection_changed(m_current_connection); - } -} - -void PyMoneroConnectionManager::reset() { - remove_listeners(); - stop_polling(); - clear(); - m_timeout = DEFAULT_TIMEOUT; - m_auto_switch = DEFAULT_AUTO_SWITCH; -} - -void PyMoneroConnectionManager::on_connection_changed(std::shared_ptr connection) { - boost::lock_guard lock(m_listeners_mutex); - - for (const auto &listener : m_listeners) { - listener->on_connection_changed(connection); - } -} - -std::vector>> PyMoneroConnectionManager::get_connections_in_ascending_priority() { - boost::lock_guard lock(m_connections_mutex); - - std::map>> connection_priorities; - - for (const auto& connection : m_connections) { - int priority = connection->m_priority; - connection_priorities[priority].push_back(connection); - } - - std::vector>> prioritized_connections; - - for (auto& [priority, group] : connection_priorities) { - prioritized_connections.push_back(group); - } - - if (!prioritized_connections.empty() && connection_priorities.count(0)) { - auto it = std::find_if(prioritized_connections.begin(), prioritized_connections.end(), - [](const auto& group) { - return !group.empty() && group[0]->m_priority == 0; - }); - - if (it != prioritized_connections.end()) { - auto zero_priority_group = *it; - prioritized_connections.erase(it); - prioritized_connections.push_back(zero_priority_group); - } - } - - return prioritized_connections; -} - -void PyMoneroConnectionManager::start_polling_connection(uint64_t period_ms) { - m_is_polling = true; - - m_thread = std::thread([this, period_ms]() { - while (m_is_polling) { - try { - check_connection(); - } catch (const std::exception& e) { - std::cout << "ERROR " << e.what() << std::endl; - } - - std::this_thread::sleep_for(std::chrono::milliseconds(period_ms)); - } - }); - m_thread.detach(); -} - -void PyMoneroConnectionManager::start_polling_connections(uint64_t period_ms) { - m_is_polling = true; - - m_thread = std::thread([this, period_ms]() { - while (m_is_polling) { - try { - check_connections(); - } catch (const std::exception& e) { - std::cout << "ERROR " << e.what() << std::endl; - } - - std::this_thread::sleep_for(std::chrono::milliseconds(period_ms)); - } - }); - m_thread.detach(); -} - -void PyMoneroConnectionManager::start_polling_prioritized_connections(uint64_t period_ms, boost::optional>> excluded_connections) { - m_is_polling = true; - m_thread = std::thread([this, period_ms, &excluded_connections]() { - while (m_is_polling) { - try { - check_prioritized_connections(excluded_connections); - } catch (const std::exception& e) { - std::cout << "ERROR " << e.what() << std::endl; - } - - std::this_thread::sleep_for(std::chrono::milliseconds(period_ms)); - } - }); - m_thread.detach(); -} - -bool PyMoneroConnectionManager::check_connections(const std::vector>& connections, const std::set>& excluded_connections) { - boost::lock_guard lock(m_connections_mutex); - try { - auto timeout_ms = m_timeout; - - bool has_connection = false; - - for (const auto& connection : connections) { - if (excluded_connections.count(connection)) continue; - - bool changed = connection->check_connection(timeout_ms); - if (changed && connection == get_connection()) { - on_connection_changed(connection); - } - if (connection->is_connected() && !has_connection) { - has_connection = true; - if (!is_connected() && m_auto_switch) { - set_connection(connection); - } - } - } - - process_responses(connections); - return has_connection; - } - catch (const std::exception& e) { - throw std::runtime_error(std::string("check_connections failed: ") + e.what()); - } -} - -void PyMoneroConnectionManager::check_prioritized_connections(boost::optional>> excluded_connections) { - for (const auto &prioritized_connections : get_connections_in_ascending_priority()) { - if (excluded_connections != boost::none) { - std::set> ex(excluded_connections.get().begin(), excluded_connections.get().end()); - check_connections(prioritized_connections, ex); - } - else { check_connections(prioritized_connections, {}); } - } -} - -std::shared_ptr PyMoneroConnectionManager::process_responses(const std::vector>& responses) { - for (const auto& conn : responses) { - if (m_response_times.find(conn) == m_response_times.end()) { - m_response_times[conn] = {}; - } - } - - for (auto& [conn, times] : m_response_times) { - if (std::find(responses.begin(), responses.end(), conn) != responses.end()) { - times.insert(times.begin(), conn->m_response_time); - } else { - times.insert(times.begin(), boost::none); - } - - if (times.size() > MIN_BETTER_RESPONSES) { - times.pop_back(); - } - } - - return update_best_connection_in_priority(); -} - -std::shared_ptr PyMoneroConnectionManager::get_best_connection_from_prioritized_responses(const std::vector>& responses) { - std::shared_ptr best_response = std::shared_ptr(nullptr); - - for (const auto& conn : responses) { - if (conn->is_connected()) { - if (!best_response || conn->m_response_time < best_response->m_response_time) { - best_response = conn; - } - } - } - - if (!best_response) return std::shared_ptr(nullptr); - - auto best_connection = get_connection(); - if (!best_connection || !best_connection->is_connected()) { - return best_response; - } - - if (PyMoneroConnectionPriorityComparator::compare(best_response->m_priority, best_connection->m_priority) != 0) { - return best_response; - } - - if (m_response_times.find(best_connection) == m_response_times.end()) { - return best_connection; - } - - for (const auto& conn : responses) { - if (conn == best_connection) continue; - - auto it_best = m_response_times.find(best_connection); - auto it_curr = m_response_times.find(conn); - if (it_curr == m_response_times.end()) continue; - if (it_curr->second.size() < MIN_BETTER_RESPONSES || it_best->second.size() < MIN_BETTER_RESPONSES) continue; - - bool consistently_better = true; - for (int i = 0; i < MIN_BETTER_RESPONSES; ++i) { - auto curr_time = it_curr->second[i]; - auto best_time = it_best->second[i]; - if (curr_time == boost::none || best_time == boost::none || curr_time.get() > best_time.get()) { - consistently_better = false; - break; - } - } - - if (consistently_better) { - best_connection = conn; - } - } - - return best_connection; -} - -std::shared_ptr PyMoneroConnectionManager::update_best_connection_in_priority() { - if (!m_auto_switch) return std::shared_ptr(nullptr); - - for (const auto& prioritized_connections : get_connections_in_ascending_priority()) { - auto best_conn = get_best_connection_from_prioritized_responses(prioritized_connections); - if (best_conn != nullptr) { - set_connection(best_conn); - return best_conn; - } - } - - return std::shared_ptr(nullptr); -} diff --git a/src/cpp/daemon/py_monero_daemon_model.h b/src/cpp/daemon/py_monero_daemon_model.h index e72b357..2a1af6c 100644 --- a/src/cpp/daemon/py_monero_daemon_model.h +++ b/src/cpp/daemon/py_monero_daemon_model.h @@ -1,12 +1,5 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include #include "common/py_monero_common.h" enum PyMoneroKeyImageSpentStatus : uint8_t { @@ -47,24 +40,6 @@ class PyMoneroTxHashes { // #region JSON-RPC -class PyMoneroRequestParams : public PySerializableStruct { -public: - boost::optional m_py_params; - - PyMoneroRequestParams() { } - PyMoneroRequestParams(boost::optional py_params) { m_py_params = py_params; } - - std::string serialize() const override; - rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override { throw std::runtime_error("PyMoneroRequestParams::to_rapid_json_value(): not implemented"); }; -}; - -class PyMoneroRequestEmptyParams : public PyMoneroRequestParams { - public: - PyMoneroRequestEmptyParams() {} - - rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override { rapidjson::Value root(rapidjson::kObjectType); return root; }; -}; - class PyMoneroDownloadUpdateParams : public PyMoneroRequestParams { public: boost::optional m_command; @@ -118,107 +93,6 @@ class PyMoneroStartMiningParams : public PyMoneroRequestParams { rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroJsonRequestParams : public PyMoneroRequestParams { -public: - PyMoneroJsonRequestParams() { } - PyMoneroJsonRequestParams(boost::optional py_params) { m_py_params = py_params; } - - rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override { throw std::runtime_error("PyMoneroJsonRequestParams::to_rapid_json_value(): not implemented"); }; -}; - -class PyMoneroJsonRequestEmptyParams : public PyMoneroJsonRequestParams { -public: - PyMoneroJsonRequestEmptyParams() {} - - rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override { rapidjson::Value root(rapidjson::kObjectType); return root; }; -}; - -class PyMoneroRequest : public PySerializableStruct { -public: - boost::optional m_method; - - PyMoneroRequest() { } - - rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override { throw std::runtime_error("PyMoneroRequest::to_rapid_json_value(): not implemented"); }; -}; - -class PyMoneroPathRequest : public PyMoneroRequest { -public: - boost::optional> m_params; - - PyMoneroPathRequest() { } - - PyMoneroPathRequest(std::string method, boost::optional params = boost::none) { - m_method = method; - if (params != boost::none) m_params = std::make_shared(params); - m_params = std::make_shared(); - } - - PyMoneroPathRequest(std::string method, std::shared_ptr params) { - m_method = method; - m_params = params; - } - - rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; -}; - -class PyMoneroBinaryRequest : public PyMoneroPathRequest { -public: - PyMoneroBinaryRequest() {} - - PyMoneroBinaryRequest(std::string method, boost::optional params = boost::none) { - m_method = method; - if (params != boost::none) m_params = std::make_shared(params); - m_params = std::make_shared(); - } - - PyMoneroBinaryRequest(std::string method, std::shared_ptr params) { - m_method = method; - m_params = params; - } - - std::string to_binary_val() const; -}; - -class PyMoneroJsonRequest : public PyMoneroRequest { -public: - boost::optional m_version; - boost::optional m_id; - boost::optional> m_params; - - PyMoneroJsonRequest() { - m_version = "2.0"; - m_id = "0"; - m_params = std::make_shared(); - } - - PyMoneroJsonRequest(const PyMoneroJsonRequest& request) { - m_version = request.m_version; - m_id = request.m_id; - m_method = request.m_method; - m_params = request.m_params; - } - - PyMoneroJsonRequest(std::string method, boost::optional params = boost::none) { - m_version = "2.0"; - m_id = "0"; - m_method = method; - if (params != boost::none) { - m_params = std::make_shared(params); - } - else m_params = std::make_shared(); - } - - PyMoneroJsonRequest(std::string method, std::shared_ptr params) { - m_version = "2.0"; - m_id = "0"; - m_method = method; - m_params = params; - } - - rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; -}; - class PyMoneroPruneBlockchainParams : public PyMoneroJsonRequestParams { public: boost::optional m_check; @@ -320,72 +194,6 @@ class PyMoneroGetBlocksByHeightRequest : public PyMoneroBinaryRequest { rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroJsonResponse { -public: - boost::optional m_jsonrpc; - boost::optional m_id; - boost::optional m_result; - - static std::shared_ptr deserialize(const std::string& response_json); - - PyMoneroJsonResponse() { - m_jsonrpc = "2.0"; - m_id = "0"; - } - - PyMoneroJsonResponse(const PyMoneroJsonResponse& response) { - m_jsonrpc = response.m_jsonrpc; - m_id = response.m_id; - m_result = response.m_result; - } - - PyMoneroJsonResponse(boost::optional &result) { - m_jsonrpc = "2.0"; - m_id = "0"; - m_result = result; - } - - boost::optional get_result() const; -}; - -class PyMoneroPathResponse { -public: - boost::optional m_response; - - PyMoneroPathResponse() { } - - PyMoneroPathResponse(const PyMoneroPathResponse& response) { - m_response = response.m_response; - } - - PyMoneroPathResponse(boost::optional &response) { - m_response = response; - } - - boost::optional get_response() const; - static std::shared_ptr deserialize(const std::string& response_json); -}; - -class PyMoneroBinaryResponse { -public: - boost::optional m_binary; - boost::optional m_response; - - PyMoneroBinaryResponse() {} - - PyMoneroBinaryResponse(const std::string &binary) { - m_binary = binary; - } - - PyMoneroBinaryResponse(const PyMoneroBinaryResponse& response) { - m_binary = response.m_binary; - m_response = response.m_response; - } - - static std::shared_ptr deserialize(const std::string& response_binary); - boost::optional get_response() const; -}; - class PyMoneroVersion : public monero::monero_version { public: PyMoneroVersion() {} @@ -828,246 +636,3 @@ class PyMoneroIsKeyImageSpentParams : public PyMoneroRequestParams { rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; - -class PyMoneroRpcConnection : public monero_rpc_connection { -public: - boost::optional m_zmq_uri; - int m_priority; - uint64_t m_timeout; - boost::optional m_response_time; - - static int compare(std::shared_ptr c1, std::shared_ptr c2, std::shared_ptr current_connection); - - PyMoneroRpcConnection(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, uint64_t timeout = 0) { - m_uri = uri; - m_username = username; - m_password = password; - m_zmq_uri = zmq_uri; - m_priority = priority; - m_timeout = timeout; - m_proxy_uri = proxy_uri; - set_credentials(username, password); - } - - PyMoneroRpcConnection(const PyMoneroRpcConnection& rpc) { - m_uri = rpc.m_uri; - m_username = rpc.m_username; - m_password = rpc.m_password; - m_zmq_uri = rpc.m_zmq_uri; - m_proxy_uri = rpc.m_proxy_uri; - m_is_authenticated = rpc.m_is_authenticated; - set_credentials(m_username.value_or(""), m_password.value_or("")); - } - - PyMoneroRpcConnection(const monero::monero_rpc_connection& rpc) { - m_uri = rpc.m_uri; - m_username = rpc.m_username; - m_password = rpc.m_password; - m_proxy_uri = rpc.m_proxy_uri; - set_credentials(m_username.value_or(""), m_password.value_or("")); - } - - bool is_onion() const; - bool is_i2p() const; - void set_credentials(const std::string& username, const std::string& password); - void set_attribute(const std::string& key, const std::string& val); - std::string get_attribute(const std::string& key); - bool is_online() const; - bool is_authenticated() const; - bool is_connected() const; - bool check_connection(int timeout_ms = 2000); - - template - inline int invoke_post(const boost::string_ref uri, const t_request& request, t_response& res, std::chrono::milliseconds timeout = std::chrono::seconds(15)) const { - if (!m_http_client) throw std::runtime_error("http client not set"); - - rapidjson::Document document(rapidjson::Type::kObjectType); - rapidjson::Value req = request.to_rapidjson_val(document.GetAllocator()); - rapidjson::StringBuffer sb; - rapidjson::Writer writer(sb); - req.Accept(writer); - std::string body = sb.GetString(); - - const epee::net_utils::http::http_response_info* response = invoke_post(uri, body, timeout); - - int status_code = response->m_response_code; - - if (status_code == 200) { - res = *t_response::deserialize(response->m_body); - } - - return status_code; - } - - inline const epee::net_utils::http::http_response_info* invoke_post(const boost::string_ref uri, const std::string& body, std::chrono::milliseconds timeout = std::chrono::seconds(15)) const { - if (!m_http_client) throw std::runtime_error("http client not set"); - - std::shared_ptr _res = std::make_shared(); - const epee::net_utils::http::http_response_info* response = _res.get(); - boost::lock_guard lock(m_mutex); - - if (!m_http_client->invoke_post(uri, body, timeout, &response)) throw std::runtime_error("Network error"); - - return response; - } - - inline const std::shared_ptr send_json_request(const PyMoneroJsonRequest &request, std::chrono::milliseconds timeout = std::chrono::seconds(15)) { - PyMoneroJsonResponse response; - - int result = invoke_post("/json_rpc", request, response, timeout); - - if (result != 200) throw std::runtime_error("HTTP error: code " + std::to_string(result)); - - return std::make_shared(response); - } - - inline const std::shared_ptr send_path_request(const PyMoneroPathRequest &request, std::chrono::milliseconds timeout = std::chrono::seconds(15)) { - PyMoneroPathResponse response; - - if (request.m_method == boost::none || request.m_method->empty()) throw std::runtime_error("No RPC method set in path request"); - int result = invoke_post(std::string("/") + request.m_method.get(), request, response, timeout); - - if (result != 200) throw std::runtime_error("HTTP error: code " + std::to_string(result)); - - return std::make_shared(response); - } - - inline const std::shared_ptr send_binary_request(const PyMoneroBinaryRequest &request, std::chrono::milliseconds timeout = std::chrono::seconds(15)) { - if (request.m_method == boost::none || request.m_method->empty()) throw std::runtime_error("No RPC method set in binary request"); - if (!m_http_client) throw std::runtime_error("http client not set"); - - std::string uri = std::string("/") + request.m_method.get(); - std::string body = request.to_binary_val(); - - const epee::net_utils::http::http_response_info* response = invoke_post(uri, body, timeout); - int result = response->m_response_code; - if (result != 200) throw std::runtime_error("HTTP error: code " + std::to_string(result)); - - auto res = std::make_shared(); - res->m_binary = response->m_body; - - return res; - } - - // exposed python methods - - inline boost::optional send_json_request(const std::string method, boost::optional parameters) { - PyMoneroJsonRequest request(method, parameters); - auto response = send_json_request(request); - - return response->get_result(); - } - - inline boost::optional send_path_request(const std::string method, boost::optional parameters) { - PyMoneroPathRequest request(method, parameters); - auto response = send_path_request(request); - - return response->get_response(); - } - - inline boost::optional send_binary_request(const std::string method, boost::optional parameters) { - PyMoneroBinaryRequest request(method, parameters); - auto response = send_binary_request(request); - - return response->get_response(); - } - -protected: - mutable boost::recursive_mutex m_mutex; - std::string m_server; - boost::optional m_credentials; - std::unique_ptr m_http_client; - serializable_unordered_map m_attributes; - boost::optional m_is_online; - boost::optional m_is_authenticated; -}; - -struct monero_connection_manager_listener { -public: - virtual void on_connection_changed(std::shared_ptr &connection) { - throw std::runtime_error("monero_connection_manager_listener::on_connection_changed(): not implemented"); - } -}; - -class PyMoneroConnectionManagerListener : public monero_connection_manager_listener { -public: - void on_connection_changed(std::shared_ptr &connection) override { - PYBIND11_OVERRIDE_PURE( - void, - monero_connection_manager_listener, - on_connection_changed, - connection - ); - } -}; - -class PyMoneroConnectionManager { -public: - - PyMoneroConnectionManager() { } - - PyMoneroConnectionManager(const PyMoneroConnectionManager &connection_manager) { - m_listeners = connection_manager.get_listeners(); - m_connections = connection_manager.get_connections(); - m_current_connection = connection_manager.get_connection(); - m_auto_switch = connection_manager.get_auto_switch(); - m_timeout = connection_manager.get_timeout(); - } - - void add_listener(const std::shared_ptr &listener); - void remove_listener(const std::shared_ptr &listener); - void remove_listeners(); - std::vector> get_listeners() const; - std::shared_ptr get_connection_by_uri(const std::string &uri); - void add_connection(std::shared_ptr connection); - void add_connection(const std::string &uri); - void remove_connection(const std::string &uri); - void set_connection(std::shared_ptr connection); - void set_connection(const std::string& uri); - bool has_connection(const std::string& uri); - std::shared_ptr get_connection() const { return m_current_connection; } - std::vector> get_connections() const { return m_connections; } - bool get_auto_switch() const { return m_auto_switch; } - void set_timeout(uint64_t timeout_ms) { m_timeout = timeout_ms; } - uint64_t get_timeout() const { return m_timeout; } - bool is_connected() const; - void check_connection(); - void set_auto_switch(bool auto_switch); - void stop_polling(); - void start_polling(boost::optional period_ms, boost::optional auto_switch, boost::optional timeout_ms, boost::optional poll_type, boost::optional>> &excluded_connections); - std::vector> get_peer_connections() const { throw std::runtime_error("PyMoneroConnectionManager::get_peer_connections(): not implemented"); } - std::shared_ptr get_best_available_connection(const std::set>& excluded_connections = {}); - std::shared_ptr get_best_available_connection(std::shared_ptr& excluded_connection); - void check_connections(); - void disconnect(); - void clear(); - void reset(); - -private: - // static variables - inline static const uint64_t DEFAULT_TIMEOUT = 5000; - inline static const uint64_t DEFAULT_POLL_PERIOD = 20000; - inline static const bool DEFAULT_AUTO_SWITCH = true; - inline static const int MIN_BETTER_RESPONSES = 3; - mutable boost::recursive_mutex m_listeners_mutex; - mutable boost::recursive_mutex m_connections_mutex; - std::vector> m_listeners; - std::vector> m_connections; - std::shared_ptr m_current_connection; - bool m_auto_switch = true; - uint64_t m_timeout = 5000; - std::map, std::vector>> m_response_times; - bool m_is_polling = false; - std::thread m_thread; - - void on_connection_changed(std::shared_ptr connection); - std::vector>> get_connections_in_ascending_priority(); - void start_polling_connection(uint64_t period_ms); - void start_polling_connections(uint64_t period_ms); - void start_polling_prioritized_connections(uint64_t period_ms, boost::optional>> excluded_connections); - bool check_connections(const std::vector>& connections, const std::set>& excluded_connections = {}); - void check_prioritized_connections(boost::optional>> excluded_connections); - std::shared_ptr process_responses(const std::vector>& responses); - std::shared_ptr get_best_connection_from_prioritized_responses(const std::vector>& responses); - std::shared_ptr update_best_connection_in_priority(); -}; diff --git a/src/cpp/daemon/py_monero_daemon_rpc.cpp b/src/cpp/daemon/py_monero_daemon_rpc.cpp index 047b184..b7419fc 100644 --- a/src/cpp/daemon/py_monero_daemon_rpc.cpp +++ b/src/cpp/daemon/py_monero_daemon_rpc.cpp @@ -1,4 +1,5 @@ #include "py_monero_daemon_rpc.h" +#include "utils/py_monero_utils.h" static const uint64_t MAX_REQ_SIZE = 3000000; static const uint64_t NUM_HEADERS_PER_REQ = 750; @@ -230,7 +231,6 @@ std::shared_ptr PyMoneroDaemonRpc::get_block_header return m_cached_headers[height]; } - std::vector> PyMoneroDaemonRpc::get_blocks_by_hash(const std::vector& block_hashes, uint64_t start_height, bool prune) { throw std::runtime_error("PyMoneroDaemonRpc::get_blocks_by_hash(): not implemented"); } diff --git a/src/cpp/py_monero.cpp b/src/cpp/py_monero.cpp index f746e53..6ba4d84 100644 --- a/src/cpp/py_monero.cpp +++ b/src/cpp/py_monero.cpp @@ -1,6 +1,8 @@ #include "daemon/py_monero_daemon_rpc.h" #include "wallet/py_monero_wallet_full.h" #include "wallet/py_monero_wallet_rpc.h" +#include "wallet/monero_wallet_keys.h" +#include "utils/py_monero_utils.h" #define MONERO_CATCH_AND_RETHROW(expr) \ try { \ @@ -29,6 +31,10 @@ PYBIND11_MODULE(monero, m) { m.doc() = ""; auto py_serializable_struct = py::class_>(m, "SerializableStruct"); + auto py_monero_rpc_connection = py::class_>(m, "MoneroRpcConnection"); + auto py_monero_connection_manager_listener = py::class_>(m, "MoneroConnectionManagerListener"); + auto py_monero_connection_manager = py::class_>(m, "MoneroConnectionManager"); + auto py_monero_ssl_options = py::class_(m, "MoneroSslOptions"); auto py_monero_version = py::class_>(m, "MoneroVersion"); auto py_monero_block_header = py::class_>(m, "MoneroBlockHeader"); @@ -80,6 +86,8 @@ PYBIND11_MODULE(monero, m) { py::bind_vector(m, "VectorUint32"); py::bind_vector(m, "VectorUint64"); py::bind_vector>(m, "VectorString"); + py::bind_vector>>(m, "VectorMoneroAccountTagPtr"); + py::bind_vector>>(m, "VectorKeyImagePtr"); py::bind_vector>>(m, "VectorMoneroBlock"); py::bind_vector>>(m, "VectorMoneroBlockHeader"); py::bind_vector>>(m, "VectorMoneroTx"); @@ -97,6 +105,8 @@ PYBIND11_MODULE(monero, m) { py::implicitly_convertible(); py::implicitly_convertible(); py::implicitly_convertible>(); + py::implicitly_convertible>>(); + py::implicitly_convertible>>(); py::implicitly_convertible>>(); py::implicitly_convertible>>(); py::implicitly_convertible>>(); @@ -266,7 +276,7 @@ PYBIND11_MODULE(monero, m) { }, py::arg("p1"), py::arg("p2")); // monero_rpc_connection - py::class_>(m, "MoneroRpcConnection") + py_monero_rpc_connection .def(py::init(), py::arg("uri") = "", py::arg("username") = "", py::arg("password") = "", py::arg("proxy_uri") = "", py::arg("zmq_uri") = "", py::arg("priority") = 0, py::arg("timeout") = 0) .def(py::init(), py::arg("rpc")) .def_static("compare", [](const std::shared_ptr c1, const std::shared_ptr c2, std::shared_ptr current_connection) { @@ -326,14 +336,14 @@ PYBIND11_MODULE(monero, m) { }, py::arg("method"), py::arg("parameters") = py::none()); // monero_connection_manager_listener - py::class_>(m, "MoneroConnectionManagerListener") + py_monero_connection_manager_listener .def(py::init<>()) .def("on_connection_changed", [](monero_connection_manager_listener& self, std::shared_ptr &connection) { MONERO_CATCH_AND_RETHROW(self.on_connection_changed(connection)); }, py::arg("connection")); // monero_connection_manager - py::class_>(m, "MoneroConnectionManager") + py_monero_connection_manager .def(py::init<>()) .def("add_listener", [](PyMoneroConnectionManager& self, std::shared_ptr &listener) { MONERO_CATCH_AND_RETHROW(self.add_listener(listener)); @@ -1765,6 +1775,7 @@ PYBIND11_MODULE(monero, m) { MONERO_CATCH_AND_RETHROW(self.delete_address_book_entry(index)); }, py::arg("index")) .def("tag_accounts", [](PyMoneroWallet& self, const std::string& tag, const std::vector& account_indices) { + std::cout << "Indirizzo dell'oggetto A: " << &self << std::endl; MONERO_CATCH_AND_RETHROW(self.tag_accounts(tag, account_indices)); }, py::arg("tag"), py::arg("account_indices")) .def("untag_accounts", [](PyMoneroWallet& self, const std::vector& account_indices) { diff --git a/src/cpp/utils/py_monero_utils.cpp b/src/cpp/utils/py_monero_utils.cpp new file mode 100644 index 0000000..05b9093 --- /dev/null +++ b/src/cpp/utils/py_monero_utils.cpp @@ -0,0 +1,250 @@ +#include +#include "py_monero_utils.h" +#include "cryptonote_basic/cryptonote_format_utils.h" + + +int PyMoneroUtils::get_ring_size() { + return monero_utils::RING_SIZE; +} + +void PyMoneroUtils::set_log_level(int level) { + monero_utils::set_log_level(level); +} + +void PyMoneroUtils::configure_logging(const std::string& path, bool console) { + monero_utils::configure_logging(path, console); +} + + +bool PyMoneroUtils::is_valid_address(const std::string& address, monero_network_type network_type) { + return monero_utils::is_valid_address(address, network_type); +} + +bool PyMoneroUtils::is_valid_public_view_key(const std::string& public_view_key) { + return is_hex_64(public_view_key); +} + +bool PyMoneroUtils::is_valid_public_spend_key(const std::string& public_spend_key) { + return is_hex_64(public_spend_key); +} + +bool PyMoneroUtils::is_valid_private_view_key(const std::string& private_view_key) { + return monero_utils::is_valid_private_view_key(private_view_key); +} + +bool PyMoneroUtils::is_valid_private_spend_key(const std::string& private_spend_key) { + return monero_utils::is_valid_private_spend_key(private_spend_key); +} + +bool PyMoneroUtils::is_valid_payment_id(const std::string& payment_id) { + return payment_id.size() == 16 || payment_id.size() == 64; +} + +bool PyMoneroUtils::is_valid_mnemonic(const std::string& mnemonic) { + try { + validate_mnemonic(mnemonic); + return true; + } + catch (...) { + return false; + } +} + +void PyMoneroUtils::validate_address(const std::string& address, monero_network_type network_type) { + monero_utils::validate_address(address, network_type); +} + +void PyMoneroUtils::validate_public_view_key(const std::string& public_view_key) { + if(!is_hex_64(public_view_key)) throw std::runtime_error("Invalid public view key"); +} + +void PyMoneroUtils::validate_public_spend_key(const std::string& public_spend_key) { + if(!is_hex_64(public_spend_key)) throw std::runtime_error("Invalid public view key"); +} + +void PyMoneroUtils::validate_private_view_key(const std::string& private_view_key) { + monero_utils::validate_private_view_key(private_view_key); +} + +void PyMoneroUtils::validate_private_spend_key(const std::string& private_spend_key) { + monero_utils::validate_private_spend_key(private_spend_key); +} + +void PyMoneroUtils::validate_payment_id(const std::string& payment_id) { + if (!is_valid_payment_id(payment_id)) throw std::runtime_error("Invalid payment id"); +} + +void PyMoneroUtils::validate_mnemonic(const std::string& mnemonic) { + if (mnemonic.empty()) throw std::runtime_error("Mnemonic phrase is empty"); + + size_t count = 0; + bool in_word = false; + + for (char c : mnemonic) { + if (std::isspace(c)) { + if (in_word) { + ++count; + in_word = false; + } + } else { + in_word = true; + } + } + + if (in_word) ++count; + + if (count != 25) throw std::runtime_error("Mnemonic phared words must be 25"); +} + +std::string PyMoneroUtils::json_to_binary(const std::string &json) { + std::string bin; + monero_utils::json_to_binary(json, bin); + return bin; +} + +std::string PyMoneroUtils::dict_to_binary(const py::dict &dictionary) { + py::object JSON = py::module_::import("json"); + py::object JSON_DUMPS = JSON.attr("dumps"); + + py::object result_py = JSON_DUMPS(dictionary); + std::string json = result_py.cast(); + + return json_to_binary(json); +} + +py::dict PyMoneroUtils::binary_to_dict(const std::string& bin) { + py::object JSON = py::module_::import("json"); + py::object JSON_LOADS = JSON.attr("loads"); + + std::string json = binary_to_json(bin); + + py::object result_py = JSON_LOADS(json); + py::dict result = result_py.cast(); + + return result; +} + +std::string PyMoneroUtils::binary_to_json(const std::string &bin) { + std::string json; + monero_utils::binary_to_json(bin, json); + return json; +} + +void PyMoneroUtils::binary_blocks_to_json(const std::string &bin, std::string &json) { + monero_utils::binary_blocks_to_json(bin, json); +} + +void PyMoneroUtils::binary_blocks_to_property_tree(const std::string &bin, boost::property_tree::ptree &node) { + std::string response_json; + monero_utils::binary_blocks_to_json(bin, response_json); + std::istringstream iss = response_json.empty() ? std::istringstream() : std::istringstream(response_json); + boost::property_tree::read_json(iss, node); +} + +bool PyMoneroUtils::is_valid_language(const std::string& language) { + return monero_utils::is_valid_language(language); +} + +std::vector> PyMoneroUtils::get_blocks_from_txs(std::vector> txs) { + return monero_utils::get_blocks_from_txs(txs); +} + +std::vector> PyMoneroUtils::get_blocks_from_transfers(std::vector> transfers) { + return monero_utils::get_blocks_from_transfers(transfers); +} + +std::vector> PyMoneroUtils::get_blocks_from_outputs(std::vector> outputs) { + return monero_utils::get_blocks_from_outputs(outputs); +} + +std::string PyMoneroUtils::get_payment_uri(const monero_tx_config& config) { + // validate config + std::vector> destinations = config.get_normalized_destinations(); + if (destinations.size() != 1) throw std::runtime_error("Cannot make URI from supplied parameters: must provide exactly one destination to send funds"); + if (destinations.at(0)->m_address == boost::none) throw std::runtime_error("Cannot make URI from supplied parameters: must provide destination address"); + if (destinations.at(0)->m_amount == boost::none) throw std::runtime_error("Cannot make URI from supplied parameters: must provide destination amount"); + + // prepare wallet2 params + std::string address = destinations.at(0)->m_address.get(); + std::string payment_id = config.m_payment_id == boost::none ? "" : config.m_payment_id.get(); + uint64_t amount = destinations.at(0)->m_amount.get(); + std::string note = config.m_note == boost::none ? "" : config.m_note.get(); + std::string m_recipient_name = config.m_recipient_name == boost::none ? "" : config.m_recipient_name.get(); + + // make uri + std::string uri = make_uri(address, payment_id, amount, note, m_recipient_name); + if (uri.empty()) throw std::runtime_error("Cannot make URI from supplied parameters"); + return uri; +} + +uint64_t PyMoneroUtils::xmr_to_atomic_units(double amount_xmr) { + if (amount_xmr < 0) throw std::invalid_argument("amount_xmr cannot be negative"); + return static_cast(std::round(amount_xmr * XMR_AU_MULTIPLIER)); +} + +double PyMoneroUtils::atomic_units_to_xmr(uint64_t amount_atomic_units) { + return static_cast(amount_atomic_units) / static_cast(XMR_AU_MULTIPLIER); +} + +bool PyMoneroUtils::is_hex_64(const std::string& value) { + if (value.size() != 64) return false; + const std::regex hexRegex("^-?[0-9a-fA-F]+$"); + return std::regex_match(value, hexRegex); +} + +std::string PyMoneroUtils::make_uri(const std::string &address, const std::string &payment_id, uint64_t amount, const std::string &tx_description, const std::string &recipient_name) { + cryptonote::address_parse_info info; + + if(!get_account_address_from_str(info, cryptonote::MAINNET, address)) + { + if(!get_account_address_from_str(info, cryptonote::TESTNET, address)) + { + if(!get_account_address_from_str(info, cryptonote::STAGENET, address)) + { + throw std::runtime_error(std::string("wrong address: ") + address); + } + } + } + + // we want only one payment id + if (info.has_payment_id && !payment_id.empty()) + { + throw std::runtime_error("A single payment id is allowed"); + } + + if (!payment_id.empty()) + { + throw std::runtime_error("Standalone payment id deprecated, use integrated address instead"); + } + + std::string uri = "monero:" + address; + unsigned int n_fields = 0; + + if (!payment_id.empty()) + { + uri += (n_fields++ ? "&" : "?") + std::string("tx_payment_id=") + payment_id; + } + + if (amount > 0) + { + // URI encoded amount is in decimal units, not atomic units + uri += (n_fields++ ? "&" : "?") + std::string("tx_amount=") + cryptonote::print_money(amount); + } + + if (!recipient_name.empty()) + { + uri += (n_fields++ ? "&" : "?") + std::string("recipient_name=") + epee::net_utils::conver_to_url_format(recipient_name); + } + + if (!tx_description.empty()) + { + uri += (n_fields++ ? "&" : "?") + std::string("tx_description=") + epee::net_utils::conver_to_url_format(tx_description); + } + + return uri; +} + +monero_integrated_address PyMoneroUtils::get_integrated_address(monero_network_type network_type, const std::string& standard_address, const std::string& payment_id) { + return monero_utils::get_integrated_address(network_type, standard_address, payment_id); +} + diff --git a/src/cpp/utils/py_monero_utils.h b/src/cpp/utils/py_monero_utils.h new file mode 100644 index 0000000..1017ead --- /dev/null +++ b/src/cpp/utils/py_monero_utils.h @@ -0,0 +1,51 @@ +#pragma once + +#include "common/py_monero_common.h" +#include "utils/monero_utils.h" + + +class PyMoneroUtils { +public: + inline static const uint64_t NUM_MNEMONIC_WORDS = 25; + inline static const uint64_t XMR_AU_MULTIPLIER = 1000000000000ULL; + + PyMoneroUtils() {}; + static std::string get_version() { return std::string("0.0.1"); }; + static int get_ring_size(); + static void set_log_level(int level); + static void configure_logging(const std::string& path, bool console); + static monero_integrated_address get_integrated_address(monero_network_type network_type, const std::string& standard_address, const std::string& payment_id = ""); + static bool is_valid_address(const std::string& address, monero_network_type network_type); + static bool is_valid_public_view_key(const std::string& public_view_key); + static bool is_valid_public_spend_key(const std::string& public_spend_key); + static bool is_valid_private_view_key(const std::string& private_view_key); + static bool is_valid_private_spend_key(const std::string& private_spend_key); + static bool is_valid_payment_id(const std::string& payment_id); + static bool is_valid_mnemonic(const std::string& mnemonic); + static void validate_address(const std::string& address, monero_network_type network_type); + static void validate_public_view_key(const std::string& public_view_key); + static void validate_public_spend_key(const std::string& public_spend_key); + static void validate_private_view_key(const std::string& private_view_key); + static void validate_private_spend_key(const std::string& private_spend_key); + static void validate_payment_id(const std::string& payment_id); + static void validate_mnemonic(const std::string& mnemonic); + + static std::string json_to_binary(const std::string &json); + static std::string dict_to_binary(const py::dict &dictionary); + static py::dict binary_to_dict(const std::string& bin); + static std::string binary_to_json(const std::string &bin); + static void binary_blocks_to_json(const std::string &bin, std::string &json); + static void binary_blocks_to_property_tree(const std::string &bin, boost::property_tree::ptree &node); + static bool is_valid_language(const std::string& language); + static std::vector> get_blocks_from_txs(std::vector> txs); + static std::vector> get_blocks_from_transfers(std::vector> transfers); + static std::vector> get_blocks_from_outputs(std::vector> outputs); + static std::string get_payment_uri(const monero_tx_config& config); + static uint64_t xmr_to_atomic_units(double amount_xmr); + static double atomic_units_to_xmr(uint64_t amount_atomic_units); + +private: + + static bool is_hex_64(const std::string& value); + static std::string make_uri(const std::string &address, const std::string &payment_id, uint64_t amount, const std::string &tx_description, const std::string &recipient_name); +}; diff --git a/src/cpp/wallet/py_monero_wallet.cpp b/src/cpp/wallet/py_monero_wallet.cpp index bae84b2..f449417 100644 --- a/src/cpp/wallet/py_monero_wallet.cpp +++ b/src/cpp/wallet/py_monero_wallet.cpp @@ -9,48 +9,23 @@ void PyMoneroWalletConnectionManagerListener::on_connection_changed(std::shared_ } void PyMoneroWalletListener::on_sync_progress(uint64_t height, uint64_t start_height, uint64_t end_height, double percent_done, const std::string& message) { - PYBIND11_OVERRIDE( - void, - monero_wallet_listener, - on_sync_progress, - height, start_height, end_height, percent_done, message - ); + PYBIND11_OVERRIDE(void, monero_wallet_listener, on_sync_progress, height, start_height, end_height, percent_done, message); } void PyMoneroWalletListener::on_new_block(uint64_t height) { - PYBIND11_OVERRIDE( - void, - monero_wallet_listener, - on_new_block, - height - ); + PYBIND11_OVERRIDE(void, monero_wallet_listener, on_new_block, height); } void PyMoneroWalletListener::on_balances_changed(uint64_t new_balance, uint64_t new_unlocked_balance) { - PYBIND11_OVERRIDE( - void, - monero_wallet_listener, - on_balances_changed, - new_balance, new_unlocked_balance - ); + PYBIND11_OVERRIDE(void, monero_wallet_listener, on_balances_changed, new_balance, new_unlocked_balance); } void PyMoneroWalletListener::on_output_received(const monero_output_wallet& output) { - PYBIND11_OVERRIDE( - void, - monero_wallet_listener, - on_output_received, - output - ); + PYBIND11_OVERRIDE(void, monero_wallet_listener, on_output_received, output); } void PyMoneroWalletListener::on_output_spent(const monero_output_wallet& output) { - PYBIND11_OVERRIDE( - void, - monero_wallet_listener, - on_output_spent, - output - ); + PYBIND11_OVERRIDE(void, monero_wallet_listener, on_output_spent, output); } void PyMoneroWallet::set_connection_manager(const std::shared_ptr &connection_manager) { diff --git a/src/cpp/wallet/py_monero_wallet.h b/src/cpp/wallet/py_monero_wallet.h index 5121183..cf6b69f 100644 --- a/src/cpp/wallet/py_monero_wallet.h +++ b/src/cpp/wallet/py_monero_wallet.h @@ -3,7 +3,7 @@ #include #include #include "py_monero_wallet_model.h" -#include "daemon/py_monero_daemon.h" +#include "wallet/monero_wallet.h" class PyMoneroWalletConnectionManagerListener : public PyMoneroConnectionManagerListener { public: @@ -37,10 +37,12 @@ PYBIND11_MAKE_OPAQUE(std::vector>); PYBIND11_MAKE_OPAQUE(std::vector); PYBIND11_MAKE_OPAQUE(std::vector>); +PYBIND11_MAKE_OPAQUE(std::vector>); class PyMoneroWallet : public monero::monero_wallet { public: - using monero::monero_wallet::monero_wallet; + PyMoneroWallet() { } + virtual ~PyMoneroWallet() = default; virtual void tag_accounts(const std::string& tag, const std::vector& account_indices) { PYBIND11_OVERRIDE_PURE(void, PyMoneroWallet, tag_accounts); diff --git a/src/cpp/wallet/py_monero_wallet_full.h b/src/cpp/wallet/py_monero_wallet_full.h index f409800..d3d377f 100644 --- a/src/cpp/wallet/py_monero_wallet_full.h +++ b/src/cpp/wallet/py_monero_wallet_full.h @@ -1,6 +1,7 @@ #pragma once #include "py_monero_wallet.h" +#include "wallet/monero_wallet_full.h" class PyMoneroWalletFull : public monero::monero_wallet_full { diff --git a/src/cpp/wallet/py_monero_wallet_model.cpp b/src/cpp/wallet/py_monero_wallet_model.cpp index 55255b8..b9ebecf 100644 --- a/src/cpp/wallet/py_monero_wallet_model.cpp +++ b/src/cpp/wallet/py_monero_wallet_model.cpp @@ -1,5 +1,5 @@ #include "py_monero_wallet_model.h" - +#include "utils/monero_utils.h" void PyMoneroTxQuery::decontextualize(const std::shared_ptr &query) { query->m_is_incoming = boost::none; @@ -1229,7 +1229,8 @@ PyMoneroSweepParams::PyMoneroSweepParams(const monero_tx_config& config): m_below_amount(config.m_below_amount), m_get_tx_key(true), m_get_tx_hex(true), - m_get_tx_metadata(true) { } + m_get_tx_metadata(true) { +} rapidjson::Value PyMoneroSweepParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); @@ -1304,10 +1305,12 @@ rapidjson::Value PyMoneroImportExportKeyImagesParams::to_rapidjson_val(rapidjson } PyMoneroCreateOpenWalletParams::PyMoneroCreateOpenWalletParams(const boost::optional& filename, const boost::optional &password): - m_filename(filename), m_password(password), m_autosave_current(false) { } + m_filename(filename), m_password(password), m_autosave_current(false) { +} PyMoneroCreateOpenWalletParams::PyMoneroCreateOpenWalletParams(const boost::optional& filename, const boost::optional &password, const boost::optional &language): - m_filename(filename), m_password(password), m_language(language), m_autosave_current(false) { } + m_filename(filename), m_password(password), m_language(language), m_autosave_current(false) { +} PyMoneroCreateOpenWalletParams::PyMoneroCreateOpenWalletParams(const boost::optional& filename, const boost::optional &password, const boost::optional &seed, const boost::optional &seed_offset, const boost::optional &restore_height, const boost::optional &language, const boost::optional &autosave_current, const boost::optional &enable_multisig_experimental): m_filename(filename), @@ -1317,7 +1320,8 @@ PyMoneroCreateOpenWalletParams::PyMoneroCreateOpenWalletParams(const boost::opti m_restore_height(restore_height), m_language(language), m_autosave_current(autosave_current), - m_enable_multisig_experimental(enable_multisig_experimental) { } + m_enable_multisig_experimental(enable_multisig_experimental) { +} PyMoneroCreateOpenWalletParams::PyMoneroCreateOpenWalletParams(const boost::optional& filename, const boost::optional &password, const boost::optional &address, const boost::optional &view_key, const boost::optional &spend_key, const boost::optional &restore_height, const boost::optional &autosave_current): m_filename(filename), @@ -1326,7 +1330,8 @@ PyMoneroCreateOpenWalletParams::PyMoneroCreateOpenWalletParams(const boost::opti m_view_key(view_key), m_spend_key(spend_key), m_restore_height(restore_height), - m_autosave_current(autosave_current) { } + m_autosave_current(autosave_current) { +} rapidjson::Value PyMoneroCreateOpenWalletParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); @@ -1347,19 +1352,24 @@ rapidjson::Value PyMoneroCreateOpenWalletParams::to_rapidjson_val(rapidjson::Doc } PyMoneroReserveProofParams::PyMoneroReserveProofParams(const std::string &message, bool all): - m_all(all), m_message(message) { } + m_all(all), m_message(message) { +} PyMoneroReserveProofParams::PyMoneroReserveProofParams(const std::string &address, const std::string &message, const std::string &signature): - m_address(address), m_message(message), m_signature(signature) { } + m_address(address), m_message(message), m_signature(signature) { +} PyMoneroReserveProofParams::PyMoneroReserveProofParams(const std::string &tx_hash, const std::string &address, const std::string &message, const std::string &signature): - m_tx_hash(tx_hash), m_address(address), m_message(message), m_signature(signature) { } + m_tx_hash(tx_hash), m_address(address), m_message(message), m_signature(signature) { +} PyMoneroReserveProofParams::PyMoneroReserveProofParams(const std::string &tx_hash, const std::string &message): - m_tx_hash(tx_hash), m_message(message) { } + m_tx_hash(tx_hash), m_message(message) { +} PyMoneroReserveProofParams::PyMoneroReserveProofParams(uint32_t account_index, uint64_t amount, const std::string &message): - m_account_index(account_index), m_amount(amount), m_message(message) { } + m_account_index(account_index), m_amount(amount), m_message(message) { +} rapidjson::Value PyMoneroReserveProofParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); diff --git a/src/cpp/wallet/py_monero_wallet_model.h b/src/cpp/wallet/py_monero_wallet_model.h index 7d13c87..fae179d 100644 --- a/src/cpp/wallet/py_monero_wallet_model.h +++ b/src/cpp/wallet/py_monero_wallet_model.h @@ -1,6 +1,7 @@ #pragma once #include "daemon/py_monero_daemon_model.h" +#include "wallet/monero_wallet_model.h" enum PyMoneroAddressType : uint8_t { PRIMARY_ADDRESS = 0, @@ -169,11 +170,7 @@ class PyMoneroTransfer : public monero_transfer { using monero_transfer::monero_transfer; boost::optional is_incoming() const override { - PYBIND11_OVERRIDE_PURE( - boost::optional, - monero_transfer, - is_incoming - ); + PYBIND11_OVERRIDE_PURE(boost::optional, monero_transfer, is_incoming); } }; diff --git a/src/cpp/wallet/py_monero_wallet_rpc.cpp b/src/cpp/wallet/py_monero_wallet_rpc.cpp index 3e1f13e..7513aa0 100644 --- a/src/cpp/wallet/py_monero_wallet_rpc.cpp +++ b/src/cpp/wallet/py_monero_wallet_rpc.cpp @@ -1,4 +1,5 @@ #include "py_monero_wallet_rpc.h" +#include "utils/monero_utils.h" PyMoneroWalletPoller::~PyMoneroWalletPoller() { @@ -779,7 +780,7 @@ std::vector PyMoneroWalletRpc::get_subaddresses(const uint32_ // cache addresses auto it = m_address_cache.find(account_idx); if (it == m_address_cache.end()) { - m_address_cache[account_idx] = serializable_unordered_map(); + m_address_cache[account_idx] = std::unordered_map(); } for (const auto& subaddress : subaddresses) { diff --git a/src/cpp/wallet/py_monero_wallet_rpc.h b/src/cpp/wallet/py_monero_wallet_rpc.h index ec978cb..2c668cd 100644 --- a/src/cpp/wallet/py_monero_wallet_rpc.h +++ b/src/cpp/wallet/py_monero_wallet_rpc.h @@ -180,7 +180,7 @@ class PyMoneroWalletRpc : public PyMoneroWallet { std::shared_ptr m_poller; mutable boost::recursive_mutex m_sync_mutex; - mutable serializable_unordered_map> m_address_cache; + mutable std::unordered_map> m_address_cache; PyMoneroWalletRpc* create_wallet_random(const std::shared_ptr &conf); PyMoneroWalletRpc* create_wallet_from_seed(const std::shared_ptr &conf);