From 8f0dad1ead62fbb4a3799d435758785e4ac8e81f Mon Sep 17 00:00:00 2001 From: everoddandeven Date: Sat, 3 Jan 2026 19:51:42 +0100 Subject: [PATCH] Daemon rpc tests and fixes --- .github/workflows/test.yml | 4 +- Dockerfile.linux | 2 +- README.md | 4 +- src/cpp/daemon/py_monero_daemon.cpp | 22 ++-- src/cpp/daemon/py_monero_daemon_model.cpp | 124 +++++++++++++++++----- src/cpp/py_monero.cpp | 2 + tests/test_monero_daemon_rpc.py | 19 ++-- tests/utils/monero_test_utils.py | 36 ++++--- 8 files changed, 150 insertions(+), 63 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0aa83f6..6ade385 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,7 +21,7 @@ jobs: - name: Install dependencies run: | sudo apt update - sudo apt install -y build-essential cmake pkg-config libssl-dev libzmq3-dev libunbound-dev libsodium-dev libunwind8-dev liblzma-dev libreadline6-dev libexpat1-dev libpgm-dev qttools5-dev-tools libhidapi-dev libusb-1.0-0-dev libprotobuf-dev protobuf-compiler libudev-dev libboost-chrono-dev libboost-date-time-dev libboost-filesystem-dev libboost-locale-dev libboost-program-options-dev libboost-regex-dev libboost-serialization-dev libboost-system-dev libboost-thread-dev python3 ccache doxygen graphviz git curl autoconf libtool gperf nettle-dev libevent-dev debhelper python3-all python3-pip python3-pybind11 python3-pytest + sudo apt install -y build-essential cmake pkg-config libssl-dev libzmq3-dev libunbound-dev libsodium-dev libunwind8-dev liblzma-dev libreadline6-dev libexpat1-dev libpgm-dev qttools5-dev-tools libhidapi-dev libusb-1.0-0-dev libprotobuf-dev protobuf-compiler libudev-dev libboost-chrono-dev libboost-date-time-dev libboost-filesystem-dev libboost-locale-dev libboost-program-options-dev libboost-regex-dev libboost-serialization-dev libboost-system-dev libboost-thread-dev python3 ccache doxygen graphviz git curl autoconf libtool gperf nettle-dev libevent-dev debhelper python3-all python3-pip python3-pybind11 python3-pytest python3-pytest-rerunfailures pip3 install pybind11-stubgen pytest --break-system-packages - name: Install expat @@ -85,6 +85,8 @@ jobs: docker compose -f tests/docker-compose.yml up -d node_1 node_2 xmr_wallet_1 xmr_wallet_2 - name: Run tests + env: + REGTEST: "true" run: | pytest diff --git a/Dockerfile.linux b/Dockerfile.linux index 12a52dd..eed4962 100644 --- a/Dockerfile.linux +++ b/Dockerfile.linux @@ -12,7 +12,7 @@ RUN apt-get update && apt-get install -y \ libboost-chrono-dev libboost-date-time-dev libboost-filesystem-dev \ libboost-locale-dev libboost-program-options-dev libboost-regex-dev \ libboost-serialization-dev libboost-system-dev libboost-thread-dev \ - python3 python3-pip python3-all python3-pybind11 python3-pytest \ + python3 python3-pip python3-all python3-pybind11 python3-pytest python3-pytest-rerunfailures \ ccache doxygen graphviz git curl autoconf libtool gperf nettle-dev libevent-dev \ debhelper bison flex wget \ && rm -rf /var/lib/apt/lists/* diff --git a/README.md b/README.md index ac002c4..1c53f85 100644 --- a/README.md +++ b/README.md @@ -175,12 +175,12 @@ For example: `export LD_PRELOAD=/path/to/libjemalloc.a` then run your app. ```bash # System-wide installation Ubuntu/Debian - sudo apt install -y python3-pytest + sudo apt install -y python3-pytest python3-pytest-rerunfailures ``` ```bash # System-wide installation Fedora/RedHat - sudo dnf install -y python3-pytest + sudo dnf install -y python3-pytest python3-pytest-rerunfailures ``` 2. Clone the project repository: ```bash diff --git a/src/cpp/daemon/py_monero_daemon.cpp b/src/cpp/daemon/py_monero_daemon.cpp index 1acd9fb..7676236 100644 --- a/src/cpp/daemon/py_monero_daemon.cpp +++ b/src/cpp/daemon/py_monero_daemon.cpp @@ -184,7 +184,7 @@ std::string PyMoneroDaemonRpc::get_block_hash(uint64_t height) { } std::shared_ptr PyMoneroDaemonRpc::get_block_template(std::string& wallet_address, int reserve_size) { - auto params = std::make_shared(wallet_address, reserve_size); + auto params = std::make_shared(wallet_address, reserve_size); PyMoneroJsonRequest request("get_block_template", params); std::shared_ptr response = m_rpc->send_json_request(request); @@ -197,7 +197,7 @@ std::shared_ptr PyMoneroDaemonRpc::get_block_template(std } std::shared_ptr PyMoneroDaemonRpc::get_block_template(std::string& wallet_address) { - auto params = std::make_shared(wallet_address); + auto params = std::make_shared(wallet_address); PyMoneroJsonRequest request("get_block_template", params); std::shared_ptr response = m_rpc->send_json_request(request); @@ -224,7 +224,7 @@ std::shared_ptr PyMoneroDaemonRpc::get_last_block_h std::shared_ptr PyMoneroDaemonRpc::get_block_header_by_hash(const std::string& hash) { std::shared_ptr params = std::make_shared(hash); - PyMoneroJsonRequest request("/get_block_header_by_hash", params); + PyMoneroJsonRequest request("get_block_header_by_hash", params); auto response = m_rpc->send_json_request(request); if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); @@ -238,7 +238,7 @@ std::shared_ptr PyMoneroDaemonRpc::get_block_header std::shared_ptr PyMoneroDaemonRpc::get_block_header_by_height(uint64_t height) { std::shared_ptr params = std::make_shared(height); - PyMoneroJsonRequest request("/get_block_header_by_height", params); + PyMoneroJsonRequest request("get_block_header_by_height", params); auto response = m_rpc->send_json_request(request); if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); @@ -250,7 +250,7 @@ std::shared_ptr PyMoneroDaemonRpc::get_block_header } std::vector> PyMoneroDaemonRpc::get_block_headers_by_range(uint64_t start_height, uint64_t end_height) { - auto params = std::make_shared(); + auto params = std::make_shared(start_height, end_height); PyMoneroJsonRequest request("get_block_headers_range", params); auto response = m_rpc->send_json_request(request); @@ -265,7 +265,7 @@ std::vector> PyMoneroDaemonRpc::get std::shared_ptr PyMoneroDaemonRpc::get_block_by_hash(const std::string& hash) { std::shared_ptr params = std::make_shared(hash); - PyMoneroJsonRequest request("/get_block", params); + PyMoneroJsonRequest request("get_block", params); auto response = m_rpc->send_json_request(request); if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); @@ -282,7 +282,7 @@ std::vector> PyMoneroDaemonRpc::get_blocks std::shared_ptr PyMoneroDaemonRpc::get_block_by_height(uint64_t height) { std::shared_ptr params = std::make_shared(height); - PyMoneroJsonRequest request("/get_block", params); + PyMoneroJsonRequest request("get_block", params); auto response = m_rpc->send_json_request(request); if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); @@ -472,7 +472,7 @@ std::shared_ptr PyMoneroDaemonRpc::get_info() { } std::shared_ptr PyMoneroDaemonRpc::get_sync_info() { - PyMoneroJsonRequest request("get_sync_info"); + PyMoneroJsonRequest request("sync_info"); std::shared_ptr response = m_rpc->send_json_request(request); if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); @@ -498,7 +498,7 @@ std::shared_ptr PyMoneroDaemonRpc::get_hard_fork_info() { std::vector> PyMoneroDaemonRpc::get_alt_chains() { std::vector> result; - PyMoneroJsonRequest request("/get_alternate_chains"); + PyMoneroJsonRequest request("get_alternate_chains"); std::shared_ptr response = m_rpc->send_json_request(request); if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); @@ -537,6 +537,8 @@ int PyMoneroDaemonRpc::get_download_limit() { } int PyMoneroDaemonRpc::set_download_limit(int limit) { + if (limit == -1) return reset_download_limit(); + if (limit <= 0) throw std::runtime_error("Download limit must be an integer greater than 0"); auto res = set_bandwidth_limits(0, limit); if (res->m_down != boost::none) return res->m_down.get(); throw std::runtime_error("Could not set download limit"); @@ -555,6 +557,8 @@ int PyMoneroDaemonRpc::get_upload_limit() { } int PyMoneroDaemonRpc::set_upload_limit(int limit) { + if (limit == -1) return reset_upload_limit(); + if (limit <= 0) throw std::runtime_error("Upload limit must be an integer greater than 0"); auto res = set_bandwidth_limits(limit, 0); if (res->m_up != boost::none) return res->m_up.get(); throw std::runtime_error("Could not set download limit"); diff --git a/src/cpp/daemon/py_monero_daemon_model.cpp b/src/cpp/daemon/py_monero_daemon_model.cpp index 7a0567b..0fb8647 100644 --- a/src/cpp/daemon/py_monero_daemon_model.cpp +++ b/src/cpp/daemon/py_monero_daemon_model.cpp @@ -274,14 +274,14 @@ void PyMoneroBlockHeader::from_property_tree(const boost::property_tree::ptree& for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; if (key == std::string("block_header")) { - from_property_tree(it->second, header); + PyMoneroBlockHeader::from_property_tree(it->second, header); return; } else if (key == std::string("hash")) header->m_hash = it->second.data(); else if (key == std::string("height")) header->m_height = it->second.get_value(); else if (key == std::string("timestamp")) header->m_timestamp = it->second.get_value(); - else if (key == std::string("size")) header->m_size = it->second.get_value(); - else if (key == std::string("weight")) header->m_weight = it->second.get_value(); + else if (key == std::string("block_size")) header->m_size = it->second.get_value(); + else if (key == std::string("block_weight")) header->m_weight = it->second.get_value(); else if (key == std::string("long_term_weight")) header->m_long_term_weight = it->second.get_value(); else if (key == std::string("depth")) header->m_depth = it->second.get_value(); else if (key == std::string("difficulty")) header->m_difficulty = it->second.get_value(); @@ -290,11 +290,14 @@ void PyMoneroBlockHeader::from_property_tree(const boost::property_tree::ptree& else if (key == std::string("minor_version")) header->m_minor_version = it->second.get_value(); else if (key == std::string("nonce")) header->m_nonce = it->second.get_value(); else if (key == std::string("miner_tx_hash")) header->m_miner_tx_hash = it->second.data(); - else if (key == std::string("num_txes")) header->m_orphan_status = it->second.get_value(); + else if (key == std::string("num_txes")) header->m_num_txs = it->second.get_value(); else if (key == std::string("orphan_status")) header->m_orphan_status = it->second.get_value(); else if (key == std::string("prev_hash")) header->m_prev_hash = it->second.data(); else if (key == std::string("reward")) header->m_reward = it->second.get_value(); - else if (key == std::string("pow_hash")) header->m_pow_hash = it->second.data(); + else if (key == std::string("pow_hash")) { + std::string pow_hash = it->second.data(); + if (!pow_hash.empty()) header->m_pow_hash = pow_hash; + } } } @@ -307,7 +310,7 @@ void PyMoneroBlockHeader::from_property_tree(const boost::property_tree::ptree& for(boost::property_tree::ptree::const_iterator it2 = node2.begin(); it2 != node2.end(); ++it2) { auto header = std::make_shared(); - PyMoneroBlockHeader::from_property_tree(node2, header); + PyMoneroBlockHeader::from_property_tree(it2->second, header); headers.push_back(header); } } @@ -326,15 +329,23 @@ void PyMoneroBlock::from_property_tree(const boost::property_tree::ptree& node, else if (key == std::string("txs")) { for (const auto &tx_node : it->second) { auto tx = std::make_shared(); - monero::monero_tx::from_property_tree(tx_node.second, tx); + PyMoneroTx::from_property_tree(tx_node.second, tx); block->m_txs.push_back(tx); } } else if (key == std::string("miner_tx")) { auto tx = std::make_shared(); - monero::monero_tx::from_property_tree(it->second, tx); + PyMoneroTx::from_property_tree(it->second, tx); + tx->m_is_miner_tx = true; block->m_miner_tx = tx; } + else if (key == std::string("json")) { + auto json = it->second.data(); + std::istringstream iss = json.empty() ? std::istringstream() : std::istringstream(json); + boost::property_tree::ptree json_node; + boost::property_tree::read_json(iss, json_node); + PyMoneroBlock::from_property_tree(json_node, block); + } } } @@ -431,7 +442,7 @@ void PyMoneroTx::from_property_tree(const boost::property_tree::ptree& node, con if (block == nullptr) block = std::make_shared(); tx->m_version = it->second.get_value(); } - else if (key == std::string("vin")) { + else if (key == std::string("vin") && it->second.size() != 1) { auto node2 = it->second; std::vector> inputs; for(auto it2 = node2.begin(); it2 != node2.end(); ++it2) { @@ -507,6 +518,12 @@ void PyMoneroTx::from_property_tree(const boost::property_tree::ptree& node, con tx->m_last_failed_hash = hash; } } + else if (key == std::string("extra")) { + auto extra_node = it->second; + for(auto it_extra = extra_node.begin(); it_extra != extra_node.end(); ++it_extra) { + tx->m_extra.push_back(it_extra->second.get_value()); + } + } else if (key == std::string("max_used_block_height")) tx->m_max_used_block_height = it->second.get_value(); else if (key == std::string("max_used_block_id_hash")) tx->m_max_used_block_hash = it->second.data(); else if (key == std::string("prunable_hash")) tx->m_prunable_hash = it->second.data(); @@ -681,7 +698,7 @@ void PyMoneroBlockTemplate::from_property_tree(const boost::property_tree::ptree else if (key == std::string("expected_reward")) tmplt->m_expected_reward = it->second.get_value(); else if (key == std::string("height")) tmplt->m_height = it->second.get_value(); else if (key == std::string("prev_hash")) tmplt->m_prev_hash = it->second.data(); - else if (key == std::string("reserved_offset")) tmplt->m_height = it->second.get_value(); + else if (key == std::string("reserved_offset")) tmplt->m_reserved_offset = it->second.get_value(); else if (key == std::string("seed_height")) tmplt->m_seed_height = it->second.get_value(); else if (key == std::string("seed_hash")) tmplt->m_seed_hash = it->second.data(); else if (key == std::string("next_seed_hash")) tmplt->m_next_seed_hash = it->second.data(); @@ -705,12 +722,48 @@ void PyMoneroPeer::from_property_tree(const boost::property_tree::ptree& node, c for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; if (key == std::string("host")) peer->m_host = it->second.data(); - else if (key == std::string("id")) peer->m_id = it->second.data(); + else if (key == std::string("address")) peer->m_address = it->second.data(); + else if (key == std::string("current_download")) peer->m_current_download = it->second.get_value(); + else if (key == std::string("current_upload")) peer->m_current_upload = it->second.get_value(); + else if (key == std::string("avg_download")) peer->m_avg_download = it->second.get_value(); + else if (key == std::string("avg_upload")) peer->m_avg_upload = it->second.get_value(); + else if (key == std::string("connection_id")) peer->m_hash = it->second.data(); + else if (key == std::string("height")) peer->m_height = it->second.get_value(); + else if (key == std::string("incoming")) peer->m_is_incoming = it->second.get_value(); + else if (key == std::string("live_time")) peer->m_live_time = it->second.get_value(); + else if (key == std::string("local_ip")) peer->m_is_local_ip = it->second.get_value(); + else if (key == std::string("localhost")) peer->m_is_local_host = it->second.get_value(); + else if (key == std::string("recv_count")) peer->m_num_receives = it->second.get_value(); + else if (key == std::string("send_count")) peer->m_num_sends = it->second.get_value(); + else if (key == std::string("recv_idle_time")) peer->m_receive_idle_time = it->second.get_value(); + else if (key == std::string("send_idle_time")) peer->m_send_idle_time = it->second.get_value(); + else if (key == std::string("state")) peer->m_state = it->second.data(); + else if (key == std::string("support_flags")) peer->m_num_support_flags = it->second.get_value(); + else if (key == std::string("id") || key == std::string("peer_id")) peer->m_id = it->second.data(); else if (key == std::string("last_seen")) peer->m_last_seen_timestamp = it->second.get_value(); else if (key == std::string("port")) peer->m_port = it->second.get_value(); else if (key == std::string("rpc_port")) peer->m_rpc_port = it->second.get_value(); else if (key == std::string("pruning_seed")) peer->m_pruning_seed = it->second.get_value(); else if (key == std::string("rpc_credits_per_hash")) peer->m_rpc_credits_per_hash = it->second.get_value(); + else if (key == std::string("address_type")) { + int rpc_type = it->second.get_value(); + if (rpc_type == 0) { + peer->m_connection_type = PyMoneroConnectionType::INVALID; + } + else if (rpc_type == 1) { + peer->m_connection_type = PyMoneroConnectionType::IPV4; + } + else if (rpc_type == 2) { + peer->m_connection_type = PyMoneroConnectionType::IPV6; + } + else if (rpc_type == 3) { + peer->m_connection_type = PyMoneroConnectionType::TOR; + } + else if (rpc_type == 4) { + peer->m_connection_type = PyMoneroConnectionType::I2P; + } + else throw std::runtime_error("Invalid RPC peer type, expected 0-4: " + std::to_string(rpc_type)); + } } } @@ -722,7 +775,7 @@ void PyMoneroPeer::from_property_tree(const boost::property_tree::ptree& node, s auto node2 = it->second; for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) { auto peer = std::make_shared(); - PyMoneroPeer::from_property_tree(node2, peer); + PyMoneroPeer::from_property_tree(it2->second, peer); peer->m_is_online = is_online; peers.push_back(peer); } @@ -745,7 +798,10 @@ void PyMoneroSubmitTxResult::from_property_tree(const boost::property_tree::ptre else if (key == std::string("too_big")) result->m_is_too_big = it->second.get_value(); else if (key == std::string("sanity_check_failed")) result->m_sanity_check_failed = it->second.get_value(); else if (key == std::string("credits")) result->m_credits = it->second.get_value(); - else if (key == std::string("top_hash")) result->m_top_block_hash = it->second.data(); + else if (key == std::string("top_hash")) { + std::string top_hash = it->second.data(); + if (!top_hash.empty()) result->m_top_block_hash = top_hash; + } else if (key == std::string("tx_extra_too_big")) result->m_is_tx_extra_too_big = it->second.get_value(); else if (key == std::string("nonzero_unlock_time")) result->m_is_nonzero_unlock_time = it->second.get_value(); } @@ -814,10 +870,10 @@ void PyMoneroDaemonUpdateCheckResult::from_property_tree(const boost::property_t for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; if (key == std::string("update")) check->m_is_update_available = it->second.get_value(); - else if (key == std::string("version")) check->m_version = it->second.data(); - else if (key == std::string("hash")) check->m_hash = it->second.data(); - else if (key == std::string("auto_uri")) check->m_auto_uri = it->second.data(); - else if (key == std::string("user_uri")) check->m_user_uri = it->second.data(); + else if (key == std::string("version") && !it->second.data().empty()) check->m_version = it->second.data(); + else if (key == std::string("hash") && !it->second.data().empty()) check->m_hash = it->second.data(); + else if (key == std::string("auto_uri") && !it->second.data().empty()) check->m_auto_uri = it->second.data(); + else if (key == std::string("user_uri") && !it->second.data().empty()) check->m_user_uri = it->second.data(); } } @@ -826,7 +882,7 @@ void PyMoneroDaemonUpdateDownloadResult::from_property_tree(const boost::propert for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; - if (key == std::string("download_path")) check->m_download_path = it->second.data(); + if (key == std::string("download_path") && !it->second.data().empty()) check->m_download_path = it->second.data(); } } @@ -849,22 +905,22 @@ void PyMoneroDaemonInfo::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("version")) info->m_version = it->second.data(); - else if (key == std::string("num_alt_blocks")) info->m_num_alt_blocks = it->second.get_value(); + else if (key == std::string("alt_blocks_count")) info->m_num_alt_blocks = it->second.get_value(); else if (key == std::string("block_size_limit")) info->m_block_size_limit = it->second.get_value(); else if (key == std::string("block_size_median")) info->m_block_size_median = it->second.get_value(); else if (key == std::string("block_weight_limit")) info->m_block_weight_limit = it->second.get_value(); else if (key == std::string("block_weight_median")) info->m_block_weight_median = it->second.get_value(); - else if (key == std::string("bootstrap_daemon_address")) info->m_bootstrap_daemon_address = it->second.data(); + else if (key == std::string("bootstrap_daemon_address") && !it->second.data().empty()) info->m_bootstrap_daemon_address = it->second.data(); else if (key == std::string("difficulty")) info->m_difficulty = it->second.get_value(); else if (key == std::string("cumulative_difficulty")) info->m_cumulative_difficulty = it->second.get_value(); else if (key == std::string("free_space")) info->m_free_space = it->second.get_value(); - else if (key == std::string("num_offline_peers")) info->m_num_offline_peers = it->second.get_value(); - else if (key == std::string("num_online_peers")) info->m_num_online_peers = it->second.get_value(); + else if (key == std::string("grey_peerlist_size")) info->m_num_offline_peers = it->second.get_value(); + else if (key == std::string("white_peerlist_size")) info->m_num_online_peers = it->second.get_value(); else if (key == std::string("height")) info->m_height = it->second.get_value(); else if (key == std::string("height_without_bootstrap")) info->m_height_without_bootstrap = it->second.get_value(); else if (key == std::string("nettype")) { std::string nettype = it->second.data(); - if (nettype == std::string("mainnet")) { + if (nettype == std::string("mainnet") || nettype == std::string("fakechain")) { info->m_network_type = monero::monero_network_type::MAINNET; } else if (nettype == std::string("testnet")) { @@ -882,9 +938,12 @@ void PyMoneroDaemonInfo::from_property_tree(const boost::property_tree::ptree& n else if (key == std::string("adjusted_time")) info->m_adjusted_timestamp = it->second.get_value(); else if (key == std::string("target")) info->m_target = it->second.get_value(); else if (key == std::string("target_height")) info->m_target_height = it->second.get_value(); - else if (key == std::string("top_block_hash")) info->m_top_block_hash = it->second.data(); - else if (key == std::string("num_txs")) info->m_num_txs = it->second.get_value(); - else if (key == std::string("num_txs_pool")) info->m_num_txs_pool = it->second.get_value(); + else if (key == std::string("top_block_hash") || key == std::string("top_hash")) { + std::string top_hash = it->second.data(); + if (!top_hash.empty()) info->m_top_block_hash = top_hash; + } + else if (key == std::string("tx_count")) info->m_num_txs = it->second.get_value(); + else if (key == std::string("tx_pool_size")) info->m_num_txs_pool = it->second.get_value(); else if (key == std::string("was_bootstrap_ever_used")) info->m_was_bootstrap_ever_used = it->second.get_value(); else if (key == std::string("database_size")) info->m_database_size = it->second.get_value(); else if (key == std::string("update_available")) info->m_update_available = it->second.get_value(); @@ -901,9 +960,13 @@ void PyMoneroDaemonSyncInfo::from_property_tree(const boost::property_tree::ptre if (key == std::string("height")) info->m_height = it->second.get_value(); else if (key == std::string("target_height")) info->m_target_height = it->second.get_value(); else if (key == std::string("next_needed_pruning_seed")) info->m_next_needed_pruning_seed = it->second.get_value(); - else if (key == std::string("overview")) info->m_overview = it->second.data(); + // TODO implement overview field + //else if (key == std::string("overview") && !it->second.data().empty()) info->m_overview = it->second.data(); else if (key == std::string("credits")) info->m_credits = it->second.get_value(); - else if (key == std::string("top_hash")) info->m_top_block_hash = it->second.data(); + else if (key == std::string("top_hash")) { + std::string top_hash = it->second.data(); + if (!top_hash.empty()) info->m_top_block_hash = top_hash; + } } } @@ -919,7 +982,10 @@ void PyMoneroHardForkInfo::from_property_tree(const boost::property_tree::ptree& else if (key == std::string("window")) info->m_window = it->second.get_value(); else if (key == std::string("voting")) info->m_voting = it->second.get_value(); else if (key == std::string("credits")) info->m_credits = it->second.get_value(); - else if (key == std::string("top_hash")) info->m_top_block_hash = it->second.data(); + else if (key == std::string("top_hash")) { + std::string top_hash = it->second.data(); + if (!top_hash.empty()) info->m_top_block_hash = top_hash; + } } } diff --git a/src/cpp/py_monero.cpp b/src/cpp/py_monero.cpp index 9593229..9e40c53 100644 --- a/src/cpp/py_monero.cpp +++ b/src/cpp/py_monero.cpp @@ -418,6 +418,7 @@ PYBIND11_MODULE(monero, m) { // monero_block_header py_monero_block_header .def(py::init<>()) + .def("__str__", &monero::monero_block_header::serialize) .def_readwrite("hash", &monero::monero_block_header::m_hash) .def_readwrite("height", &monero::monero_block_header::m_height) .def_readwrite("timestamp", &monero::monero_block_header::m_timestamp) @@ -446,6 +447,7 @@ PYBIND11_MODULE(monero, m) { // monero_block (needs: monero_tx) py_monero_block .def(py::init<>()) + .def("__str__", &monero::monero_block::serialize) .def_readwrite("hex", &monero::monero_block::m_hex) .def_readwrite("miner_tx", &monero::monero_block::m_miner_tx) .def_readwrite("txs", &monero::monero_block::m_txs) diff --git a/tests/test_monero_daemon_rpc.py b/tests/test_monero_daemon_rpc.py index 692e949..eb92328 100644 --- a/tests/test_monero_daemon_rpc.py +++ b/tests/test_monero_daemon_rpc.py @@ -14,7 +14,6 @@ # TODO enable rpc daemon tests -@pytest.mark.skipif(True, reason="TODO") class TestMoneroDaemonRpc: _daemon: MoneroDaemonRpc = Utils.get_daemon_rpc() _wallet: MoneroWalletRpc #= Utils.get_wallet_rpc() @@ -132,17 +131,18 @@ def test_get_block_by_hash(self): assert block.height is not None Utils.test_block(block, ctx) Utils.assert_equals(self._daemon.get_block_by_height(block.height), block) - Utils.assert_equals(None, block.txs) + assert len(block.txs) == 0, f"No block tx expected, found: {len(block.txs)}" # retrieve by hash of previous to last block hash_str = self._daemon.get_block_hash(last_header.height - 1) block = self._daemon.get_block_by_hash(hash_str) Utils.test_block(block, ctx) Utils.assert_equals(self._daemon.get_block_by_height(last_header.height - 1), block) - Utils.assert_equals(None, block.txs) + assert len(block.txs) == 0, f"No block tx expected, found: {len(block.txs)}" # Can get blocks by hash which includes transactions (binary) - @pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") + #@pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") + @pytest.mark.skip(reason="Not implemented") def test_get_blocks_by_hash_binary(self) -> None: raise NotImplementedError("Not implemented") @@ -169,7 +169,8 @@ def test_get_block_by_height(self): Utils.assert_equals(last_header.height - 1, block.height) # Can get blocks by height which includes transactions (binary) - @pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") + #@pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") + @pytest.mark.skip(reason="Not implemented") def test_get_blocks_by_height_binary(self): # set number of blocks to test num_blocks = 100 @@ -324,7 +325,11 @@ def test_get_peers(self): @pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") def test_get_known_peers(self): peers: list[MoneroPeer] = self._daemon.get_known_peers() - Utils.assert_false(len(peers) == 0, "Daemon has no known peers to test") + if Utils.REGTEST: + Utils.assert_true(len(peers) == 0, "Regtest daemon should not have known peers to test") + else: + Utils.assert_false(len(peers) == 0, "Daemon has no known peers to test") + for peer in peers: Utils.test_known_peer(peer, False) @@ -463,12 +468,14 @@ def test_prune_blockchain(self): # Can check for an update @pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") + @pytest.mark.flaky(reruns=3, reruns_delay=2) def test_check_for_update(self): result: MoneroDaemonUpdateCheckResult = self._daemon.check_for_update() Utils.test_update_check_result(result) # Can download an update @pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") + @pytest.mark.flaky(reruns=3, reruns_delay=2) def test_download_update(self): # download to default path result: MoneroDaemonUpdateDownloadResult = self._daemon.download_update() diff --git a/tests/utils/monero_test_utils.py b/tests/utils/monero_test_utils.py index 83db3bf..c7a6d55 100644 --- a/tests/utils/monero_test_utils.py +++ b/tests/utils/monero_test_utils.py @@ -3,14 +3,14 @@ from random import choices, shuffle from time import sleep, time from os.path import exists as path_exists -from os import makedirs +from os import makedirs, getenv from monero import ( MoneroNetworkType, MoneroTx, MoneroUtils, MoneroWalletFull, MoneroRpcConnection, MoneroWalletConfig, MoneroDaemonRpc, MoneroWalletRpc, MoneroBlockHeader, MoneroBlockTemplate, MoneroBlock, MoneroDaemonUpdateCheckResult, MoneroDaemonUpdateDownloadResult, MoneroWalletKeys, MoneroSubaddress, MoneroPeer, MoneroDaemonInfo, MoneroDaemonSyncInfo, MoneroHardForkInfo, MoneroAltChain, MoneroTxPoolStats, MoneroWallet, MoneroRpcError, MoneroTxConfig, - MoneroAccount, MoneroTxWallet, MoneroTxQuery, MoneroConnectionSpan + MoneroAccount, MoneroTxWallet, MoneroTxQuery, MoneroConnectionSpan, SerializableStruct ) from .wallet_sync_printer import WalletSyncPrinter @@ -68,6 +68,7 @@ class MoneroTestUtils(ABC): # test wallet constants MAX_FEE = 7500000*10000 NETWORK_TYPE: MoneroNetworkType = MoneroNetworkType.MAINNET + REGTEST: bool = getenv("REGTEST") == "true" LANGUAGE: str = "English" SEED: str = "vortex degrees outbreak teeming gimmick school rounded tonic observant injury leech ought problems ahead upcoming ledge textbook cigar atrium trash dunes eavesdrop dullness evolved vortex" ADDRESS: str = "48W9YHwPzRz9aPTeXCA6kmSpW6HsvmWx578jj3of2gT3JwZzwTf33amESBoNDkL6SVK34Q2HTKqgYbGyE1hBws3wCrcBDR2" @@ -148,7 +149,10 @@ def assert_is_none(cls, expr: Any, message: str = "assertion failed"): @classmethod def assert_equals(cls, expr1: Any, expr2: Any, message: str = "assertion failed"): - assert expr1 == expr2, f"{message}: {expr1} == {expr2}" + if isinstance(expr1, SerializableStruct) and isinstance(expr2, SerializableStruct): + assert expr1.serialize() == expr2.serialize(), f"{message}: {expr1} == {expr2}" + else: + assert expr1 == expr2, f"{message}: {expr1} == {expr2}" @classmethod def assert_not_equals(cls, expr1: Any, expr2: Any, message: str = "assertion failed"): @@ -183,6 +187,12 @@ 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 @@ -399,7 +409,7 @@ def test_block_template(cls, template: MoneroBlockTemplate): cls.assert_not_none(template.reserved_offset) cls.assert_not_none(template.seed_height) assert template.seed_height is not None - cls.assert_true(template.seed_height > 0) + cls.assert_true(template.seed_height >= 0) cls.assert_not_none(template.seed_hash) cls.assert_false(template.seed_hash == "") # next seed hash can be null or initialized TODO: test circumstances for each @@ -510,7 +520,7 @@ def test_block(cls, block: MoneroBlock, ctx: TestContext): else: cls.assert_is_none(ctx.tx_context) - cls.assert_is_none(block.txs) + assert len(block.txs) == 0, "No txs expected" @classmethod def is_empty(cls, value: Union[str, list[Any], None]) -> bool: @@ -754,16 +764,12 @@ def test_sync_info(cls, sync_info: Union[Any, MoneroDaemonSyncInfo]): cls.assert_true(isinstance(sync_info, MoneroDaemonSyncInfo)) assert sync_info.height is not None cls.assert_true(sync_info.height >= 0) - if sync_info.peers is not None: - cls.assert_true(len(sync_info.peers) > 0) - for connection in sync_info.peers: - cls.test_peer(connection) - - # TODO: test that this is being hit, so far not used - if sync_info.spans is not None: - cls.assert_true(len(sync_info.spans) > 0) - for span in sync_info.spans: - cls.test_connection_span(span) + + for connection in sync_info.peers: + cls.test_peer(connection) + + for span in sync_info.spans: + cls.test_connection_span(span) assert sync_info.next_needed_pruning_seed is not None cls.assert_true(sync_info.next_needed_pruning_seed >= 0)