From b2f9e8e61193f90b9d608315d95766af1e02ab52 Mon Sep 17 00:00:00 2001 From: everoddandeven Date: Sat, 3 Jan 2026 22:49:02 +0100 Subject: [PATCH] Consolidate test environment * Ensure fakechain height is at least 100 before starting daemon tests * Code issues fixes --- src/cpp/daemon/py_monero_daemon.cpp | 2 +- src/cpp/daemon/py_monero_daemon_model.h | 2 +- src/cpp/py_monero.cpp | 20 ++-- src/cpp/wallet/py_monero_wallet.cpp | 16 +-- src/cpp/wallet/py_monero_wallet.h | 2 +- src/cpp/wallet/py_monero_wallet_model.cpp | 19 ++-- tests/test_monero_daemon_rpc.py | 8 +- tests/test_monero_utils.py | 28 +++--- tests/test_monero_wallet_common.py | 2 +- tests/test_monero_wallet_keys.py | 2 +- tests/utils/__init__.py | 2 + tests/utils/const.py | 2 +- tests/utils/mining_utils.py | 113 ++++++++++++++++++++++ tests/utils/monero_test_utils.py | 8 +- tests/utils/wallet_equality_utils.py | 2 +- 15 files changed, 170 insertions(+), 58 deletions(-) create mode 100644 tests/utils/mining_utils.py diff --git a/src/cpp/daemon/py_monero_daemon.cpp b/src/cpp/daemon/py_monero_daemon.cpp index 7676236..e2de4de 100644 --- a/src/cpp/daemon/py_monero_daemon.cpp +++ b/src/cpp/daemon/py_monero_daemon.cpp @@ -626,7 +626,7 @@ std::vector> PyMoneroDaemonRpc::get_peer_bans() { void PyMoneroDaemonRpc::set_peer_bans(std::vector> bans) { auto params = std::make_shared(bans); - PyMoneroJsonRequest request("set_bans"); + PyMoneroJsonRequest request("set_bans", params); std::shared_ptr response = m_rpc->send_json_request(request); check_response_status(response); } diff --git a/src/cpp/daemon/py_monero_daemon_model.h b/src/cpp/daemon/py_monero_daemon_model.h index 6a63abd..5fdc09b 100644 --- a/src/cpp/daemon/py_monero_daemon_model.h +++ b/src/cpp/daemon/py_monero_daemon_model.h @@ -759,7 +759,7 @@ class PyMoneroGetFeeEstimateParams : public PyMoneroJsonRequestParams { rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroSetBansParams : public PyMoneroRequestParams { +class PyMoneroSetBansParams : public PyMoneroJsonRequestParams { public: std::vector> m_bans; diff --git a/src/cpp/py_monero.cpp b/src/cpp/py_monero.cpp index 9e40c53..b3836ec 100644 --- a/src/cpp/py_monero.cpp +++ b/src/cpp/py_monero.cpp @@ -4,7 +4,7 @@ try { \ return expr; \ } catch (const PyMoneroRpcError& e) { \ - throw e; \ + throw; \ } \ catch (const std::exception& e) { \ throw PyMoneroError(e.what()); \ @@ -1456,10 +1456,10 @@ PYBIND11_MODULE(monero, m) { assert_wallet_is_not_closed(&self); MONERO_CATCH_AND_RETHROW(self.set_daemon_connection(connection)); }, py::arg("connection")) - .def("set_daemon_connection", [](PyMoneroWallet& self, std::string uri, std::string username, std::string password, std::string proxy) { + .def("set_daemon_connection", [](PyMoneroWallet& self, const std::string& uri, const std::string& username, const std::string& password, const std::string& proxy) { assert_wallet_is_not_closed(&self); MONERO_CATCH_AND_RETHROW(self.set_daemon_connection(uri, username, password, proxy)); - }, py::arg("uri") = "", py::arg("username") = "", py::arg("password") = "", py::arg("proxy") = "") + }, py::arg("uri") = "", py::arg("username") = "", py::arg("password") = "", py::arg("proxy") = "") .def("get_daemon_connection", [](PyMoneroWallet& self) { assert_wallet_is_not_closed(&self); MONERO_CATCH_AND_RETHROW(self.get_daemon_connection()); @@ -1894,8 +1894,8 @@ PYBIND11_MODULE(monero, m) { }, py::arg("uri")) .def("get_attribute", [](PyMoneroWallet& self, const std::string& key) { assert_wallet_is_not_closed(&self); - std::string val; try { + std::string val; self.get_attribute(key, val); return val; } catch (const std::exception& e) { @@ -2031,10 +2031,10 @@ PYBIND11_MODULE(monero, m) { return &self; } catch(const PyMoneroRpcError& ex) { - throw ex; + throw; } catch(const PyMoneroError& ex) { - throw ex; + throw; } catch(const std::exception& ex) { throw py::value_error(ex.what()); @@ -2047,10 +2047,10 @@ PYBIND11_MODULE(monero, m) { return &self; } catch(const PyMoneroRpcError& ex) { - throw ex; + throw; } catch(const PyMoneroError& ex) { - throw ex; + throw; } catch(const std::exception& ex) { throw py::value_error(ex.what()); @@ -2063,10 +2063,10 @@ PYBIND11_MODULE(monero, m) { return &self; } catch(const PyMoneroRpcError& ex) { - throw ex; + throw; } catch(const PyMoneroError& ex) { - throw ex; + throw; } catch(const std::exception& ex) { throw py::value_error(ex.what()); diff --git a/src/cpp/wallet/py_monero_wallet.cpp b/src/cpp/wallet/py_monero_wallet.cpp index 0bb5c2e..ba34a64 100644 --- a/src/cpp/wallet/py_monero_wallet.cpp +++ b/src/cpp/wallet/py_monero_wallet.cpp @@ -249,8 +249,8 @@ void PyMoneroWalletPoller::poll() { // announce new unlocked outputs for (const auto &unlocked_tx : unlocked_txs) { std::string tx_hash = unlocked_tx->m_hash.get(); - m_prev_confirmed_notifications.erase(std::remove_if(m_prev_confirmed_notifications.begin(), m_prev_confirmed_notifications.end(), [&tx_hash](std::string iter){ return iter == tx_hash; }), m_prev_confirmed_notifications.end()); - m_prev_unconfirmed_notifications.erase(std::remove_if(m_prev_unconfirmed_notifications.begin(), m_prev_unconfirmed_notifications.end(), [&tx_hash](std::string iter){ return iter == tx_hash; }), m_prev_unconfirmed_notifications.end()); + m_prev_confirmed_notifications.erase(std::remove_if(m_prev_confirmed_notifications.begin(), m_prev_confirmed_notifications.end(), [&tx_hash](const std::string& iter){ return iter == tx_hash; }), m_prev_confirmed_notifications.end()); + m_prev_unconfirmed_notifications.erase(std::remove_if(m_prev_unconfirmed_notifications.begin(), m_prev_unconfirmed_notifications.end(), [&tx_hash](const std::string& iter){ return iter == tx_hash; }), m_prev_unconfirmed_notifications.end()); notify_outputs(unlocked_tx); } @@ -266,8 +266,8 @@ void PyMoneroWalletPoller::poll() { } } -std::shared_ptr PyMoneroWalletPoller::get_tx(const std::vector> txs, std::string tx_hash) { - for (auto &tx : txs) { +std::shared_ptr PyMoneroWalletPoller::get_tx(const std::vector>& txs, const std::string& tx_hash) { + for (auto tx : txs) { if (tx->m_hash == tx_hash) return tx; } @@ -428,7 +428,7 @@ std::vector PyMoneroWalletRpc::get_seed_languages() const { void PyMoneroWalletRpc::stop() { PyMoneroJsonRequest request("stop_wallet"); - std::shared_ptr response = m_rpc->send_json_request(request); + m_rpc->send_json_request(request); } bool PyMoneroWalletRpc::is_view_only() const { @@ -440,7 +440,7 @@ bool PyMoneroWalletRpc::is_view_only() const { catch (const PyMoneroRpcError& e) { if (e.code == -29) return true; if (e.code == -1) return false; - throw e; + throw; } } @@ -1471,14 +1471,14 @@ std::shared_ptr PyMoneroWalletRpc::parse_payment_uri(const std void PyMoneroWalletRpc::set_attribute(const std::string& key, const std::string& val) { auto params = std::make_shared(key, val); - PyMoneroJsonRequest request("set_attribute"); + PyMoneroJsonRequest request("set_attribute", params); std::shared_ptr response = m_rpc->send_json_request(request); } bool PyMoneroWalletRpc::get_attribute(const std::string& key, std::string& value) const { try { auto params = std::make_shared(key); - PyMoneroJsonRequest request("get_attribute"); + PyMoneroJsonRequest request("get_attribute", params); std::shared_ptr response = m_rpc->send_json_request(request); if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); auto res = response->m_result.get(); diff --git a/src/cpp/wallet/py_monero_wallet.h b/src/cpp/wallet/py_monero_wallet.h index 15b7271..3ce3bb0 100644 --- a/src/cpp/wallet/py_monero_wallet.h +++ b/src/cpp/wallet/py_monero_wallet.h @@ -611,7 +611,7 @@ class PyMoneroWalletPoller { boost::optional m_prev_height; std::vector> m_prev_locked_txs; - std::shared_ptr get_tx(const std::vector> txs, std::string tx_hash); + std::shared_ptr get_tx(const std::vector>& txs, const std::string& tx_hash); void loop(); void on_new_block(uint64_t height); void notify_outputs(const std::shared_ptr &tx); diff --git a/src/cpp/wallet/py_monero_wallet_model.cpp b/src/cpp/wallet/py_monero_wallet_model.cpp index 4193c8e..56134be 100644 --- a/src/cpp/wallet/py_monero_wallet_model.cpp +++ b/src/cpp/wallet/py_monero_wallet_model.cpp @@ -466,10 +466,9 @@ void PyMoneroTxSet::from_sent_txs(const boost::property_tree::ptree& node, const for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; - int i = 0; if (key == std::string("tx_hash_list") && num_txs == 0) { auto node2 = it->second; - i = 0; + int i = 0; for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) { auto tx = txs[i]; tx->m_hash = it2->second.data(); @@ -478,7 +477,7 @@ void PyMoneroTxSet::from_sent_txs(const boost::property_tree::ptree& node, const } else if (key == std::string("tx_key_list") && num_txs == 0) { auto node2 = it->second; - i = 0; + int i = 0; for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) { auto tx = txs[i]; tx->m_key = it2->second.data(); @@ -487,7 +486,7 @@ void PyMoneroTxSet::from_sent_txs(const boost::property_tree::ptree& node, const } else if (key == std::string("tx_blob_list") && num_txs == 0) { auto node2 = it->second; - i = 0; + int i = 0; for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) { auto tx = txs[i]; tx->m_full_hex = it2->second.data(); @@ -496,7 +495,7 @@ void PyMoneroTxSet::from_sent_txs(const boost::property_tree::ptree& node, const } else if (key == std::string("tx_metadata_list") && num_txs == 0) { auto node2 = it->second; - i = 0; + int i = 0; for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) { auto tx = txs[i]; tx->m_metadata = it2->second.data(); @@ -505,7 +504,7 @@ void PyMoneroTxSet::from_sent_txs(const boost::property_tree::ptree& node, const } else if (key == std::string("fee_list") && num_txs == 0) { auto node2 = it->second; - i = 0; + int i = 0; for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) { auto tx = txs[i]; tx->m_fee = it2->second.get_value(); @@ -514,7 +513,7 @@ void PyMoneroTxSet::from_sent_txs(const boost::property_tree::ptree& node, const } else if (key == std::string("amount_list") && num_txs == 0) { auto node2 = it->second; - i = 0; + int i = 0; for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) { auto tx = txs[i]; auto outgoing_transfer = std::make_shared(); @@ -526,7 +525,7 @@ void PyMoneroTxSet::from_sent_txs(const boost::property_tree::ptree& node, const } else if (key == std::string("weight_list") && num_txs == 0) { auto node2 = it->second; - i = 0; + int i = 0; for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) { auto tx = txs[i]; tx->m_weight = it2->second.get_value(); @@ -535,7 +534,7 @@ void PyMoneroTxSet::from_sent_txs(const boost::property_tree::ptree& node, const } else if (key == std::string("spent_key_images_list") && num_txs == 0) { auto node2 = it->second; - i = 0; + int i = 0; for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) { auto tx = txs[i]; if (tx->m_inputs.size() > 0) throw std::runtime_error("Expected no inputs in sent tx"); @@ -562,7 +561,7 @@ void PyMoneroTxSet::from_sent_txs(const boost::property_tree::ptree& node, const } else if (key == std::string("amounts_by_dest_list") && num_txs == 0) { auto node2 = it->second; - i = 0; + int i = 0; int destination_idx = 0; for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) { diff --git a/tests/test_monero_daemon_rpc.py b/tests/test_monero_daemon_rpc.py index eb92328..69c2ce0 100644 --- a/tests/test_monero_daemon_rpc.py +++ b/tests/test_monero_daemon_rpc.py @@ -10,15 +10,19 @@ MoneroHardForkInfo, MoneroAltChain, MoneroTx, MoneroSubmitTxResult, MoneroTxPoolStats ) -from utils import MoneroTestUtils as Utils, TestContext, BinaryBlockContext +from utils import MoneroTestUtils as Utils, TestContext, BinaryBlockContext, MiningUtils -# TODO enable rpc daemon tests class TestMoneroDaemonRpc: _daemon: MoneroDaemonRpc = Utils.get_daemon_rpc() _wallet: MoneroWalletRpc #= Utils.get_wallet_rpc() BINARY_BLOCK_CTX: BinaryBlockContext = BinaryBlockContext() + @pytest.fixture(scope="class", autouse=True) + def before_all(self): + MiningUtils.wait_for_height(101) + MiningUtils.try_stop_mining() + # 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): diff --git a/tests/test_monero_utils.py b/tests/test_monero_utils.py index f70706f..cdad9f1 100644 --- a/tests/test_monero_utils.py +++ b/tests/test_monero_utils.py @@ -16,7 +16,7 @@ class Config: stagenet: AddressBook = AddressBook() keys: KeysBook = KeysBook() serialization_msg: str = '' - + @classmethod def parse(cls, parser: ConfigParser) -> TestMoneroUtils.Config: config = cls() @@ -111,19 +111,19 @@ def test_serialize_text_long(self, config: TestMoneroUtils.Config): msg = config.serialization_msg json_map: dict[str, str] = { "msg": f"{msg}\n" + - f"{msg}\n" + - f"{msg}\n" + - f"{msg}\n" + - f"{msg}\n" + - f"{msg}\n" + - f"{msg}\n" + - f"{msg}\n" + - f"{msg}\n" + - f"{msg}\n" + - f"{msg}\n" + - f"{msg}\n" + - f"{msg}\n" + - f"{msg}\n" + f"{msg}\n" + + f"{msg}\n" + + f"{msg}\n" + + f"{msg}\n" + + f"{msg}\n" + + f"{msg}\n" + + f"{msg}\n" + + f"{msg}\n" + + f"{msg}\n" + + f"{msg}\n" + + f"{msg}\n" + + f"{msg}\n" + + f"{msg}\n" } binary: bytes = MoneroUtils.dict_to_binary(json_map) diff --git a/tests/test_monero_wallet_common.py b/tests/test_monero_wallet_common.py index 23262b0..cab3c08 100644 --- a/tests/test_monero_wallet_common.py +++ b/tests/test_monero_wallet_common.py @@ -277,7 +277,7 @@ def test_create_wallet_from_keys(self) -> None: except Exception as e: e2 = e - + self._close_wallet(wallet) if e2 is not None: raise e2 diff --git a/tests/test_monero_wallet_keys.py b/tests/test_monero_wallet_keys.py index 0d02d40..3ba6b25 100644 --- a/tests/test_monero_wallet_keys.py +++ b/tests/test_monero_wallet_keys.py @@ -67,7 +67,7 @@ def _create_wallet(self, config: Optional[MoneroWalletConfig]): config = MoneroWalletConfig() print("create_wallet(self): created config") - print(f"""create_wallet(): + print(f"""create_wallet(): seed: {config.seed}, address: {config.primary_address}, view key: {config.private_view_key}, diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py index ef6a1a0..fcdffd6 100644 --- a/tests/utils/__init__.py +++ b/tests/utils/__init__.py @@ -1,4 +1,5 @@ from .monero_test_utils import MoneroTestUtils +from .mining_utils import MiningUtils from .wallet_sync_printer import WalletSyncPrinter from .connection_change_collector import ConnectionChangeCollector from .address_book import AddressBook @@ -14,6 +15,7 @@ __all__ = [ 'MoneroTestUtils', + 'MiningUtils', 'WalletSyncPrinter', 'ConnectionChangeCollector', 'AddressBook', diff --git a/tests/utils/const.py b/tests/utils/const.py index 74d505b..0a3e37b 100644 --- a/tests/utils/const.py +++ b/tests/utils/const.py @@ -1 +1 @@ -MINING_ADDRESS = "9tsUiG9bwcU7oTbAdBwBk2PzxFtysge5qcEsHEpetmEKgerHQa1fDqH7a4FiquZmms7yM22jdifVAD7jAb2e63GSJMuhY75" +MINING_ADDRESS = "42U9v3qs5CjZEePHBZHwuSckQXebuZu299NSmVEmQ41YJZQhKcPyujyMSzpDH4VMMVSBo3U3b54JaNvQLwAjqDhKS3rvM3L" diff --git a/tests/utils/mining_utils.py b/tests/utils/mining_utils.py new file mode 100644 index 0000000..253ec6d --- /dev/null +++ b/tests/utils/mining_utils.py @@ -0,0 +1,113 @@ +from typing import Optional +from time import sleep +from monero import MoneroDaemonRpc +from .const import MINING_ADDRESS + + +class MiningUtils: + """ + Mining utilities. + """ + _DAEMON: Optional[MoneroDaemonRpc] = None + """Internal mining daemon.""" + + @classmethod + def _get_daemon(cls) -> MoneroDaemonRpc: + """ + Get internal mining daemon. + """ + if cls._DAEMON is None: + cls._DAEMON = MoneroDaemonRpc("127.0.0.1:18089") + + return cls._DAEMON + + @classmethod + def is_mining(cls) -> bool: + """ + Check if mining is enabled. + """ + # max tries 3 + daemon = cls._get_daemon() + for i in range(3): + try: + status = daemon.get_mining_status() + return status.is_active is True + + except Exception as e: + if i == 2: + raise e + + return False + + @classmethod + def start_mining(cls) -> None: + """ + Start internal mining. + """ + if cls.is_mining(): + raise Exception("Mining already started") + + daemon = cls._get_daemon() + daemon.start_mining(MINING_ADDRESS, 1, False, False) + + @classmethod + def stop_mining(cls) -> None: + """ + Stop internal mining. + """ + if not cls.is_mining(): + raise Exception("Mining already stopped") + + daemon = cls._get_daemon() + daemon.stop_mining() + + @classmethod + def try_stop_mining(cls) -> bool: + """ + Try stop internal mining. + """ + try: + cls.stop_mining() + return True + except Exception as e: + print(f"MiningUtils.stop_mining(): {e}") + return False + + @classmethod + def try_start_mining(cls) -> bool: + """ + Try start internal mining. + """ + try: + cls.start_mining() + return True + except Exception as e: + print(f"MiningUtils.start_mining(): {e}") + return False + + @classmethod + def wait_for_height(cls, height: int) -> int: + """ + Wait for blockchain height. + """ + daemon = cls._get_daemon() + current_height = daemon.get_height() + if height <= current_height: + return current_height + + stop_mining: bool = False + if not cls.is_mining(): + cls.start_mining() + stop_mining = True + + while current_height < height: + block = daemon.wait_for_next_block_header() + assert block.height is not None + current_height = block.height + + if stop_mining: + cls.stop_mining() + sleep(3) + current_height = daemon.get_height() + + return current_height diff --git a/tests/utils/monero_test_utils.py b/tests/utils/monero_test_utils.py index c7a6d55..a184360 100644 --- a/tests/utils/monero_test_utils.py +++ b/tests/utils/monero_test_utils.py @@ -167,7 +167,7 @@ def get_random_string(cls, n: int = 25) -> str: return ''.join(choices(cls.BASE58_ALPHABET, k=n)) @classmethod - def get_wallets(cls, type: str) -> list[MoneroWallet]: + def get_wallets(cls, wallet_type: str) -> list[MoneroWallet]: raise NotImplementedError() @classmethod @@ -187,12 +187,6 @@ def get_daemon_rpc(cls) -> MoneroDaemonRpc: if cls._DAEMON_RPC is None: cls._DAEMON_RPC = MoneroDaemonRpc(cls.DAEMON_RPC_URI, cls.DAEMON_RPC_USERNAME, cls.DAEMON_RPC_PASSWORD) - if cls._DAEMON_RPC.is_connected(): - height = cls._DAEMON_RPC.get_height() - while height <= 100: - cls._DAEMON_RPC.wait_for_next_block_header() - height = cls._DAEMON_RPC.get_height() - return cls._DAEMON_RPC @classmethod diff --git a/tests/utils/wallet_equality_utils.py b/tests/utils/wallet_equality_utils.py index 8c00d07..f196ab8 100644 --- a/tests/utils/wallet_equality_utils.py +++ b/tests/utils/wallet_equality_utils.py @@ -125,7 +125,7 @@ def test_subaddresses_equal_on_chain( assert False is subaddresses1[j].is_used return - + i += 1 @classmethod