diff --git a/pytest.ini b/pytest.ini index 057b61b..1cd06b9 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,5 +1,6 @@ [pytest] minversion = 6.0 addopts = -ra -q +log_cli_level = INFO testpaths = tests diff --git a/src/cpp/daemon/py_monero_daemon_model.cpp b/src/cpp/daemon/py_monero_daemon_model.cpp index 6e23969..2d65ba0 100644 --- a/src/cpp/daemon/py_monero_daemon_model.cpp +++ b/src/cpp/daemon/py_monero_daemon_model.cpp @@ -218,12 +218,7 @@ bool PyMoneroRpcConnection::check_connection(int timeout_ms) { throw std::runtime_error("Could not set rpc connection: " + m_uri.get()); } - std::cout << "Connecting to server " << m_uri.get() << ", timeout: " << timeout_ms << std::endl; bool connected = m_http_client->connect(std::chrono::milliseconds(timeout_ms)); - - if (connected) std::cout << "Connected to server" << std::endl; - else std::cout << "Could not connect to server" << std::endl; - auto start = std::chrono::high_resolution_clock::now(); PyMoneroJsonRequest request("get_version"); auto response = send_json_request(request); diff --git a/src/cpp/py_monero.cpp b/src/cpp/py_monero.cpp index bb763a5..f1b60ac 100644 --- a/src/cpp/py_monero.cpp +++ b/src/cpp/py_monero.cpp @@ -31,7 +31,7 @@ PYBIND11_MODULE(monero, m) { auto py_monero_tx = py::class_>(m, "MoneroTx"); auto py_monero_key_image = py::class_>(m, "MoneroKeyImage"); auto py_monero_output = py::class_>(m, "MoneroOutput"); - auto py_monero_wallet_config = py::class_>(m, "MoneroWalletConfig"); + auto py_monero_wallet_config = py::class_>(m, "MoneroWalletConfig"); auto py_monero_subaddress = py::class_>(m, "MoneroSubaddress"); auto py_monero_sync_result = py::class_>(m, "MoneroSyncResult"); auto py_monero_account = py::class_>(m, "MoneroAccount"); @@ -772,32 +772,27 @@ PYBIND11_MODULE(monero, m) { // monero_wallet_config py_monero_wallet_config .def(py::init<>()) - .def(py::init(), py::arg("config")) + .def(py::init(), py::arg("config"), py::keep_alive<1, 2>()) .def_static("deserialize", [](const std::string& config_json) { - MONERO_CATCH_AND_RETHROW(monero::monero_wallet_config::deserialize(config_json)); + MONERO_CATCH_AND_RETHROW(PyMoneroWalletConfig::deserialize(config_json)); }, py::arg("config_json")) - .def_readwrite("path", &monero::monero_wallet_config::m_path) - .def_readwrite("password", &monero::monero_wallet_config::m_password) - .def_readwrite("network_type", &monero::monero_wallet_config::m_network_type) - .def_readwrite("server", &monero::monero_wallet_config::m_server) - .def_readwrite("seed", &monero::monero_wallet_config::m_seed) - .def_readwrite("seed_offset", &monero::monero_wallet_config::m_seed_offset) - .def_readwrite("primary_address", &monero::monero_wallet_config::m_primary_address) - .def_readwrite("private_view_key", &monero::monero_wallet_config::m_private_view_key) - .def_readwrite("private_spend_key", &monero::monero_wallet_config::m_private_spend_key) - .def_readwrite("save_current", &monero::monero_wallet_config::m_save_current) - .def_readwrite("language", &monero::monero_wallet_config::m_language) - .def_readwrite("restore_height", &monero::monero_wallet_config::m_restore_height) - .def_readwrite("account_lookahead", &monero::monero_wallet_config::m_account_lookahead) - .def_readwrite("subaddress_lookahead", &monero::monero_wallet_config::m_subaddress_lookahead) - .def_readwrite("is_multisig", &monero::monero_wallet_config::m_is_multisig) - .def_property("connection_manager", - [](const PyMoneroWalletConfig& self) { return self.m_connection_manager; }, - [](PyMoneroWalletConfig& self, std::optional> val) { - if (val.has_value()) self.m_connection_manager = val.value(); - else self.m_connection_manager = boost::none; - }) - .def("copy", [](monero::monero_wallet_config& self) { + .def_readwrite("path", &PyMoneroWalletConfig::m_path) + .def_readwrite("password", &PyMoneroWalletConfig::m_password) + .def_readwrite("network_type", &PyMoneroWalletConfig::m_network_type) + .def_readwrite("server", &PyMoneroWalletConfig::m_server) + .def_readwrite("seed", &PyMoneroWalletConfig::m_seed) + .def_readwrite("seed_offset", &PyMoneroWalletConfig::m_seed_offset) + .def_readwrite("primary_address", &PyMoneroWalletConfig::m_primary_address) + .def_readwrite("private_view_key", &PyMoneroWalletConfig::m_private_view_key) + .def_readwrite("private_spend_key", &PyMoneroWalletConfig::m_private_spend_key) + .def_readwrite("save_current", &PyMoneroWalletConfig::m_save_current) + .def_readwrite("language", &PyMoneroWalletConfig::m_language) + .def_readwrite("restore_height", &PyMoneroWalletConfig::m_restore_height) + .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()); }); @@ -1975,13 +1970,13 @@ PYBIND11_MODULE(monero, m) { // monero_wallet_keys py_monero_wallet_keys - .def_static("create_wallet_random", [](const monero::monero_wallet_config& config) { + .def_static("create_wallet_random", [](const PyMoneroWalletConfig& config) { MONERO_CATCH_AND_RETHROW(monero::monero_wallet_keys::create_wallet_random(config)); }, py::arg("config")) - .def_static("create_wallet_from_seed", [](const monero::monero_wallet_config& config) { + .def_static("create_wallet_from_seed", [](const PyMoneroWalletConfig& config) { MONERO_CATCH_AND_RETHROW(monero::monero_wallet_keys::create_wallet_from_seed(config)); }, py::arg("config")) - .def_static("create_wallet_from_keys", [](const monero::monero_wallet_config& config) { + .def_static("create_wallet_from_keys", [](const PyMoneroWalletConfig& config) { MONERO_CATCH_AND_RETHROW(monero::monero_wallet_keys::create_wallet_from_keys(config)); }, py::arg("config")) .def_static("get_seed_languages", []() { @@ -2002,7 +1997,7 @@ PYBIND11_MODULE(monero, m) { .def_static("open_wallet_data", [](const std::string& password, monero::monero_network_type nettype, const std::string& keys_data, const std::string& cache_data, const monero_rpc_connection& daemon_connection) { MONERO_CATCH_AND_RETHROW(monero::monero_wallet_full::open_wallet_data(password, nettype, keys_data, cache_data, daemon_connection)); }, py::arg("password"), py::arg("nettype"), py::arg("keys_data"), py::arg("cache_data"), py::arg("daemon_connection")) - .def_static("create_wallet", [](const monero::monero_wallet_config& config) { + .def_static("create_wallet", [](const PyMoneroWalletConfig& config) { MONERO_CATCH_AND_RETHROW(monero::monero_wallet_full::create_wallet(config)); }, py::arg("config")) .def_static("get_seed_languages", []() { @@ -2069,14 +2064,14 @@ PYBIND11_MODULE(monero, m) { throw py::value_error(ex.what()); } }, py::arg("name"), py::arg("password")) + .def("get_seed_languages", [](PyMoneroWalletRpc& self) { + assert_wallet_is_not_closed(&self); + MONERO_CATCH_AND_RETHROW(self.get_seed_languages()); + }) .def("get_rpc_connection", [](PyMoneroWalletRpc& self) { assert_wallet_is_not_closed(&self); MONERO_CATCH_AND_RETHROW(self.get_rpc_connection()); }) - .def("get_accounts", [](PyMoneroWalletRpc& self, bool include_subaddresses, const std::string& tag, bool skip_balances) { - assert_wallet_is_not_closed(&self); - MONERO_CATCH_AND_RETHROW(self.get_accounts(include_subaddresses, tag, skip_balances)); - }, py::arg("include_subaddresses"), py::arg("tag"), py::arg("skip_balances")) .def("stop", [](PyMoneroWalletRpc& self) { assert_wallet_is_not_closed(&self); MONERO_CATCH_AND_RETHROW(self.stop()); diff --git a/src/cpp/wallet/py_monero_wallet.cpp b/src/cpp/wallet/py_monero_wallet.cpp index ac9f72a..9031661 100644 --- a/src/cpp/wallet/py_monero_wallet.cpp +++ b/src/cpp/wallet/py_monero_wallet.cpp @@ -83,12 +83,6 @@ void PyMoneroWallet::set_connection_manager(const std::shared_ptr> PyMoneroWallet::get_connection_manager() const { - std::optional> result; - if (m_connection_manager != nullptr) result = m_connection_manager; - return result; -} - void PyMoneroWallet::announce_new_block(uint64_t height) { for (const auto &listener : m_listeners) { try { @@ -365,7 +359,7 @@ PyMoneroWalletRpc* PyMoneroWalletRpc::open_wallet(const std::shared_ptrm_server != boost::none) { set_daemon_connection(config->m_server); } - + return this; } @@ -377,7 +371,7 @@ PyMoneroWalletRpc* PyMoneroWalletRpc::open_wallet(const std::string& name, const } PyMoneroWalletRpc* PyMoneroWalletRpc::create_wallet(const std::shared_ptr &config) { - if (!config) throw std::runtime_error("Must specify config to create wallet"); + if (config == nullptr) throw std::runtime_error("Must specify config to create wallet"); if (config->m_network_type != boost::none) throw std::runtime_error("Cannot specify network type when creating RPC wallet"); if (config->m_seed != boost::none && (config->m_primary_address != boost::none || config->m_private_view_key != boost::none || config->m_private_spend_key != boost::none)) { throw std::runtime_error("Wallet can be initialized with a seed or keys but not both"); @@ -385,8 +379,13 @@ 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 connection = config->m_connection_manager.get()->get_connection(); - if (connection) config->m_server = *connection; + auto cm = config->m_connection_manager.value(); + 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); @@ -551,16 +550,20 @@ std::string PyMoneroWalletRpc::get_address(const uint32_t account_idx, const uin get_subaddresses(account_idx, empty_indices, true); return get_address(account_idx, subaddress_idx); } - + auto subaddress_map = it->second; auto it2 = subaddress_map.find(subaddress_idx); if (it2 == subaddress_map.end()) { get_subaddresses(account_idx, empty_indices, true); - return get_address(account_idx, subaddress_idx); + auto it3 = m_address_cache.find(account_idx); + if (it3 == m_address_cache.end()) throw std::runtime_error("Could not find account address at index (" + std::to_string(account_idx) + ", " + std::to_string(subaddress_idx) + ")" ); + auto it4 = it3->second.find(subaddress_idx); + if (it4 == it3->second.end()) throw std::runtime_error("Could not find address at index (" + std::to_string(account_idx) + ", " + std::to_string(subaddress_idx) + ")" ); + return it4->second; } - return it2->second.data(); + return it2->second; } monero_subaddress PyMoneroWalletRpc::get_address_index(const std::string& address) const { @@ -736,6 +739,25 @@ uint64_t PyMoneroWalletRpc::get_unlocked_balance(uint32_t account_idx, uint32_t return wallet_balance->m_unlocked_balance; } +monero_account PyMoneroWalletRpc::get_account(const uint32_t account_idx, bool include_subaddresses) const { + return get_account(account_idx, include_subaddresses, false); +} + +monero_account PyMoneroWalletRpc::get_account(const uint32_t account_idx, bool include_subaddresses, bool skip_balances) const { + for(auto& account : monero::monero_wallet::get_accounts()) { + if (account.m_index.get() == account_idx) { + std::vector empty_indices; + if (include_subaddresses) account.m_subaddresses = get_subaddresses(account_idx, empty_indices, skip_balances); + return account; + } + } + throw std::runtime_error("Account with index " + std::to_string(account_idx) + " does not exist"); +} + +std::vector PyMoneroWalletRpc::get_accounts(bool include_subaddresses, const std::string& tag) const { + return get_accounts(include_subaddresses, tag, false); +} + std::vector PyMoneroWalletRpc::get_accounts(bool include_subaddresses, const std::string& tag, bool skip_balances) const { auto params = std::make_shared(tag); PyMoneroJsonRequest request("get_accounts", params); @@ -744,7 +766,6 @@ std::vector PyMoneroWalletRpc::get_accounts(bool include_subaddr auto node = response->m_result.get(); std::vector accounts; PyMoneroAccount::from_property_tree(node, accounts); - if (include_subaddresses) { for (auto &account : accounts) { @@ -769,7 +790,6 @@ std::vector PyMoneroWalletRpc::get_accounts(bool include_subaddr auto node2 = response2->m_result.get(); auto bal_res = std::make_shared(); PyMoneroGetBalanceResponse::from_property_tree(node2, bal_res); - for (const auto &subaddress : bal_res->m_per_subaddress) { // merge info auto account = &accounts[subaddress->m_account_index.get()]; @@ -795,6 +815,8 @@ monero_account PyMoneroWalletRpc::create_account(const std::string& label) { if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); auto node = response->m_result.get(); monero_account res; + res.m_balance = 0; + res.m_unlocked_balance = 0; bool found_index = false; bool address_found = false; @@ -826,18 +848,19 @@ std::vector PyMoneroWalletRpc::get_subaddresses(const uint32_ std::vector subaddresses; - // initialize subaddressesb + // initialize subaddresses for (auto it = node.begin(); it != node.end(); ++it) { std::string key = it->first; if (key == std::string("addresses")) { auto node2 = it->second; - for (auto it2 = node2.begin(); it != node2.end(); ++it2) { + for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) { auto subaddress = std::make_shared(); - PyMoneroSubaddress::from_rpc_property_tree(node2, subaddress); + PyMoneroSubaddress::from_rpc_property_tree(it2->second, subaddress); subaddress->m_account_index = account_idx; subaddresses.push_back(*subaddress); } + break; } } @@ -864,8 +887,7 @@ std::vector PyMoneroWalletRpc::get_subaddresses(const uint32_ for (auto &tgt_subaddress: subaddresses) { for (const auto &rpc_subaddress : subaddresses2) { - if (rpc_subaddress->m_index != tgt_subaddress.m_index) continue; - + if (rpc_subaddress->m_index != tgt_subaddress.m_index) continue; // skip to subaddress with same index if (rpc_subaddress->m_balance != boost::none) tgt_subaddress.m_balance = rpc_subaddress->m_balance; if (rpc_subaddress->m_unlocked_balance != boost::none) tgt_subaddress.m_unlocked_balance = rpc_subaddress->m_unlocked_balance; if (rpc_subaddress->m_num_unspent_outputs != boost::none) tgt_subaddress.m_num_unspent_outputs = rpc_subaddress->m_num_unspent_outputs; @@ -873,19 +895,17 @@ std::vector PyMoneroWalletRpc::get_subaddresses(const uint32_ } } } - + // cache addresses - /* - Map subaddressMap = addressCache.get(accountIdx); - if (subaddressMap == null) { - subaddressMap = new HashMap(); - addressCache.put(accountIdx, subaddressMap); + auto it = m_address_cache.find(account_idx); + if (it == m_address_cache.end()) { + m_address_cache[account_idx] = serializable_unordered_map(); } - for (const auto &subaddress : subaddresses) { - subaddressMap.put(subaddress.getIndex(), subaddress.getAddress()); + for (const auto& subaddress : subaddresses) { + m_address_cache[account_idx][subaddress.m_index.get()] = subaddress.m_address.get(); } - */ + // return results return subaddresses; } @@ -895,8 +915,8 @@ std::vector PyMoneroWalletRpc::get_subaddresses(uint32_t acco } std::vector PyMoneroWalletRpc::get_subaddresses(const uint32_t account_idx) const { - std::vector subaddress_indices; - return get_subaddresses(account_idx, subaddress_indices); + std::vector empty_indices; + return get_subaddresses(account_idx, empty_indices); } monero_subaddress PyMoneroWalletRpc::get_subaddress(const uint32_t account_idx, const uint32_t subaddress_idx) const { @@ -916,7 +936,7 @@ monero_subaddress PyMoneroWalletRpc::create_subaddress(uint32_t account_idx, con auto node = response->m_result.get(); monero_subaddress sub; sub.m_account_index = account_idx; - sub.m_label = label; + if (!label.empty()) sub.m_label = label; sub.m_balance = 0; sub.m_unlocked_balance = 0; sub.m_num_unspent_outputs = 0; diff --git a/src/cpp/wallet/py_monero_wallet.h b/src/cpp/wallet/py_monero_wallet.h index 3ce3bb0..d1da70e 100644 --- a/src/cpp/wallet/py_monero_wallet.h +++ b/src/cpp/wallet/py_monero_wallet.h @@ -551,7 +551,7 @@ class PyMoneroWallet : public monero::monero_wallet { } virtual void set_connection_manager(const std::shared_ptr &connection_manager); - virtual std::optional> get_connection_manager() const; + 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); @@ -678,6 +678,9 @@ class PyMoneroWalletRpc : public PyMoneroWallet { uint64_t get_unlocked_balance() const override; uint64_t get_unlocked_balance(uint32_t account_index) const override; uint64_t get_unlocked_balance(uint32_t account_idx, uint32_t subaddress_idx) const override; + monero_account get_account(const uint32_t account_idx, bool include_subaddresses) const override; + monero_account get_account(const uint32_t account_idx, bool include_subaddresses, bool skip_balances) const; + std::vector get_accounts(bool include_subaddresses, const std::string& tag) const override; std::vector get_accounts(bool include_subaddresses, const std::string& tag, bool skip_balances) const; monero_account create_account(const std::string& label = "") override; std::vector get_subaddresses(const uint32_t account_idx, const std::vector& subaddress_indices, bool skip_balances) const; @@ -755,7 +758,7 @@ class PyMoneroWalletRpc : public PyMoneroWallet { std::shared_ptr m_poller; mutable boost::recursive_mutex m_sync_mutex; - serializable_unordered_map> m_address_cache; + mutable serializable_unordered_map> m_address_cache; PyMoneroWalletRpc* create_wallet_random(const std::shared_ptr &conf); PyMoneroWalletRpc* create_wallet_from_seed(const std::shared_ptr &conf); diff --git a/src/cpp/wallet/py_monero_wallet_model.cpp b/src/cpp/wallet/py_monero_wallet_model.cpp index fc06436..c6c6499 100644 --- a/src/cpp/wallet/py_monero_wallet_model.cpp +++ b/src/cpp/wallet/py_monero_wallet_model.cpp @@ -715,7 +715,7 @@ void PyMoneroAccountTag::from_property_tree(const boost::property_tree::ptree& n for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; if (key == std::string("tag")) account_tag->m_tag = it->second.data(); - else if (key == std::string("label")) account_tag->m_label = it->second.data(); + else if (key == std::string("label") && !it->second.data().empty()) account_tag->m_label = it->second.data(); } } @@ -742,7 +742,7 @@ void PyMoneroSubaddress::from_rpc_property_tree(const boost::property_tree::ptre else if (key == std::string("address")) subaddress->m_address = it->second.data(); else if (key == std::string("balance")) subaddress->m_balance = it->second.get_value(); else if (key == std::string("unlocked_balance")) subaddress->m_unlocked_balance = it->second.get_value(); - else if (key == std::string("label")) subaddress->m_label = it->second.data(); + else if (key == std::string("label") && !it->second.data().empty()) subaddress->m_label = it->second.data(); else if (key == std::string("used")) subaddress->m_is_used = it->second.get_value(); else if (key == std::string("num_unspent_outputs")) subaddress->m_num_unspent_outputs = it->second.get_value(); else if (key == std::string("blocks_to_unlock")) subaddress->m_num_blocks_to_unlock = it->second.get_value(); @@ -1037,7 +1037,7 @@ void PyMoneroGetBalanceResponse::from_property_tree(const boost::property_tree:: rapidjson::Value PyMoneroCreateAccountParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); rapidjson::Value value_str(rapidjson::kStringType); - if (m_tag != boost::none) monero_utils::add_json_member("tag", m_tag.get(), allocator, root, value_str); + if (m_tag != boost::none) monero_utils::add_json_member("label", m_tag.get(), allocator, root, value_str); return root; } diff --git a/src/cpp/wallet/py_monero_wallet_model.h b/src/cpp/wallet/py_monero_wallet_model.h index 1750942..a502fa3 100644 --- a/src/cpp/wallet/py_monero_wallet_model.h +++ b/src/cpp/wallet/py_monero_wallet_model.h @@ -29,11 +29,33 @@ class PyMoneroTransferQuery : public monero::monero_transfer_query { static bool is_contextual(const monero::monero_transfer_query &query); }; -class PyMoneroWalletConfig : public monero::monero_wallet_config { +struct PyMoneroWalletConfig : public monero::monero_wallet_config { public: boost::optional> m_connection_manager; - PyMoneroWalletConfig() {} + PyMoneroWalletConfig() { + m_connection_manager = boost::none; + } + + PyMoneroWalletConfig(const PyMoneroWalletConfig& config) { + m_path = config.m_path; + m_password = config.m_password; + m_network_type = config.m_network_type; + m_server = config.m_server; + m_seed = config.m_seed; + m_seed_offset = config.m_seed_offset; + m_primary_address = config.m_primary_address; + m_private_view_key = config.m_private_view_key; + m_private_spend_key = config.m_private_spend_key; + m_restore_height = config.m_restore_height; + m_language = config.m_language; + m_save_current = config.m_save_current; + 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; + } + }; class PyMoneroTxWallet : public monero::monero_tx_wallet { diff --git a/src/python/monero_wallet_rpc.pyi b/src/python/monero_wallet_rpc.pyi index 18ead55..ff443cc 100644 --- a/src/python/monero_wallet_rpc.pyi +++ b/src/python/monero_wallet_rpc.pyi @@ -3,7 +3,6 @@ import typing from .monero_wallet import MoneroWallet from .monero_wallet_config import MoneroWalletConfig from .monero_rpc_connection import MoneroRpcConnection -from .monero_account import MoneroAccount class MoneroWalletRpc(MoneroWallet): @@ -36,17 +35,6 @@ class MoneroWalletRpc(MoneroWallet): :return MoneroWalletConfig: this wallet client. """ ... - @typing.overload - def get_accounts(self, include_subaddresses: bool, tag: str, skip_balances: bool) -> list[MoneroAccount]: # type: ignore - """ - Get all accounts. - - :param include_subaddresses: specifies if subaddresses should be included - :param skip_balances: skip balance check - - :return list[MoneroAccount]: all accounts within the wallet - """ - ... def get_rpc_connection(self) -> MoneroRpcConnection | None: """ Get the wallet's RPC connection. @@ -78,3 +66,10 @@ class MoneroWalletRpc(MoneroWallet): Save and close the current wallet and stop the RPC server. """ ... + def get_seed_languages(self) -> list[str]: + """_summary_ + + Returns: + list[str]: _description_ + """ + ... diff --git a/tests/test_monero_daemon_rpc.py b/tests/test_monero_daemon_rpc.py index 57fa319..d9dd0d2 100644 --- a/tests/test_monero_daemon_rpc.py +++ b/tests/test_monero_daemon_rpc.py @@ -1,6 +1,7 @@ import pytest import time import json +import logging from monero import ( MoneroDaemonRpc, MoneroVersion, MoneroBlockHeader, MoneroBlockTemplate, @@ -12,17 +13,31 @@ ) from utils import MoneroTestUtils as Utils, TestContext, BinaryBlockContext, MiningUtils +logger: logging.Logger = logging.getLogger(__name__) + class TestMoneroDaemonRpc: _daemon: MoneroDaemonRpc = Utils.get_daemon_rpc() _wallet: MoneroWalletRpc #= Utils.get_wallet_rpc() BINARY_BLOCK_CTX: BinaryBlockContext = BinaryBlockContext() + #region Fixtures + @pytest.fixture(scope="class", autouse=True) def before_all(self): MiningUtils.wait_for_height(101) MiningUtils.try_stop_mining() + @pytest.fixture(autouse=True) + def before_each(self, request: pytest.FixtureRequest): + logger.info(f"Before test {request.node.name}") # type: ignore + yield + logger.info(f"After test {request.node.name}") # type: ignore + + #endregion + + #region Tests + # Can get the daemon's version @pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") def test_get_version(self): @@ -713,3 +728,5 @@ def test_stop(self): raise Exception("Should have thrown error") except Exception as e: Utils.assert_not_equals("Should have thrown error", str(e)) + + #endregion diff --git a/tests/test_monero_wallet_common.py b/tests/test_monero_wallet_common.py index 615a596..4548161 100644 --- a/tests/test_monero_wallet_common.py +++ b/tests/test_monero_wallet_common.py @@ -1,5 +1,6 @@ from __future__ import annotations import pytest +import logging from configparser import ConfigParser from abc import ABC, abstractmethod from typing import Optional @@ -12,6 +13,8 @@ ) from utils import MoneroTestUtils as TestUtils, WalletEqualityUtils +logger: logging.Logger = logging.getLogger(__name__) + class BaseTestMoneroWallet(ABC): CREATED_WALLET_KEYS_ERROR: str = "Wallet created from keys is not connected to authenticated daemon" @@ -77,6 +80,12 @@ def test_config(self) -> BaseTestMoneroWallet.Config: parser.read('tests/config/test_monero_wallet_common.ini') return BaseTestMoneroWallet.Config.parse(parser) + @pytest.fixture(autouse=True) + def before_each(self, request: pytest.FixtureRequest): + logger.info(f"Before test {request.node.name}") # type: ignore + yield + logger.info(f"After test {request.node.name}") # type: ignore + #endregion #region Tests @@ -746,7 +755,9 @@ def test_get_subaddresses_by_indices(self): # remove a subaddress for query if possible if len(subaddresses) > 1: - subaddresses.remove(subaddresses[0]) + # TODO implement remove (needs operator == overload) + #subaddresses.remove(subaddresses[0]) + pass # get subaddress indices subaddress_indices: list[int] = [] @@ -944,12 +955,15 @@ def test_get_payment_uri(self): TestUtils.assert_equals(config1, config2) # test with subaddress and all fields - config1.destinations[0].address = wallet.get_subaddress(0, 1).address - config1.destinations[0].amount = 425000000000 + subaddress = wallet.get_subaddress(0, 1) + assert subaddress.address is not None + config1.destinations.append(MoneroDestination(subaddress.address, 425000000000)) config1.recipient_name = "John Doe" config1.note = "OMZG XMR FTW" uri = wallet.get_payment_uri(config1) config2 = wallet.parse_payment_uri(uri) + # TODO implement set_address like Java MoneroTxConfig and remove next line + config2.destinations.append(MoneroDestination(subaddress.address, 425000000000)) TestUtils.assert_equals(config1, config2) # test with undefined address diff --git a/tests/test_monero_wallet_keys.py b/tests/test_monero_wallet_keys.py index 53e907e..6bb020e 100644 --- a/tests/test_monero_wallet_keys.py +++ b/tests/test_monero_wallet_keys.py @@ -1,4 +1,6 @@ import pytest +import logging + from typing import Optional from typing_extensions import override from monero import ( @@ -9,6 +11,8 @@ from test_monero_wallet_common import BaseTestMoneroWallet +logger: logging.Logger = logging.getLogger(__name__) + class TestMoneroWalletKeys(BaseTestMoneroWallet): @@ -16,6 +20,12 @@ class TestMoneroWalletKeys(BaseTestMoneroWallet): _subaddress_indices: list[int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] _wallet: MoneroWalletKeys = Utils.get_wallet_keys() # type: ignore + @pytest.fixture(autouse=True) + def before_each(self, request: pytest.FixtureRequest): + logger.info(f"Before test {request.node.name}") # type: ignore + yield + logger.info(f"After test {request.node.name}") # type: ignore + #region Overrides @classmethod diff --git a/tests/test_monero_wallet_rpc.py b/tests/test_monero_wallet_rpc.py new file mode 100644 index 0000000..38d4fae --- /dev/null +++ b/tests/test_monero_wallet_rpc.py @@ -0,0 +1,171 @@ +import pytest +from monero import MoneroWallet, MoneroWalletConfig, MoneroDaemonRpc + +from typing_extensions import override +from utils import MoneroTestUtils as Utils +from test_monero_wallet_common import BaseTestMoneroWallet + + +class TestMoneroWalletRpc(BaseTestMoneroWallet): + + _wallet: MoneroWallet = Utils.get_wallet_rpc() + _daemon: MoneroDaemonRpc = Utils.get_daemon_rpc() + + #region Overrides + + @override + def get_test_wallet(self) -> MoneroWallet: + return Utils.get_wallet_rpc() + + @override + def _open_wallet(self, config: MoneroWalletConfig | None) -> MoneroWallet: + return Utils.open_wallet_rpc(config) + + @override + def _create_wallet(self, config: MoneroWalletConfig) -> MoneroWallet: + return Utils.create_wallet_rpc(config) + + @override + def _close_wallet(self, wallet: MoneroWallet, save: bool = False) -> None: + wallet.close(save) + + @override + def _get_seed_languages(self) -> list[str]: + return self._wallet.get_seed_languages() # type: ignore + + #endregion + + #region Tests + + # Can indicate if multisig import is needed for correct balance information + #@pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") + @pytest.mark.skip("TODO") + def test_is_multisig_needed(self): + # TODO test with multisig wallet + assert self._wallet.is_multisig_import_needed() is False, "Expected non-multisig wallet" + + # Can save the wallet + @pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") + def test_save(self): + self._wallet.save() + + #endregion + + #region Disabled Tests + + @pytest.mark.skip(reason="Not supported") + @override + def test_get_seed_language(self): + return super().test_get_seed_language() + + @pytest.mark.skip(reason="Not supported") + @override + def test_get_height_by_date(self): + return super().test_get_height_by_date() + + @pytest.mark.skip(reason="TODO monero-project") + @override + def test_get_public_view_key(self): + return super().test_get_public_view_key() + + @pytest.mark.skip(reason="TODO monero-project") + @override + def test_get_public_spend_key(self): + return super().test_get_public_spend_key() + + @pytest.mark.skip(reason="TODO") + @override + def test_create_wallet_random(self) -> None: + return super().test_create_wallet_random() + + @pytest.mark.skip(reason="TODO") + @override + def test_create_wallet_from_seed(self, test_config: BaseTestMoneroWallet.Config) -> None: + return super().test_create_wallet_from_seed(test_config) + + @pytest.mark.skip(reason="TODO") + @override + def test_create_wallet_from_keys(self) -> None: + return super().test_create_wallet_from_keys() + + @pytest.mark.skip(reason="TODO") + @override + def test_create_wallet_from_seed_with_offset(self) -> None: + return super().test_create_wallet_from_seed_with_offset() + + @pytest.mark.skip(reason="TODO") + @override + def test_wallet_equality_ground_truth(self): + return super().test_wallet_equality_ground_truth() + + @pytest.mark.skip(reason="TODO") + @override + def test_get_path(self) -> None: + return super().test_get_path() + + @pytest.mark.skip(reason="TODO") + @override + def test_get_payment_uri(self): + return super().test_get_payment_uri() + + @pytest.mark.skip(reason="TODO") + @override + def test_set_tx_note(self) -> None: + return super().test_set_tx_note() + + @pytest.mark.skip(reason="TODO") + @override + def test_set_tx_notes(self): + return super().test_set_tx_notes() + + @pytest.mark.skip(reason="TODO") + @override + def test_set_daemon_connection(self): + return super().test_set_daemon_connection() + + @pytest.mark.skip(reason="TODO") + @override + def test_mining(self): + return super().test_mining() + + @pytest.mark.skip(reason="TODO") + @override + def test_export_key_images(self): + return super().test_export_key_images() + + @pytest.mark.skip(reason="TODO (monero-project): https://github.com/monero-project/monero/issues/5812") + @override + def test_import_key_images(self): + return super().test_import_key_images() + + @pytest.mark.skip(reason="TODO") + @override + def test_get_new_key_images_from_last_import(self): + return super().test_get_new_key_images_from_last_import() + + @pytest.mark.skip(reason="TODO") + @override + def test_subaddress_lookahead(self) -> None: + return super().test_subaddress_lookahead() + + @pytest.mark.skip(reason="TODO") + @override + def test_set_account_label(self): + return super().test_set_account_label() + + @pytest.mark.skip(reason="TODO") + @override + def test_set_subaddress_label(self): + return super().test_set_subaddress_label() + + @pytest.mark.skip(reason="TODO") + @override + def test_get_subaddresses_by_indices(self): + return super().test_get_subaddresses_by_indices() + + @pytest.mark.skip(reason="TODO") + @override + def test_get_subaddress_address_out_of_range(self): + return super().test_get_subaddress_address_out_of_range() + + #endregion diff --git a/tests/utils/mining_utils.py b/tests/utils/mining_utils.py index 253ec6d..1ee65ef 100644 --- a/tests/utils/mining_utils.py +++ b/tests/utils/mining_utils.py @@ -101,6 +101,7 @@ def wait_for_height(cls, height: int) -> int: stop_mining = True while current_height < height: + print(f"[INFO] Waiting for height ({current_height}/{height})") block = daemon.wait_for_next_block_header() assert block.height is not None current_height = block.height diff --git a/tests/utils/monero_test_utils.py b/tests/utils/monero_test_utils.py index f70eaff..3c1f234 100644 --- a/tests/utils/monero_test_utils.py +++ b/tests/utils/monero_test_utils.py @@ -123,7 +123,6 @@ def get_network_type(cls) -> str: @classmethod def create_dir_if_not_exists(cls, dir_path: str) -> None: - print(f"create_dir_if_not_exists(): {dir_path}") if path_exists(dir_path): return @@ -152,7 +151,9 @@ def assert_is_none(cls, expr: Any, message: str = "assertion failed"): @classmethod def assert_equals(cls, expr1: Any, expr2: Any, message: str = "assertion failed"): if isinstance(expr1, SerializableStruct) and isinstance(expr2, SerializableStruct): - assert expr1.serialize() == expr2.serialize(), f"{message}: {expr1} == {expr2}" + str1 = expr1.serialize() + str2 = expr2.serialize() + assert str1 == str2, f"{message}: {str1} == {str2}" else: assert expr1 == expr2, f"{message}: {expr1} == {expr2}" @@ -294,6 +295,14 @@ def get_wallet_rpc(cls) -> MoneroWalletRpc: # return cached wallet rpc return cls._WALLET_RPC + @classmethod + def open_wallet_rpc(cls, config: Optional[MoneroWalletConfig]) -> MoneroWalletRpc: + raise NotImplementedError("Utils.open_wallet_rpc(): not implemented") + + @classmethod + def create_wallet_rpc(cls, config: MoneroWalletConfig) -> MoneroWalletRpc: + raise NotImplementedError("Utils.create_wallet_rpc(): not implemented") + @classmethod def create_wallet_ground_truth( cls, @@ -554,7 +563,7 @@ def test_update_download_result(cls, result: MoneroDaemonUpdateDownloadResult, p @classmethod def test_unsigned_big_integer(cls, value: Any, bool_val: bool = False): if not isinstance(value, int): - raise Exception("Value is not number") + raise Exception(f"Value is not number: {value}") if value < 0: raise Exception("Value cannot be negative") @@ -592,6 +601,7 @@ def test_account(cls, account: Optional[MoneroAccount], full: bool = True): msg2 = f"Subaddress unlocked balances {unlocked_balance} != account {account.index} unlocked balance {account.unlocked_balance}" assert account.balance == balance, msg1 assert account.unlocked_balance == unlocked_balance, msg2 + i += 1 # tag must be undefined or non-empty tag = account.tag @@ -950,7 +960,6 @@ def test_get_blocks_range(cls, daemon: MoneroDaemonRpc, start_height: Optional[i # fetch blocks by range real_start_height = 0 if start_height is None else start_height real_end_height = chain_height - 1 if end_height is None else end_height - print(f"get_blocks_by_range_chunked(): start_height: {start_height}, end_height: {end_height}") blocks = daemon.get_blocks_by_range_chunked(start_height, end_height) if chunked else daemon.get_blocks_by_range(start_height, end_height) cls.assert_equals(real_end_height - real_start_height + 1, len(blocks))