diff --git a/src/cpp/common/py_monero_common.cpp b/src/cpp/common/py_monero_common.cpp index f2a5cb1..2118455 100644 --- a/src/cpp/common/py_monero_common.cpp +++ b/src/cpp/common/py_monero_common.cpp @@ -34,7 +34,7 @@ void PyThreadPoller::set_is_polling(bool is_polling) { } else { if (m_poll_loop_running) { m_poll_cv.notify_one(); - // TODO: in emscripten, m_sync_cv.notify_one() returns without waiting, so sleep; bug in emscripten upstream llvm? + // TODO: in emscripten, m_poll_cv.notify_one() returns without waiting, so sleep; bug in emscripten upstream llvm? std::this_thread::sleep_for(std::chrono::milliseconds(1)); if (m_thread.joinable()) m_thread.join(); } @@ -579,509 +579,3 @@ bool PyMoneroRpcConnection::check_connection(const boost::optional& timeout return is_online_before != m_is_online || is_authenticated_before != m_is_authenticated; } - -void PyMoneroConnectionManager::add_listener(const std::shared_ptr &listener) { - boost::lock_guard lock(m_listeners_mutex); - m_listeners.push_back(listener); -} - -PyMoneroConnectionManager::~PyMoneroConnectionManager() { - MTRACE("~PyMoneroConnectionManager()"); - reset(); -} - -void PyMoneroConnectionManager::remove_listener(const std::shared_ptr &listener) { - boost::lock_guard lock(m_listeners_mutex); - // find and remove listener - 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() { - // remove all listeners - boost::lock_guard lock(m_listeners_mutex); - m_listeners.clear(); -} - -std::vector> PyMoneroConnectionManager::get_listeners() const { - return m_listeners; -} - -std::vector> PyMoneroConnectionManager::get_connections() const { - boost::lock_guard lock(m_connections_mutex); - // sort connections by priority - std::vector> sorted_connections(m_connections); - std::sort(sorted_connections.begin(), sorted_connections.end(), [this](const std::shared_ptr& c1, const std::shared_ptr& c2) { - return PyMoneroRpcConnection::before(c1, c2, m_current_connection); - }); - return sorted_connections; -} - -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; - } - - // connection not found - return nullptr; -} - -void PyMoneroConnectionManager::add_connection(const std::shared_ptr& connection) { - if (connection->m_uri == boost::none) throw std::runtime_error("Invalid connection uri"); - boost::lock_guard lock(m_connections_mutex); - // check for duplicates - 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()); - } - - // add connection - m_connections.push_back(connection); -} - -void PyMoneroConnectionManager::add_connection(const std::string &uri) { - // add new connection by 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"); - - // remove connection - 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) { - // remove also from current connection - m_current_connection = nullptr; - on_connection_changed(m_current_connection); - } -} - -void PyMoneroConnectionManager::set_connection(const std::shared_ptr& connection) { - if (connection == m_current_connection) return; - - // check if setting null connection - if (connection == nullptr) { - m_current_connection = nullptr; - on_connection_changed(nullptr); - return; - } - - // must provide uri - if (connection->m_uri == boost::none || connection->m_uri->empty()) throw std::runtime_error("Connection is missing URI"); - - // add or replace connection - boost::lock_guard lock(m_connections_mutex); - - // remove previuous connection - 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()); - - // set connection and notify changes - add_connection(connection); - m_current_connection = connection; - on_connection_changed(connection); -} - -void PyMoneroConnectionManager::set_connection(const std::string& uri) { - if (uri.empty()) { - // remove current connection - set_connection(std::shared_ptr(nullptr)); - return; - } - - // check if connection already exists - auto found = get_connection_by_uri(uri); - - if (found != nullptr) { - // set already found connection - set_connection(found); - } - else { - // create new connection - 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); - return connection != nullptr; -} - -boost::optional 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) { - // check current connection - if (connection->check_connection(m_timeout.load())) connection_changed = true; - std::vector> cons; - cons.push_back(connection); - process_responses(cons); - } - - if (m_auto_switch && !is_connected().value_or(false)) { - // switch to best available connection - std::shared_ptr best_connection = get_best_available_connection(connection); - if (best_connection != nullptr) { - set_connection(best_connection); - return; - } - } - - // notify changes - if (connection_changed) on_connection_changed(connection); -} - -void PyMoneroConnectionManager::set_auto_switch(bool auto_switch) { - m_auto_switch = auto_switch; -} - -void PyMoneroConnectionManager::stop_polling() { - set_is_polling(false); -} - -void PyMoneroConnectionManager::start_polling(const boost::optional& period_ms, const boost::optional& auto_switch, const boost::optional& timeout_ms, const boost::optional& poll_type, const boost::optional>> &excluded_connections) { - // stop polling - stop_polling(); - - // apply defaults - uint64_t poll_period_ms = period_ms == boost::none ? DEFAULT_POLL_PERIOD : period_ms.get(); - set_period_in_ms(poll_period_ms); - if (auto_switch != boost::none) set_auto_switch(auto_switch.get()); - if (timeout_ms != boost::none) set_timeout(timeout_ms.get()); - m_excluded_connections.clear(); - - // set poll type - m_poll_type = poll_type == boost::none ? PyMoneroConnectionPollType::PRIORITIZED : poll_type.get(); - if (excluded_connections != boost::none) { - m_excluded_connections.insert(excluded_connections.get().begin(), excluded_connections.get().end()); - } - - // start polling - set_is_polling(true); -} - -std::shared_ptr PyMoneroConnectionManager::get_best_available_connection(const std::set>& excluded_connections) { - // try connections within each ascending priority - auto cons = get_connections_in_ascending_priority(); - - for (const auto& prioritized_connections : cons) { - try { - // check connections in parallel - boost::asio::thread_pool pool(4); - - boost::mutex mtx; - boost::condition_variable cv; - std::queue> completed; - std::atomic remaining{0}; - - for (const auto& connection : prioritized_connections) { - if (!connection) throw std::runtime_error("connection is nullptr"); - if (excluded_connections.count(connection)) continue; - - remaining++; - - boost::asio::post(pool, [&, connection]() { - connection->check_connection(m_timeout.load()); - - { - boost::lock_guard lock(mtx); - completed.push(connection); - } - cv.notify_one(); - }); - } - - // get connection by completion order - while (remaining > 0) { - boost::unique_lock lock(mtx); - - cv.wait(lock, [&]() { return !completed.empty(); }); - - auto connection = completed.front(); - completed.pop(); - remaining--; - - lock.unlock(); - - // use first available connection - if (connection->is_connected().value_or(false)) { - pool.join(); - return connection; - } - } - - pool.join(); - - } catch (const std::exception& e) { - throw PyMoneroError(std::string("Connection check error: ") + e.what()); - } - } - - return nullptr; -} - -std::shared_ptr PyMoneroConnectionManager::get_best_available_connection(const 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() { - // clear connections - m_connections.clear(); - m_excluded_connections.clear(); - - if (m_current_connection != nullptr) { - // clear current connection - 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(const std::shared_ptr& connection) { - boost::lock_guard lock(m_listeners_mutex); - // notify connection change to listeners - 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); - - // build connection priorities map - std::map>> connection_priorities; - for (const auto& connection : m_connections) { - int priority = connection->m_priority; - connection_priorities[priority].push_back(connection); - } - - // build prioritized connections vector - 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()) { - // move priority 0 to end - auto zero_priority_group = *it; - prioritized_connections.erase(it); - prioritized_connections.push_back(zero_priority_group); - } - } - - return prioritized_connections; -} - -void PyMoneroConnectionManager::poll() { - // do polling - switch (m_poll_type) { - case PyMoneroConnectionPollType::CURRENT: - check_connection(); - break; - case PyMoneroConnectionPollType::ALL: - check_connections(); - break; - case PyMoneroConnectionPollType::UNDEFINED: - case PyMoneroConnectionPollType::PRIORITIZED: - check_prioritized_connections(); - break; - } -} - -bool PyMoneroConnectionManager::check_connections(const std::vector>& connections, const std::set>& excluded_connections) { - boost::lock_guard connections_lock(m_connections_mutex); - try { - // start checking connections in parallel - boost::asio::thread_pool pool(4); - boost::mutex result_mutex; - boost::condition_variable result_cv; - std::vector> completed; - - // submit tasks - int num_tasks = 0; - for (const auto& connection : connections) { - if (excluded_connections.count(connection)) continue; - - num_tasks++; - - boost::asio::post(pool, [this, connection, &result_mutex, &result_cv, &completed]() { - bool change = connection->check_connection(m_timeout.load()); - - if (change && connection == get_connection()) { - on_connection_changed(connection); - } - - { - boost::lock_guard lock(result_mutex); - completed.push_back(connection); - } - - result_cv.notify_one(); - }); - } - - bool has_connection = false; - size_t received = 0; - - // wait for responses - while (received < num_tasks) { - boost::unique_lock lock(result_mutex); - result_cv.wait(lock, [&]() { return completed.size() > received; }); - - auto connection = completed[received++]; - lock.unlock(); - - if (connection->is_connected().value_or(false) && !has_connection) { - has_connection = true; - if (!is_connected().value_or(false) && m_auto_switch) set_connection(connection); - } - } - - pool.join(); - - // process responses - process_responses(connections); - - return has_connection; - } - catch (const std::exception& e) { - throw PyMoneroError(std::string("check_connections failed: ") + e.what()); - } -} - -void PyMoneroConnectionManager::check_prioritized_connections() { - // check connections in ascending priority - for (const auto &prioritized_connections : get_connections_in_ascending_priority()) { - check_connections(prioritized_connections, m_excluded_connections); - } -} - -std::shared_ptr PyMoneroConnectionManager::process_responses(const std::vector>& responses) { - // add new connections - for (const auto& conn : responses) { - if (m_response_times.find(conn) == m_response_times.end()) { - m_response_times[conn] = {}; - } - } - - // insert response times or null - 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) { - // remove old response times - times.pop_back(); - } - } - - // update best connection based on responses and priority - return update_best_connection_in_priority(); -} - -std::shared_ptr PyMoneroConnectionManager::get_best_connection_from_prioritized_responses(const std::vector>& responses) { - // get best response - std::shared_ptr best_response = nullptr; - for (const auto& conn : responses) { - if (conn->is_connected().value_or(false)) { - if (!best_response || conn->m_response_time < best_response->m_response_time) { - best_response = conn; - } - } - } - - // no update if no responses - if (!best_response) return nullptr; - - // use best response if disconnected - auto best_connection = get_connection(); - if (!best_connection || !best_connection->is_connected().value_or(false)) { - return best_response; - } - - // use best response if different priority (assumes being called in descending priority) - if (best_response->m_priority != best_connection->m_priority) { - return best_response; - } - - // keep best connection if not enough data - if (m_response_times.find(best_connection) == m_response_times.end()) { - return best_connection; - } - - // check if a connection is consistently better - 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() || it_curr->second.size() < MIN_BETTER_RESPONSES) continue; - - bool 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()) { - better = false; - break; - } - } - - if (better) best_connection = conn; - } - - return best_connection; -} - -std::shared_ptr PyMoneroConnectionManager::update_best_connection_in_priority() { - if (!m_auto_switch) return nullptr; - - for (const auto& prioritized_connections : get_connections_in_ascending_priority()) { - // get best connection and update current - auto best_conn = get_best_connection_from_prioritized_responses(prioritized_connections); - if (best_conn != nullptr) { - set_connection(best_conn); - return best_conn; - } - } - - // no connection updated - return nullptr; -} diff --git a/src/cpp/common/py_monero_common.h b/src/cpp/common/py_monero_common.h index c200a50..05e6a34 100644 --- a/src/cpp/common/py_monero_common.h +++ b/src/cpp/common/py_monero_common.h @@ -119,16 +119,6 @@ enum PyMoneroConnectionType : uint8_t { I2P }; -/* - * Specify behavior when polling. -*/ -enum PyMoneroConnectionPollType : uint8_t { - PRIORITIZED = 0, - CURRENT, - ALL, - UNDEFINED -}; - class PyMoneroConnectionPriorityComparator { public: @@ -563,230 +553,3 @@ class PyMoneroRpcConnection : public monero::monero_rpc_connection { } }; - -struct monero_connection_manager_listener { -public: - virtual void on_connection_changed(const 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(const std::shared_ptr &connection) override { - PYBIND11_OVERRIDE_PURE(void, monero_connection_manager_listener, on_connection_changed, connection); - } -}; - -class PyMoneroConnectionManager : public PyThreadPoller { -public: - - ~PyMoneroConnectionManager(); - PyMoneroConnectionManager() { } - - /** - * Add a listener to receive notifications when the connection changes. - * - * @param listener the listener to add - */ - void add_listener(const std::shared_ptr &listener); - - /** - * Remove a listener. - * - * @param listener the listener to remove - */ - void remove_listener(const std::shared_ptr &listener); - - /** - * Remove all listeners. - */ - void remove_listeners(); - - /** - * Get all listeners. - * - * @return all listeners - */ - std::vector> get_listeners() const; - - /** - * Add a connection. The connection may have an elevated priority for this manager to use. - * - * @param connection the connection to add - */ - void add_connection(const std::shared_ptr& connection); - - /** - * Add connection URI. - * - * @param uri uri of the connection to add - */ - void add_connection(const std::string &uri); - - /** - * Remove a connection. - * - * @param uri uri of the connection to remove - */ - void remove_connection(const std::string &uri); - - /** - * Set the current connection without changing the credentials. - * Replace connection if its URI was previously added. Otherwise add new connection. - * Notify if current connection changes. - * Does not check the connection. - * - * @param connection is the connection to make current - */ - void set_connection(const std::shared_ptr& connection); - - /** - * Set the current connection without changing the credentials. - * Add new connection if URI not previously added. - * Notify if current connection changes. - * Does not check the connection. - * - * @param uri identifies the connection to make current - */ - void set_connection(const std::string& uri); - - /** - * Indicates if this manager has a connection with the given URI. - * - * @param uri URI of the connection to check - * @return true if this manager has a connection with the given URI, false otherwise - */ - bool has_connection(const std::string& uri); - - /** - * Get the current connection. - * - * @return the current connection or null if no connection set - */ - std::shared_ptr get_connection() const { return m_current_connection; } - - /** - * Get a connection by URI. - * - * @param uri URI of the connection to get - * @return the connection with the URI or null if no connection with the URI exists - */ - std::shared_ptr get_connection_by_uri(const std::string &uri); - - /** - * Get all connections in order of current connection (if applicable), online status, priority, and name. - * - * @return the list of sorted connections - */ - std::vector> get_connections() const; - - /** - * Get if auto switch is enabled or disabled. - * - * @return true if auto switch enabled, false otherwise - */ - 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; } - - /** - * Indicates if the connection manager is connected to a node. - * - * @return true if the current connection is set, online, and not unauthenticated, none if unknown, false otherwise - */ - boost::optional is_connected() const; - - /** - * Check the current connection. If disconnected and auto switch enabled, switches to best available connection. - */ - void check_connection(); - - /** - * Automatically switch to the best available connection as connections are polled, based on priority, response time, and consistency. - * - * @param auto_switch specifies if the connection should auto switch to a better connection - */ - void set_auto_switch(bool auto_switch); - - /** - * Stop polling connections. - */ - void stop_polling(); - void start_polling(const boost::optional& period_ms, const boost::optional& auto_switch, const boost::optional& timeout_ms, const boost::optional& poll_type, const boost::optional>> &excluded_connections); - - /** - * Collect connectable peers of the managed connections. - * - * @return connectable peers - */ - std::vector> get_peer_connections() const { throw std::runtime_error("PyMoneroConnectionManager::get_peer_connections(): not implemented"); } - - /** - * Get the best available connection in order of priority then response time. - * - * @param excluded_connections to be excluded from consideration (optional) - * @return the best available connection in order of priority then response time, null if no connections available - */ - std::shared_ptr get_best_available_connection(const std::set>& excluded_connections = {}); - - /** - * Get the best available connection in order of priority then response time. - * - * @param excluded_connection to be excluded from consideration - * @return the best available connection in order of priority then response time, null if no connections available - */ - std::shared_ptr get_best_available_connection(const std::shared_ptr& excluded_connection); - - /** - * Check all managed connections. - */ - void check_connections(); - - /** - * Disconnect from the current connection. - */ - void disconnect(); - - /** - * Remove all connections. - */ - void clear(); - - /** - * Reset to default state. - */ - void reset(); - void poll() override; - -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; - - // instance variables - 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; - std::set> m_excluded_connections; - - std::atomic m_auto_switch = true; - std::atomic m_timeout = 5000; - - std::map, std::vector>> m_response_times; - - PyMoneroConnectionPollType m_poll_type = PyMoneroConnectionPollType::UNDEFINED; - - void on_connection_changed(const std::shared_ptr& connection); - std::vector>> get_connections_in_ascending_priority(); - bool check_connections(const std::vector>& connections, const std::set>& excluded_connections = {}); - void check_prioritized_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/py_monero.cpp b/src/cpp/py_monero.cpp index 072fe2f..cb7d570 100644 --- a/src/cpp/py_monero.cpp +++ b/src/cpp/py_monero.cpp @@ -40,8 +40,6 @@ PYBIND11_MODULE(monero, m) { 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"); @@ -192,13 +190,6 @@ PYBIND11_MODULE(monero, m) { .value("CONFIRMED", PyMoneroKeyImageSpentStatus::CONFIRMED) .value("TX_POOL", PyMoneroKeyImageSpentStatus::TX_POOL); - // enum monero_connection_pool_type - py::enum_(m, "MoneroConnectionPollType") - .value("PRIORITIZED", PyMoneroConnectionPollType::PRIORITIZED) - .value("CURRENT", PyMoneroConnectionPollType::CURRENT) - .value("ALL", PyMoneroConnectionPollType::ALL) - .value("UNDEFINED", PyMoneroConnectionPollType::UNDEFINED); - // enum address_type py::enum_(m, "MoneroAddressType") .value("PRIMARY_ADDRESS", PyMoneroAddressType::PRIMARY_ADDRESS) @@ -292,7 +283,7 @@ PYBIND11_MODULE(monero, m) { .def_readwrite("is_release", &monero::monero_version::m_is_release); // monero_connection_priority_comparator - py::class_>(m, "MoneroConnectionProriotyComparator") + py::class_>(m, "MoneroConnectionPriorityComparator") .def_static("compare", [](int p1, int p2) { MONERO_CATCH_AND_RETHROW(PyMoneroConnectionPriorityComparator::compare(p1, p2)); }, py::arg("p1"), py::arg("p2")); @@ -376,104 +367,6 @@ PYBIND11_MODULE(monero, m) { MONERO_CATCH_AND_RETHROW(self.send_binary_request(method, parameters)); }, py::arg("method"), py::arg("parameters") = py::none()); - // monero_connection_manager_listener - py_monero_connection_manager_listener - .def(py::init<>()) - .def("on_connection_changed", [](monero_connection_manager_listener& self, const std::shared_ptr &connection) { - MONERO_CATCH_AND_RETHROW(self.on_connection_changed(connection)); - }, py::arg("connection")); - - // monero_connection_manager - py_monero_connection_manager - .def(py::init<>()) - .def("add_listener", [](PyMoneroConnectionManager& self, const std::shared_ptr &listener) { - MONERO_CATCH_AND_RETHROW(self.add_listener(listener)); - }, py::arg("listener"), py::call_guard()) - .def("remove_listener", [](PyMoneroConnectionManager& self, const std::shared_ptr &listener) { - MONERO_CATCH_AND_RETHROW(self.remove_listener(listener)); - }, py::arg("listener"), py::call_guard()) - .def("remove_listeners", [](PyMoneroConnectionManager& self) { - MONERO_CATCH_AND_RETHROW(self.remove_listeners()); - }, py::call_guard()) - .def("get_listeners", [](const PyMoneroConnectionManager& self) { - MONERO_CATCH_AND_RETHROW(self.get_listeners()); - }, py::call_guard()) - .def("get_connection_by_uri", [](PyMoneroConnectionManager& self, const std::string& uri) { - MONERO_CATCH_AND_RETHROW(self.get_connection_by_uri(uri)); - }, py::arg("uri"), py::call_guard()) - .def("add_connection", [](PyMoneroConnectionManager& self, const std::shared_ptr &connection) { - MONERO_CATCH_AND_RETHROW(self.add_connection(connection)); - }, py::arg("connection"), py::call_guard()) - .def("add_connection", [](PyMoneroConnectionManager& self, const std::string &uri) { - MONERO_CATCH_AND_RETHROW(self.add_connection(uri)); - }, py::arg("uri"), py::call_guard()) - .def("remove_connection", [](PyMoneroConnectionManager& self, const std::string &uri) { - MONERO_CATCH_AND_RETHROW(self.remove_connection(uri)); - }, py::arg("uri"), py::call_guard()) - .def("set_connection", [](PyMoneroConnectionManager& self, std::shared_ptr &connection) { - MONERO_CATCH_AND_RETHROW(self.set_connection(connection)); - }, py::arg("connection"), py::call_guard()) - .def("set_connection", [](PyMoneroConnectionManager& self, const std::string &uri) { - MONERO_CATCH_AND_RETHROW(self.set_connection(uri)); - }, py::arg("uri"), py::call_guard()) - .def("get_connection", [](const PyMoneroConnectionManager& self) { - MONERO_CATCH_AND_RETHROW(self.get_connection()); - }, py::call_guard()) - .def("has_connection", [](PyMoneroConnectionManager& self, const std::string &uri) { - MONERO_CATCH_AND_RETHROW(self.has_connection(uri)); - }, py::arg("uri"), py::call_guard()) - .def("get_connections", [](const PyMoneroConnectionManager& self) { - MONERO_CATCH_AND_RETHROW(self.get_connections()); - }, py::call_guard()) - .def("is_connected", [](const PyMoneroConnectionManager& self) { - MONERO_CATCH_AND_RETHROW(self.is_connected()); - }, py::call_guard()) - .def("check_connection", [](PyMoneroConnectionManager& self) { - MONERO_CATCH_AND_RETHROW(self.check_connection()); - }, py::call_guard()) - .def("start_polling", [](PyMoneroConnectionManager& self, const boost::optional& period_ms, const boost::optional& auto_switch, const boost::optional& timeout_ms, const boost::optional& poll_type, const boost::optional>>& excluded_connections) { - MONERO_CATCH_AND_RETHROW(self.start_polling(period_ms, auto_switch, timeout_ms, poll_type, excluded_connections)); - }, py::arg("period_ms") = py::none(), py::arg("auto_switch") = py::none(), py::arg("timeout_ms") = py::none(), py::arg("poll_type") = py::none(), py::arg("excluded_connections") = py::none(), py::call_guard()) - .def("stop_polling", [](PyMoneroConnectionManager& self) { - MONERO_CATCH_AND_RETHROW(self.stop_polling()); - }, py::call_guard()) - .def("set_auto_switch", [](PyMoneroConnectionManager& self, bool auto_switch) { - MONERO_CATCH_AND_RETHROW(self.set_auto_switch(auto_switch)); - }, py::arg("auto_switch"), py::call_guard()) - .def("get_auto_switch", [](const PyMoneroConnectionManager& self) { - MONERO_CATCH_AND_RETHROW(self.get_auto_switch()); - }, py::call_guard()) - .def("set_timeout", [](PyMoneroConnectionManager& self, uint64_t timeout_ms) { - MONERO_CATCH_AND_RETHROW(self.set_timeout(timeout_ms)); - }, py::arg("timeout_ms"), py::call_guard()) - .def("get_timeout", [](const PyMoneroConnectionManager& self) { - MONERO_CATCH_AND_RETHROW(self.get_timeout()); - }, py::call_guard()) - .def("get_peer_connections", [](const PyMoneroConnectionManager& self) { - MONERO_CATCH_AND_RETHROW(self.get_peer_connections()); - }, py::call_guard()) - .def("disconnect", [](PyMoneroConnectionManager& self) { - MONERO_CATCH_AND_RETHROW(self.disconnect()); - }, py::call_guard()) - .def("clear", [](PyMoneroConnectionManager& self) { - MONERO_CATCH_AND_RETHROW(self.clear()); - }, py::call_guard()) - .def("reset", [](PyMoneroConnectionManager& self) { - MONERO_CATCH_AND_RETHROW(self.reset()); - }, py::call_guard()) - .def("get_best_available_connection", [](PyMoneroConnectionManager& self, const std::set>& excluded_connections) { - MONERO_CATCH_AND_RETHROW(self.get_best_available_connection(excluded_connections)); - }, py::arg("excluded_connections"), py::call_guard()) - .def("get_best_available_connection", [](PyMoneroConnectionManager& self, std::shared_ptr& excluded_connection) { - MONERO_CATCH_AND_RETHROW(self.get_best_available_connection(excluded_connection)); - }, py::arg("excluded_connection"), py::call_guard()) - .def("get_best_available_connection", [](PyMoneroConnectionManager& self) { - MONERO_CATCH_AND_RETHROW(self.get_best_available_connection()); - }, py::call_guard()) - .def("check_connections", [](PyMoneroConnectionManager& self) { - MONERO_CATCH_AND_RETHROW(self.check_connections()); - }, py::call_guard()); - // monero_block_header py_monero_block_header .def(py::init<>()) @@ -854,7 +747,6 @@ PYBIND11_MODULE(monero, m) { .def_readwrite("account_lookahead", &PyMoneroWalletConfig::m_account_lookahead) .def_readwrite("subaddress_lookahead", &PyMoneroWalletConfig::m_subaddress_lookahead) .def_readwrite("is_multisig", &PyMoneroWalletConfig::m_is_multisig) - .def_readwrite("connection_manager", &PyMoneroWalletConfig::m_connection_manager) .def("copy", [](PyMoneroWalletConfig& self) { MONERO_CATCH_AND_RETHROW(self.copy()); }); @@ -1545,12 +1437,6 @@ PYBIND11_MODULE(monero, m) { .def("is_view_only", [](PyMoneroWallet& self) { MONERO_CATCH_AND_RETHROW(self.is_view_only()); }, py::call_guard()) - .def("set_connection_manager", [](PyMoneroWallet& self, const std::shared_ptr &connection_manager) { - throw PyMoneroError("MoneroWallet.set_connection_manager(): not supported"); - }, py::arg("connection_manager"), py::call_guard()) - .def("get_connection_manager", [](PyMoneroWallet& self) { - throw PyMoneroError("MoneroWallet.get_connection_manager(): not supported"); - }, py::call_guard()) .def("set_daemon_connection", [](PyMoneroWallet& self, const boost::optional& connection) { MONERO_CATCH_AND_RETHROW(self.set_daemon_connection(connection)); }, py::arg("connection"), py::call_guard()) diff --git a/src/cpp/wallet/py_monero_wallet.cpp b/src/cpp/wallet/py_monero_wallet.cpp index f449417..ae30584 100644 --- a/src/cpp/wallet/py_monero_wallet.cpp +++ b/src/cpp/wallet/py_monero_wallet.cpp @@ -1,12 +1,5 @@ #include "py_monero_wallet.h" -PyMoneroWalletConnectionManagerListener::PyMoneroWalletConnectionManagerListener(monero::monero_wallet* wallet) { - m_wallet = wallet; -} - -void PyMoneroWalletConnectionManagerListener::on_connection_changed(std::shared_ptr &connection) { - if (m_wallet != nullptr) m_wallet->set_daemon_connection(*connection); -} 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); @@ -28,16 +21,6 @@ void PyMoneroWalletListener::on_output_spent(const monero_output_wallet& output) PYBIND11_OVERRIDE(void, monero_wallet_listener, on_output_spent, output); } -void PyMoneroWallet::set_connection_manager(const std::shared_ptr &connection_manager) { - if (m_connection_manager != nullptr) m_connection_manager->remove_listener(m_connection_manager_listener); - m_connection_manager = connection_manager; - if (m_connection_manager == nullptr) return; - if (m_connection_manager_listener == nullptr) m_connection_manager_listener = std::make_shared(this); - connection_manager->add_listener(m_connection_manager_listener); - auto connection = connection_manager->get_connection(); - if (connection) set_daemon_connection(*connection); -} - void PyMoneroWallet::announce_new_block(uint64_t height) { for (const auto &listener : m_listeners) { try { diff --git a/src/cpp/wallet/py_monero_wallet.h b/src/cpp/wallet/py_monero_wallet.h index 85b90e9..e037c2e 100644 --- a/src/cpp/wallet/py_monero_wallet.h +++ b/src/cpp/wallet/py_monero_wallet.h @@ -8,16 +8,6 @@ // TODO sorting is really needed? std::vector> get_and_sort_txs(const monero::monero_wallet& wallet, const monero::monero_tx_query& tx_query); -class PyMoneroWalletConnectionManagerListener : public PyMoneroConnectionManagerListener { -public: - PyMoneroWalletConnectionManagerListener(monero::monero_wallet* wallet); - - void on_connection_changed(std::shared_ptr &connection); - -private: - monero::monero_wallet *m_wallet; -}; - class PyMoneroWalletListener : public monero_wallet_listener { public: @@ -544,8 +534,6 @@ class PyMoneroWallet : public monero::monero_wallet { PYBIND11_OVERRIDE(void, monero_wallet, close, save); } - virtual void set_connection_manager(const std::shared_ptr &connection_manager); - virtual std::shared_ptr get_connection_manager() const { return m_connection_manager; } virtual void announce_new_block(uint64_t height); virtual void announce_sync_progress(uint64_t height, uint64_t start_height, uint64_t end_height, float percent_done, const std::string &message); virtual void announce_balances_changed(uint64_t balance, uint64_t unlocked_balance); @@ -556,7 +544,5 @@ class PyMoneroWallet : public monero::monero_wallet { protected: bool m_is_closed = false; - std::shared_ptr m_connection_manager; - std::shared_ptr m_connection_manager_listener; std::set m_listeners; }; diff --git a/src/cpp/wallet/py_monero_wallet_model.cpp b/src/cpp/wallet/py_monero_wallet_model.cpp index 2a16452..5fbb205 100644 --- a/src/cpp/wallet/py_monero_wallet_model.cpp +++ b/src/cpp/wallet/py_monero_wallet_model.cpp @@ -141,7 +141,6 @@ PyMoneroWalletConfig::PyMoneroWalletConfig(const PyMoneroWalletConfig& config) { m_account_lookahead = config.m_account_lookahead; m_subaddress_lookahead = config.m_subaddress_lookahead; m_is_multisig = config.m_is_multisig; - m_connection_manager = config.m_connection_manager; } PyMoneroKeyImage::PyMoneroKeyImage(const monero::monero_key_image &key_image) { diff --git a/src/cpp/wallet/py_monero_wallet_model.h b/src/cpp/wallet/py_monero_wallet_model.h index 2a3adb7..ee5746b 100644 --- a/src/cpp/wallet/py_monero_wallet_model.h +++ b/src/cpp/wallet/py_monero_wallet_model.h @@ -65,7 +65,6 @@ class PyMoneroTransferQuery : public monero::monero_transfer_query { struct PyMoneroWalletConfig : public monero::monero_wallet_config { public: - boost::optional> m_connection_manager; PyMoneroWalletConfig() { } PyMoneroWalletConfig(const PyMoneroWalletConfig& config); diff --git a/src/cpp/wallet/py_monero_wallet_rpc.cpp b/src/cpp/wallet/py_monero_wallet_rpc.cpp index 833983e..1d37c5a 100644 --- a/src/cpp/wallet/py_monero_wallet_rpc.cpp +++ b/src/cpp/wallet/py_monero_wallet_rpc.cpp @@ -220,11 +220,7 @@ PyMoneroWalletRpc* PyMoneroWalletRpc::open_wallet(const std::shared_ptrsend_json_request("open_wallet", params); clear(); - if (config->m_connection_manager != boost::none) { - if (config->m_server != boost::none) throw std::runtime_error("Wallet can be opened with a server or connection manager but not both"); - set_connection_manager(config->m_connection_manager.get()); - } - else if (config->m_server != boost::none) { + if (config->m_server != boost::none) { set_daemon_connection(config->m_server); } @@ -253,25 +249,12 @@ PyMoneroWalletRpc* PyMoneroWalletRpc::create_wallet(const std::shared_ptrm_account_lookahead != boost::none || config->m_subaddress_lookahead != boost::none) throw std::runtime_error("monero-wallet-rpc does not support creating wallets with subaddress lookahead over rpc"); - if (config->m_connection_manager != boost::none) { - if (config->m_server != boost::none) throw std::runtime_error("Wallet can be opened with a server or connection manager but not both"); - auto cm = config->m_connection_manager.get(); - if (cm != nullptr) { - auto connection = cm->get_connection(); - if (connection) { - config->m_server = *connection; - } - } - } if (config->m_seed != boost::none) create_wallet_from_seed(config); else if (config->m_private_spend_key != boost::none || config->m_primary_address != boost::none) create_wallet_from_keys(config); else create_wallet_random(config); - if (config->m_connection_manager != boost::none) { - set_connection_manager(config->m_connection_manager.get()); - } - else if (config->m_server != boost::none) { + if (config->m_server != boost::none) { set_daemon_connection(config->m_server); } diff --git a/src/python/__init__.pyi b/src/python/__init__.pyi index c0f05d1..be322eb 100644 --- a/src/python/__init__.pyi +++ b/src/python/__init__.pyi @@ -67,10 +67,7 @@ from .monero_block_template import MoneroBlockTemplate from .monero_check import MoneroCheck from .monero_check_reserve import MoneroCheckReserve from .monero_check_tx import MoneroCheckTx -from .monero_connection_manager import MoneroConnectionManager -from .monero_connection_manager_listener import MoneroConnectionManagerListener -from .monero_connection_poll_type import MoneroConnectionPollType -from .monero_connection_priority_comparator import MoneroConnectionProriotyComparator +from .monero_connection_priority_comparator import MoneroConnectionPriorityComparator from .monero_connection_span import MoneroConnectionSpan from .monero_connection_type import MoneroConnectionType from .monero_daemon import MoneroDaemon @@ -152,10 +149,7 @@ __all__ = [ 'MoneroCheck', 'MoneroCheckReserve', 'MoneroCheckTx', - 'MoneroConnectionManager', - 'MoneroConnectionManagerListener', - 'MoneroConnectionPollType', - 'MoneroConnectionProriotyComparator', + 'MoneroConnectionPriorityComparator', 'MoneroConnectionSpan', 'MoneroConnectionType', 'MoneroDaemon', diff --git a/src/python/monero_connection_manager.pyi b/src/python/monero_connection_manager.pyi deleted file mode 100644 index 146e72f..0000000 --- a/src/python/monero_connection_manager.pyi +++ /dev/null @@ -1,222 +0,0 @@ -import typing - -from .monero_rpc_connection import MoneroRpcConnection -from .monero_connection_manager_listener import MoneroConnectionManagerListener -from .monero_connection_poll_type import MoneroConnectionPollType - - -class MoneroConnectionManager: - """ - Manages a collection of prioritized connections to daemon or wallet RPC endpoints. - """ - def __init__(self) -> None: - """Initialize a Monero connection manager.""" - ... - @typing.overload - def add_connection(self, connection: MoneroRpcConnection) -> None: - """ - Add a connection. The connection may have an elevated priority for this manager to use. - - :param MoneroRpcConnection connection: the connection to add - """ - ... - @typing.overload - def add_connection(self, uri: str) -> None: - """ - Add a connection URI. - - :param str uri: uri of the connection to add - """ - ... - def add_listener(self, listener: MoneroConnectionManagerListener) -> None: - """ - Add a listener to receive notifications when the connection changes. - - :param MoneroConnectionManagerListener listener: the listener to add - """ - ... - def check_connection(self) -> None: - """ - Check the current connection. If disconnected and auto switch enabled, switches to best available connection. - """ - ... - def check_connections(self) -> None: - """ - Check all managed connections. - """ - ... - def clear(self) -> None: - """ - Remove all connections. - """ - ... - def disconnect(self) -> None: - """ - Disconnect from the current connection. - """ - ... - def get_auto_switch(self) -> bool: - """ - Get if auto switch is enabled or disabled. - - :return bool: true if auto switch enabled, false otherwise - """ - ... - @typing.overload - def get_best_available_connection(self, excluded_connections: set[MoneroRpcConnection]) -> MoneroRpcConnection: - """ - Get the best available connection in order of priority then response time. - - :param set[MoneroRpcConnection] excluded_connections: connections to be excluded from consideration (optional) - :return MoneroRpcConnection: the best available connection in order of priority then response time, null if no connections available - """ - ... - @typing.overload - def get_best_available_connection(self, excluded_connection: MoneroRpcConnection) -> MoneroRpcConnection: - """ - Get the best available connection in order of priority then response time. - - :param MoneroRpcConnection excluded_connection: connection to be excluded from consideration (optional) - :return MoneroRpcConnection: the best available connection in order of priority then response time, null if no connections available - """ - ... - @typing.overload - def get_best_available_connection(self) -> MoneroRpcConnection: - """ - Get the best available connection in order of priority then response time. - - :return MoneroRpcConnection: the best available connection in order of priority then response time, null if no connections available - """ - ... - def get_connection(self) -> MoneroRpcConnection: - """ - Get the current connection. - """ - ... - def get_connection_by_uri(self, uri: str) -> MoneroRpcConnection: - """ - Get a connection by URI. - - :param str uri: URI of the connection to get - :return MoneroRpcConnection: the connection with the URI or null if no connection with the URI exists - """ - ... - def get_connections(self) -> list[MoneroRpcConnection]: - """ - Get all connections in order of current connection (if applicable), online status, priority, and name. - - :return list[MoneroRpcConnection]: List of RPC connections. - """ - ... - def get_listeners(self) -> list[MoneroConnectionManagerListener]: - """ - Get all listeners. - - :return list[MoneroConnectionManagerListener]: - """ - ... - def get_peer_connections(self) -> list[MoneroRpcConnection]: - """ - Collect connectable peers of the managed connections. - - :return list[MoneroRpcConnection]: connectable peers - """ - ... - def get_timeout(self) -> int: - """ - Get the request timeout. - - :return int: the request timeout before a connection is considered offline - """ - ... - def has_connection(self, uri: str) -> bool: - """ - Indicates if this manager has a connection with the given URI. - - :param str uri: URI of the connection to check - :return bool: true if this manager has a connection with the given URI, false otherwise - """ - ... - def is_connected(self) -> bool | None: - """ - Indicates if the connection manager is connected to a node. - - :return bool: `True` if the current connection is set, online, and not unauthenticated, `None` if unknown, `False` otherwise. - """ - ... - def remove_connection(self, uri: str) -> None: - """ - Remove a connection. - - :param str uri: uri of the connection to remove - """ - ... - def remove_listener(self, listener: MoneroConnectionManagerListener) -> None: - """ - Remove a listener. - - :param MoneroConnectionManagerListener listener: the listener to remove - """ - ... - def remove_listeners(self) -> None: - """ - Remove all listeners. - """ - ... - def reset(self) -> None: - """ - Reset to default state. - """ - ... - def set_auto_switch(self, auto_switch: bool) -> None: - """ - Automatically switch to the best available connection as connections are polled, based on priority, response time, and consistency. - - :param bool auto_switch: specifies if the connection should auto switch to a better connection - """ - ... - @typing.overload - def set_connection(self, connection: MoneroRpcConnection | None) -> None: - """ - Set the current connection. - Replace connection if its URI was previously added. Otherwise add new connection. - Notify if current connection changes. - Does not check the connection. - - :param Optional[MoneroRpcConnection] connection: is the connection to make current - """ - ... - @typing.overload - def set_connection(self, uri: str) -> None: - """ - Set the current connection without changing the credentials. - Add new connection if URI not previously added. - Notify if current connection changes. - Does not check the connection. - - :param str uri: identifies the connection to make current - """ - ... - def set_timeout(self, timeout_ms: int) -> None: - """ - Set the maximum request time before a connection is considered offline. - - :param int timeout_ms: is the timeout before a connection is considered offline - """ - ... - def start_polling(self, period_ms: int | None = None, auto_switch: bool | None = None, timeout_ms: int | None = None, poll_type: MoneroConnectionPollType | None = None, excluded_connections: list[MoneroRpcConnection] | None = None) -> None: - """ - Start polling connections. - - :param Optional[int] period_ms: poll period in milliseconds (default 20s) - :param Optional[bool] auto_switch: specifies to automatically switch to the best connection (default true unless changed) - :param Optional[int] timeout_ms: specifies the timeout to poll a single connection (default 5s unless changed) - :param Optional[MoneroConnectionPollType] poll_type: one of PRIORITIZED (poll connections in order of priority until connected; default), CURRENT (poll current connection), or ALL (poll all connections) - :param Optional[list[MoneroRpcConnection]] excluded_connections: connections excluded from being polled - """ - ... - def stop_polling(self) -> None: - """ - Stop polling connections. - """ - ... diff --git a/src/python/monero_connection_manager_listener.pyi b/src/python/monero_connection_manager_listener.pyi deleted file mode 100644 index 397e3d1..0000000 --- a/src/python/monero_connection_manager_listener.pyi +++ /dev/null @@ -1,18 +0,0 @@ -from typing import Optional -from .monero_rpc_connection import MoneroRpcConnection - - -class MoneroConnectionManagerListener: - """ - Default connection manager listener which takes no action on notifications. - """ - def __init__(self) -> None: - """Initialize a connection manager listener.""" - ... - def on_connection_changed(self, connection: Optional[MoneroRpcConnection]) -> None: - """ - Notified on connection change events. - - :param MoneroRpcConnection connection: the connection manager's current connection - """ - ... \ No newline at end of file diff --git a/src/python/monero_connection_poll_type.pyi b/src/python/monero_connection_poll_type.pyi deleted file mode 100644 index 649b06a..0000000 --- a/src/python/monero_connection_poll_type.pyi +++ /dev/null @@ -1,24 +0,0 @@ -from enum import IntEnum - - -class MoneroConnectionPollType(IntEnum): - """ - Members: - - PRIORITIZED - - CURRENT - - ALL - - UNDEFINED - """ - ALL = 2 - """`2` Poll all connections.""" - CURRENT = 1 - """`1` Poll only current connection.""" - PRIORITIZED = 0 - """`0` Poll only prioritized connections.""" - UNDEFINED = 3 - """`3` Invalid poll type.""" - diff --git a/src/python/monero_connection_priority_comparator.pyi b/src/python/monero_connection_priority_comparator.pyi index 13bb91f..9425bf0 100644 --- a/src/python/monero_connection_priority_comparator.pyi +++ b/src/python/monero_connection_priority_comparator.pyi @@ -1,4 +1,4 @@ -class MoneroConnectionProriotyComparator: +class MoneroConnectionPriorityComparator: @staticmethod def compare(p1: int, p2: int) -> bool: """ diff --git a/src/python/monero_rpc_connection.pyi b/src/python/monero_rpc_connection.pyi index a0a081a..88650c2 100644 --- a/src/python/monero_rpc_connection.pyi +++ b/src/python/monero_rpc_connection.pyi @@ -86,7 +86,7 @@ class MoneroRpcConnection(SerializableStruct): """ Indicates if the connection is authenticated according to the last call to check_connection(). - Note: must call check_connection() manually unless using MoneroConnectionManager. + Note: must call check_connection() manually. :return bool | None: `True` if authenticated or no authentication required, `False` if not authenticated, or `None` if check_connection() has not been called. """ @@ -95,7 +95,7 @@ class MoneroRpcConnection(SerializableStruct): """ Indicates if the connection is connected according to the last call to check_connection(). - Note: must call check_connection() manually unless using MoneroConnectionManager. + Note: must call check_connection() manually. :return bool | None: `True` or `False` to indicate if connected, or `None` if check_connection() has not been called. """ @@ -114,7 +114,7 @@ class MoneroRpcConnection(SerializableStruct): """ Indicates if the connection is online according to the last call to check_connection(). - Note: must call check_connection() manually unless using MoneroConnectionManager. + Note: must call check_connection() manually. :return bool | None: `True` or `False` to indicate if online, or `None` if check_connection() has not been called. """ diff --git a/src/python/monero_wallet.pyi b/src/python/monero_wallet.pyi index 20f4ed8..a2a4902 100644 --- a/src/python/monero_wallet.pyi +++ b/src/python/monero_wallet.pyi @@ -7,7 +7,6 @@ from .monero_account import MoneroAccount from .monero_account_tag import MoneroAccountTag from .monero_address_book_entry import MoneroAddressBookEntry from .monero_subaddress import MoneroSubaddress -from .monero_connection_manager import MoneroConnectionManager from .monero_rpc_connection import MoneroRpcConnection from .monero_tx_priority import MoneroTxPriority from .monero_tx_config import MoneroTxConfig @@ -377,13 +376,6 @@ class MoneroWallet: :return int: the subaddress's balance """ ... - def get_connection_manager(self) -> MoneroConnectionManager | None: - """ - Get the wallet's daemon connection manager. - - :return Optional[MoneroConnectionManager]: the wallet's daemon connection manager - """ - ... def get_daemon_connection(self) -> MoneroRpcConnection | None: """ Get the wallet's daemon connection. @@ -1024,13 +1016,6 @@ class MoneroWallet: :param str val: is the attribute value. """ ... - def set_connection_manager(self, connection_manager: MoneroConnectionManager | None) -> None: - """ - Set the wallet's daemon connection manager. - - :param MoneroConnectionManager connection_manager: manages connections to monerod - """ - ... @typing.overload def set_daemon_connection(self, connection: MoneroRpcConnection | None) -> None: """ diff --git a/src/python/monero_wallet_config.pyi b/src/python/monero_wallet_config.pyi index b0a4dad..3e90307 100644 --- a/src/python/monero_wallet_config.pyi +++ b/src/python/monero_wallet_config.pyi @@ -1,6 +1,5 @@ import typing -from .monero_connection_manager import MoneroConnectionManager from .monero_network_type import MoneroNetworkType from .monero_rpc_connection import MoneroRpcConnection @@ -11,8 +10,6 @@ class MoneroWalletConfig: """ account_lookahead: int | None """Account index look ahead.""" - connection_manager: MoneroConnectionManager | None - """The wallet connection manager.""" is_multisig: bool | None """Indicates if the wallet is a multisignature wallet.""" language: str | None diff --git a/tests/test_monero_connection_manager.py b/tests/test_monero_connection_manager.py deleted file mode 100644 index 74bc056..0000000 --- a/tests/test_monero_connection_manager.py +++ /dev/null @@ -1,347 +0,0 @@ -import pytest -import logging - -from typing import Optional, override -from monero import ( - MoneroConnectionManager, MoneroRpcConnection, MoneroConnectionPollType -) -from utils import ( - ConnectionChangeCollector, TestUtils as Utils, - AssertUtils, RpcConnectionUtils, BaseTestClass -) - -logger: logging.Logger = logging.getLogger("TestMoneroConnectionManager") - - -@pytest.mark.integration -class TestMoneroConnectionManager(BaseTestClass): - """Connection manager integration tests""" - - OFFLINE_PROXY_URI: str = "127.0.0.1:9050" - """Proxy used to simulate offline servers""" - - _cm: MoneroConnectionManager | None = None - """Connection manager test instance.""" - - #region Fixtures - - # Before all tests - @override - def before_all(self) -> None: - super().before_all() - self._cm = MoneroConnectionManager() - - # After all tests - @override - def after_all(self) -> None: - super().after_all() - if self._cm: - self._cm.reset() - logger.debug("Resetted connection manager") - - Utils.RPC_WALLET_MANAGER.clear() - - # test connnections fixture - @pytest.fixture(scope="class") - def connections(self) -> list[MoneroRpcConnection]: - """Rpc connections used in connection manager tests.""" - return Utils.get_all_rpc_connections() - - # connection manager - @pytest.fixture(scope="class") - def connection_manager(self) -> MoneroConnectionManager: - """Connection manager test instance.""" - if self._cm is None: - self._cm = MoneroConnectionManager() - return self._cm - - #endregion - - @pytest.mark.timeout(60 * 5) - def test_connection_manager(self, connection_manager: MoneroConnectionManager, connections: list[MoneroRpcConnection]) -> None: - # listen for changes - listener = ConnectionChangeCollector() - connection_manager.add_listener(listener) - - # add prioritized connections - connection: Optional[MoneroRpcConnection] = connections[4] - assert connection is not None - connection.priority = 1 - connection_manager.add_connection(connection) - connection = connections[2] - assert connection is not None - connection.priority = 2 - connection_manager.add_connection(connection) - connection = connections[3] - assert connection is not None - connection.priority = 2 - connection_manager.add_connection(connection) - connection = connections[0] - assert connection is not None - # default priority is lowest - connection_manager.add_connection(connection) - connection = connections[1] - assert connection is not None - assert connection.uri is not None - # test unauthenticated - connection_manager.add_connection(MoneroRpcConnection(connection.uri, timeout=connection.timeout)) - - # test connections and order - ordered_connections: list[MoneroRpcConnection] = connection_manager.get_connections() - RpcConnectionUtils.test_connections_and_order(ordered_connections, connections, True) - - # test getting connection by uri - connection = connections[0] - assert connection is not None - assert connection.uri is not None - assert connection_manager.has_connection(connection.uri) - assert connection_manager.get_connection_by_uri(connection.uri) == connections[0] - - # test unknown connection - num_expected_changes: int = 0 - connection_manager.set_connection(ordered_connections[0]) - assert connection_manager.is_connected() is None - num_expected_changes += 1 - assert num_expected_changes == listener.num_changed_connections - - # auto connect to the best available connection - connection_manager.start_polling(Utils.SYNC_PERIOD_IN_MS) - listener.wait_for_change(num_expected_changes + 1, Utils.SYNC_PERIOD_IN_MS, "Waiting for auto connect to best available connection") - assert connection_manager.is_connected() - connection = connection_manager.get_connection() - assert connection is not None - assert connection.is_online() - assert connection == connections[4] - num_expected_changes += 1 - assert num_expected_changes == listener.num_changed_connections - assert listener.changed_connections[-1] == connection - connection_manager.set_auto_switch(False) - connection_manager.stop_polling() - connection_manager.disconnect() - num_expected_changes += 1 - assert num_expected_changes == listener.num_changed_connections - assert listener.changed_connections[-1] is None - - # start periodically checking connection without auto switch - connection_manager.start_polling(Utils.SYNC_PERIOD_IN_MS, False) - - # connect to the best available connection in order of priority and response time - connection = connection_manager.get_best_available_connection() - connection_manager.set_connection(connection) - assert connection == connections[4] - assert connection.is_online() - assert connection.is_authenticated() - num_expected_changes += 1 - assert num_expected_changes == listener.num_changed_connections - assert listener.changed_connections[-1] == connection - - # test connections and order - ordered_connections = connection_manager.get_connections() - RpcConnectionUtils.test_connections_and_order(ordered_connections, connections, False) - # TODO others should not ever connected - #for i, connection in enumerate(ordered_connections): - # if i < 1: - # continue - # assert connection.is_online() is None - - # set proxies to simulate prioritized servers shutdown - for i, conn in enumerate(connections): - if i < 2: - continue - conn.proxy_uri = self.OFFLINE_PROXY_URI - - listener.wait_for_change(num_expected_changes + 1, Utils.SYNC_PERIOD_IN_MS, "Simulating priotizized servers shut down") - assert connection_manager.is_connected() is False, f"{connection_manager.get_connection().serialize()}" - connection = connection_manager.get_connection() - - assert connection.is_online() is False - assert connection.is_connected() is False - assert connection.is_authenticated() is None - num_expected_changes += 1 - assert num_expected_changes == listener.num_changed_connections - assert listener.changed_connections[-1] == connection_manager.get_connection() - - # test connection order - ordered_connections = connection_manager.get_connections() - RpcConnectionUtils.test_connections_order(ordered_connections, connections) - - # check all connections - connection_manager.check_connections() - - # test connection order - ordered_connections = connection_manager.get_connections() - RpcConnectionUtils.test_connections_order(ordered_connections, connections) - - # test online and authentication status - for i, ordered_connection in enumerate(ordered_connections): - is_online = ordered_connection.is_online() - is_authenticated = ordered_connection.is_authenticated() - if i == 1 or i == 2: - assert is_online - else: - assert is_online is False - if i == 1: - assert is_authenticated - elif i == 2: - assert is_authenticated is False - else: - assert is_authenticated is None - - # test auto switch when disconnected - connection_manager.set_auto_switch(True) - listener.wait_for_autoswitch(connection_manager, Utils.SYNC_PERIOD_IN_MS) - connection = connection_manager.get_connection() - conn_str = connection.serialize() if connection is not None else 'None' # type: ignore - assert connection_manager.is_connected(), f"conn= {conn_str}" - assert connection is not None - assert connection.is_online() - assert connection == connections[0] - num_expected_changes += 1 - assert num_expected_changes == listener.num_changed_connections - assert listener.changed_connections[-1] == connection - - # test connection order - ordered_connections = connection_manager.get_connections() - assert ordered_connections[0] == connection - assert ordered_connections[0] == connections[0] - connection = connections[1] - assert connection is not None - assert ordered_connections[1].uri == connection.uri - assert ordered_connections[2] == connections[4] - assert ordered_connections[3] == connections[2] - assert ordered_connections[4] == connections[3] - - # connect to specific endpoint without authentication - connection = ordered_connections[1] - assert connection.is_authenticated() is False - connection_manager.set_connection(connection) - assert connection_manager.is_connected() is False - num_expected_changes += 1 - assert num_expected_changes == listener.num_changed_connections - - # connect to specific endpoint with authentication - ordered_connections[1].set_credentials("rpc_user", "abc123") - connection_manager.check_connection() - cm_connection: MoneroRpcConnection = connection_manager.get_connection() - assert cm_connection is not None - assert cm_connection.uri == connections[1].uri - assert connection.is_online() - assert connection.is_authenticated() - num_expected_changes += 1 - assert num_expected_changes == listener.num_changed_connections - assert listener.changed_connections[-1] == connection - - # test connection order - ordered_connections = connection_manager.get_connections() - assert ordered_connections[0] == connection_manager.get_connection() - connection = connections[1] - assert connection is not None - assert ordered_connections[0].uri == connection.uri - assert ordered_connections[1] == connections[0] - assert ordered_connections[2] == connections[4] - assert ordered_connections[3] == connections[2] - assert ordered_connections[4] == connections[3] - - first: bool = True - for i, ordered_connection in enumerate(ordered_connections): - if i == len(ordered_connections) - 1: - break - if i <= 1: - assert ordered_connection.is_online() if first else not ordered_connection.is_online() - - assert ordered_connections[4].is_online() is False - - # set connection to existing uri - connection = connections[0] - assert connection is not None - connection_manager.set_connection(connection.uri) - assert connection_manager.is_connected() is True - assert connections[0] == connection_manager.get_connection() - connection = connection_manager.get_connection() - assert connection is not None - assert Utils.DAEMON_RPC_USERNAME == connection.username - assert Utils.DAEMON_RPC_PASSWORD == connection.password - num_expected_changes += 1 - assert num_expected_changes == listener.num_changed_connections - AssertUtils.assert_equals(listener.changed_connections[-1], connections[0]) - - # set connection to new uri - connection_manager.stop_polling() - uri: str = "http:#localhost:49999" - connection_manager.set_connection(uri) - connection = connection_manager.get_connection() - assert connection is not None - assert uri == connection.uri - connection.timeout = Utils.AUTO_CONNECT_TIMEOUT_MS - num_expected_changes += 1 - assert num_expected_changes == listener.num_changed_connections - connection = listener.changed_connections[-1] - assert connection is not None - assert uri == connection.uri - - # set connection to empty string - connection_manager.set_connection("") - assert connection_manager.get_connection() is None - num_expected_changes += 1 - assert num_expected_changes == listener.num_changed_connections - - # check all connections and test auto switch - connection_manager.check_connections() - num_expected_changes += 1 - assert num_expected_changes == listener.num_changed_connections - assert connection_manager.is_connected() - - # remove current connection and test auto switch - connection = connection_manager.get_connection() - assert connection is not None - assert connection.uri is not None - connection_manager.remove_connection(connection.uri) - num_expected_changes += 1 - assert num_expected_changes == listener.num_changed_connections - assert connection_manager.is_connected() is False - connection_manager.check_connections() - num_expected_changes += 1 - assert num_expected_changes == listener.num_changed_connections - assert connection_manager.is_connected() - - # test polling current connection - connection_manager.set_connection(None) - assert connection_manager.is_connected() is False - num_expected_changes += 1 - assert num_expected_changes == listener.num_changed_connections - connection_manager.start_polling( - period_ms=Utils.SYNC_PERIOD_IN_MS, - poll_type=MoneroConnectionPollType.CURRENT - ) - - listener.wait_for_change(num_expected_changes + 1, Utils.SYNC_PERIOD_IN_MS, "Polling current connection") - assert connection_manager.is_connected() is True - num_expected_changes += 1 - assert num_expected_changes == listener.num_changed_connections - - # test polling all connections - connection_manager.set_connection(None) - num_expected_changes += 1 - assert num_expected_changes == listener.num_changed_connections - connection_manager.start_polling(period_ms=Utils.SYNC_PERIOD_IN_MS, poll_type=MoneroConnectionPollType.ALL) - listener.wait_for_change(num_expected_changes + 1, Utils.SYNC_PERIOD_IN_MS, "Polling all connections") - assert connection_manager.is_connected() is True - num_expected_changes += 1 - assert num_expected_changes == listener.num_changed_connections - - connection = connection_manager.get_connection() - assert connection is not None - # set proxies simulating shut down all connections - for con in ordered_connections: - con.proxy_uri = self.OFFLINE_PROXY_URI - - listener.wait_for_change(num_expected_changes + 1, Utils.SYNC_PERIOD_IN_MS, "Simulating total shut down") - assert connection.is_online() is False, f"Expected offline connection: {connection.serialize()}" - num_expected_changes += 1 - assert num_expected_changes == listener.num_changed_connections - assert listener.changed_connections[-1] == connection - - # reset - connection_manager.reset() - assert len(connection_manager.get_connections()) == 0 - assert connection_manager.get_connection() is None diff --git a/tests/test_monero_rpc_connection.py b/tests/test_monero_rpc_connection.py index 64aeb7e..03da785 100644 --- a/tests/test_monero_rpc_connection.py +++ b/tests/test_monero_rpc_connection.py @@ -3,7 +3,7 @@ from monero import ( MoneroRpcConnection, MoneroConnectionType, MoneroRpcError, - MoneroUtils, MoneroConnectionProriotyComparator + MoneroUtils, MoneroConnectionPriorityComparator ) from utils import TestUtils as Utils, DaemonUtils, StringUtils, BaseTestClass @@ -195,7 +195,7 @@ def test_priority(self) -> None: for i in range(100): for j in range(100): expected: bool = (i == 0 and j != 0) or (i != 0 and j != 0 and i > j) - assert MoneroConnectionProriotyComparator.compare(i, j) is expected + assert MoneroConnectionPriorityComparator.compare(i, j) is expected # Can send json request @pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") diff --git a/tests/test_monero_wallet_full.py b/tests/test_monero_wallet_full.py index fba9cab..83f24ca 100644 --- a/tests/test_monero_wallet_full.py +++ b/tests/test_monero_wallet_full.py @@ -57,7 +57,6 @@ def _create_wallet(self, config: Optional[MoneroWalletConfig], start_syncing: bo config.password = Utils.WALLET_PASSWORD if config.network_type is None: config.network_type = Utils.NETWORK_TYPE - #if config.server is None and config.connection_manager is None: if config.server is None: config.server = Utils.get_daemon_rpc_connection() if config.restore_height is None and not random: @@ -81,7 +80,7 @@ def _open_wallet(self, config: Optional[MoneroWalletConfig], start_syncing: bool config.password = Utils.WALLET_PASSWORD if config.network_type is None: config.network_type = Utils.NETWORK_TYPE - if config.server is None and config.connection_manager is None: + if config.server is None: config.server = Utils.get_daemon_rpc_connection() # open wallet diff --git a/tests/test_monero_wallet_interface.py b/tests/test_monero_wallet_interface.py index 114a275..54b8e1c 100644 --- a/tests/test_monero_wallet_interface.py +++ b/tests/test_monero_wallet_interface.py @@ -2,7 +2,7 @@ import logging from monero import ( - MoneroWallet, MoneroConnectionManager, MoneroRpcConnection, + MoneroWallet, MoneroRpcConnection, MoneroWalletListener, MoneroTransferQuery, MoneroOutputQuery, MoneroTxConfig, MoneroTxSet, MoneroMessageSignatureType, MoneroTxWallet @@ -50,15 +50,6 @@ def test_get_path(self, wallet: MoneroWallet) -> None: def test_get_network_type(self, wallet: MoneroWallet) -> None: wallet.get_network_type() - # TODO move definitions to monero-cpp - @pytest.mark.not_supported - def test_set_connection_manager(self, wallet: MoneroWallet) -> None: - wallet.set_connection_manager(MoneroConnectionManager()) - - @pytest.mark.not_supported - def test_get_connection_manager(self, wallet: MoneroWallet) -> None: - wallet.get_connection_manager() - @pytest.mark.not_supported def test_set_daemon_connection_1(self, wallet: MoneroWallet) -> None: wallet.set_daemon_connection('') diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py index 0deefb2..a7b2445 100644 --- a/tests/utils/__init__.py +++ b/tests/utils/__init__.py @@ -3,13 +3,11 @@ from .test_utils import TestUtils from .mining_utils import MiningUtils from .wallet_sync_printer import WalletSyncPrinter -from .connection_change_collector import ConnectionChangeCollector from .address_book import AddressBook from .keys_book import KeysBook from .test_context import TestContext from .tx_context import TxContext from .binary_block_context import BinaryBlockContext -from .sample_connection_listener import SampleConnectionListener from .string_utils import StringUtils from .wallet_equality_utils import WalletEqualityUtils from .wallet_tx_tracker import WalletTxTracker @@ -45,13 +43,11 @@ 'TestUtils', 'MiningUtils', 'WalletSyncPrinter', - 'ConnectionChangeCollector', 'AddressBook', 'KeysBook', 'TestContext', 'TxContext', 'BinaryBlockContext', - 'SampleConnectionListener', 'StringUtils', 'WalletEqualityUtils', 'WalletTxTracker', diff --git a/tests/utils/connection_change_collector.py b/tests/utils/connection_change_collector.py deleted file mode 100644 index 9614108..0000000 --- a/tests/utils/connection_change_collector.py +++ /dev/null @@ -1,73 +0,0 @@ -import logging - -from typing import Optional -from typing_extensions import override -from monero import ( - MoneroConnectionManager, - MoneroConnectionManagerListener, MoneroRpcConnection -) - -from .gen_utils import GenUtils - -logger: logging.Logger = logging.getLogger("ConnectionChangeCollector") - - -class ConnectionChangeCollector(MoneroConnectionManagerListener): - """Collects connection changes.""" - - changed_connections: list[Optional[MoneroRpcConnection]] - """Collected changed connections.""" - - @property - def num_changed_connections(self) -> int: - """Number of changed connections collected.""" - return len(self.changed_connections) - - def __init__(self) -> None: - """Initialize a new connection change collector.""" - super().__init__() - self.changed_connections = [] - - @override - def on_connection_changed(self, connection: Optional[MoneroRpcConnection]) -> None: - conn_str: str = connection.serialize() if connection is not None else 'None' - logger.debug(f"Collecting connection change: {conn_str}") - self.changed_connections.append(connection) - - def wait_for_change( - self, - expected_num_changes: int, - interval_ms: int = 5000, - custom_message: str = "Waiting for connection change" - ) -> Optional[MoneroRpcConnection]: - """ - Wait until a connection change occurs. - - :param int expected_num_changes: expected number of connection changes to wait for. - :param int interval_ms: custom check interval in milliseconds (default 5000). - :param str custom_message: custom message to show in debug during wait. - :returns MoneroRpcConnection | None: changed connection. - """ - - last_num_changes: int = self.num_changed_connections - while expected_num_changes > last_num_changes: - logger.debug(f"{custom_message} (changes {last_num_changes}/{expected_num_changes})...") - GenUtils.wait_for(interval_ms) - last_num_changes = self.num_changed_connections - - logger.debug(f"Connection changed (connections {self.num_changed_connections}).") - - def wait_for_autoswitch(self, manager: MoneroConnectionManager, interval_ms: int) -> None: - """ - Wait for connection auto switch. - - :param MoneroConnectionManager manager: connection manager to wait for auto switch. - :param int interval_ms: custom check interval in milliseconds. - """ - connected: bool = False - # wait unitl manager has autoswitched connection - while not connected: - logger.debug("Waiting for autoswitch...") - GenUtils.wait_for(interval_ms) - connected = manager.is_connected() is not None - diff --git a/tests/utils/sample_connection_listener.py b/tests/utils/sample_connection_listener.py deleted file mode 100644 index 6183652..0000000 --- a/tests/utils/sample_connection_listener.py +++ /dev/null @@ -1,17 +0,0 @@ -import logging - -from typing import Optional -from typing_extensions import override -from monero import MoneroConnectionManagerListener, MoneroRpcConnection - -logger: logging.Logger = logging.getLogger("SampleConnectionListener") - - -class SampleConnectionListener(MoneroConnectionManagerListener): - - def __init__(self) -> None: - MoneroConnectionManagerListener.__init__(self) - - @override - def on_connection_changed(self, connection: Optional[MoneroRpcConnection]) -> None: - logger.debug(f"Connection changed to: {connection.uri if connection is not None else 'None'}")