From ca5229e166998ad162d4163433537c423a9d40d1 Mon Sep 17 00:00:00 2001 From: everoddandeven Date: Thu, 29 Oct 2026 21:00:41 +0100 Subject: [PATCH 1/6] tests: use the libmonero docker images Replace lalanza808/monero with ghcr.io/libmonero/monero-daemon-rpc and monero-wallet-rpc. The images have the binary as entrypoint (monerod also passes --non-interactive), so the command no longer starts with the binary name, and they run as an unprivileged user, so the data and wallet volumes move to /home/monero. MONERO_TAG selects the tag, master for now. The images are 44 MB instead of 160. Co-Authored-By: Claude Sonnet 5.5 --- tests/docker-compose.yml | 37 +++++++++++++++---------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index fe2536d..2ddd247 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -12,10 +12,9 @@ services: - xmr_wallet_3 node_1: - image: lalanza808/monero:v0.18.5.1 + image: ghcr.io/libmonero/monero-daemon-rpc:${MONERO_TAG:-master} container_name: node_1 command: [ - "monerod", "--fixed-difficulty=500", "--log-level=3", "--p2p-bind-ip=0.0.0.0", @@ -32,21 +31,19 @@ services: "--regtest", "--hide-my-port", "--no-zmq", - "--max-connections-per-ip=100", - "--non-interactive" + "--max-connections-per-ip=100" ] volumes: - - xmr_node_1_data:/data + - xmr_node_1_data:/home/monero/.bitmonero ports: - "48080:48080" - "18089:18089" restart: on-failure node_2: - image: lalanza808/monero:v0.18.5.1 + image: ghcr.io/libmonero/monero-daemon-rpc:${MONERO_TAG:-master} container_name: node_2 command: [ - "monerod", "--fixed-difficulty=500", "--log-level=3", "--p2p-bind-ip=0.0.0.0", @@ -66,11 +63,10 @@ services: "--add-exclusive-node=node_1:48080", "--regtest", "--hide-my-port", - "--max-connections-per-ip=100", - "--non-interactive" + "--max-connections-per-ip=100" ] volumes: - - xmr_node_2_data:/data + - xmr_node_2_data:/home/monero/.bitmonero ports: - "18080:18080" - "18081:18081" @@ -81,10 +77,9 @@ services: restart: on-failure xmr_wallet_1: - image: lalanza808/monero:v0.18.5.1 + image: ghcr.io/libmonero/monero-wallet-rpc:${MONERO_TAG:-master} container_name: xmr_wallet_1 command: [ - "monero-wallet-rpc", "--log-level=3", "--allow-mismatched-daemon-version", "--rpc-bind-ip=0.0.0.0", @@ -95,23 +90,22 @@ services: "--daemon-login=rpc_daemon_user:abc123", "--rpc-login=rpc_user:abc123", "--rpc-max-connections-per-private-ip=100", - "--wallet-dir=/wallet", + "--wallet-dir=/home/monero/wallets", "--rpc-access-control-origins=*", "--non-interactive" ] ports: - "18082:18082" volumes: - - xmr_wallet_1_data:/wallet + - xmr_wallet_1_data:/home/monero/wallets depends_on: - node_1 - node_2 xmr_wallet_2: - image: lalanza808/monero:v0.18.5.1 + image: ghcr.io/libmonero/monero-wallet-rpc:${MONERO_TAG:-master} container_name: xmr_wallet_2 command: [ - "monero-wallet-rpc", "--log-level=3", "--allow-mismatched-daemon-version", "--rpc-bind-ip=0.0.0.0", @@ -122,23 +116,22 @@ services: "--daemon-login=rpc_daemon_user:abc123", "--rpc-login=rpc_user:abc123", "--rpc-max-connections-per-private-ip=100", - "--wallet-dir=/wallet", + "--wallet-dir=/home/monero/wallets", "--rpc-access-control-origins=*", "--non-interactive" ] ports: - "18083:18083" volumes: - - xmr_wallet_2_data:/wallet + - xmr_wallet_2_data:/home/monero/wallets depends_on: - node_1 - node_2 xmr_wallet_3: - image: lalanza808/monero:v0.18.5.1 + image: ghcr.io/libmonero/monero-wallet-rpc:${MONERO_TAG:-master} container_name: xmr_wallet_3 command: [ - "monero-wallet-rpc", "--log-level=3", "--allow-mismatched-daemon-version", "--rpc-bind-ip=0.0.0.0", @@ -149,14 +142,14 @@ services: "--daemon-login=rpc_daemon_user:abc123", "--rpc-login=rpc_user:abc123", "--rpc-max-connections-per-private-ip=100", - "--wallet-dir=/wallet", + "--wallet-dir=/home/monero/wallets", "--rpc-access-control-origins=*", "--non-interactive" ] ports: - "18084:18084" volumes: - - xmr_wallet_3_data:/wallet + - xmr_wallet_3_data:/home/monero/wallets depends_on: - node_1 - node_2 From 7882e5c295383690ee01a11655054fbfedc4a895 Mon Sep 17 00:00:00 2001 From: everoddandeven Date: Thu, 29 Oct 2026 21:11:01 +0100 Subject: [PATCH 2/6] tests: fix the wait_for_next_block assertion wait_for_next_block returns the height of the block that was added, which is the chain length the wallet knew before it, so asserting a greater height could only pass when the wallet lagged behind the chain. It failed on every attempt in CI. Measured on a synced wallet: before=159 returned=159, before=161 returned=161, before=174 returned=174. Assert greater or equal. Co-Authored-By: Claude Sonnet 5.5 --- tests/test_monero_wallet_full.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_monero_wallet_full.py b/tests/test_monero_wallet_full.py index cfa458f..8785fc7 100644 --- a/tests/test_monero_wallet_full.py +++ b/tests/test_monero_wallet_full.py @@ -667,7 +667,9 @@ def test_wait_for_next_block(self, wallet: MoneroWalletFull) -> None: MiningUtils.try_start_mining() try: height: int = wallet.wait_for_next_block() - assert height > height_before + # the notification carries the height of the new block, which is the chain + # length the wallet knew before it was added, so it is only greater when the wallet lagged behind + assert height >= height_before finally: MiningUtils.try_stop_mining() From f6ebc3e5340a7bb4f907feae6823226f8fb2e4e8 Mon Sep 17 00:00:00 2001 From: everoddandeven Date: Thu, 29 Oct 2026 21:11:01 +0100 Subject: [PATCH 3/6] tests: expect the update checks to fail in docker check_for_update and download_update need DNSSEC validated TXT records of updates.moneropulse.*, and the daemon logs "DNSSEC validation failed" and "no two valid DNS TXT records were received" in the docker environment, locally as in CI. Mark them xfail for the RPC error only, so any other failure still shows. They no longer retry five times, which cost minutes for an error that never changes. Co-Authored-By: Claude Sonnet 5.5 --- tests/test_monero_daemon_rpc.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_monero_daemon_rpc.py b/tests/test_monero_daemon_rpc.py index 34bc53d..3ee1cab 100644 --- a/tests/test_monero_daemon_rpc.py +++ b/tests/test_monero_daemon_rpc.py @@ -7,7 +7,7 @@ from monero import ( MoneroDaemonRpc, MoneroVersion, MoneroBlockHeader, MoneroBlockTemplate, MoneroBlock, MoneroMiningStatus, MoneroPruneResult, MoneroMinerTxSum, - MoneroDaemonUpdateCheckResult, MoneroDaemonUpdateDownloadResult, + MoneroDaemonUpdateCheckResult, MoneroDaemonUpdateDownloadResult, MoneroRpcError, MoneroDaemonListener, MoneroPeer, MoneroDaemonInfo, MoneroDaemonSyncInfo, MoneroHardForkInfo, MoneroAltChain, MoneroTx, MoneroSubmitTxResult, MoneroTxPoolStats, MoneroBan, MoneroTxConfig, MoneroDestination, @@ -1061,14 +1061,14 @@ def test_prune_blockchain(self, daemon: MoneroDaemonRpc) -> None: # Can check for an update @pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") - @pytest.mark.flaky(reruns=5, reruns_delay=5, only_rerun=[]) + @pytest.mark.xfail(reason="the update check needs DNSSEC validated TXT records, which the daemon in docker does not get", raises=MoneroRpcError, strict=False) def test_check_for_update(self, daemon: MoneroDaemonRpc) -> None: result: MoneroDaemonUpdateCheckResult = daemon.check_for_update() DaemonUtils.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=5, reruns_delay=5, only_rerun=[]) + @pytest.mark.xfail(reason="the update check needs DNSSEC validated TXT records, which the daemon in docker does not get", raises=MoneroRpcError, strict=False) def test_download_update(self, daemon: MoneroDaemonRpc) -> None: # download to default path result: MoneroDaemonUpdateDownloadResult = daemon.download_update() From d92dce664b6e7e8558c38c383cfb1c0b63fb73f5 Mon Sep 17 00:00:00 2001 From: everoddandeven Date: Thu, 29 Oct 2026 21:15:21 +0100 Subject: [PATCH 4/6] Bump monero-cpp to v0.9.10 Move the submodule from 83832b8 to v0.9.10, 21 commits behind master at the time and the last tagged release. monero-project goes from df6907e8e to 09e1b2b7b, monero 0.18.5.3. Upstream changes since our pin: the TLS certificate verification setting is propagated to the rpc connections (new m_ssl_verify, serialized as sslVerify), wallet full cancels requests before closing and no longer reports outgoing amounts with an incomplete history, wallet rpc keeps unlock notifications after long polling gaps and resets its snapshots when the wallet changes, the restore height of a random wallet is persisted at creation, and the wallet model serialization fixes. Co-Authored-By: Claude Sonnet 5.5 --- external/monero-cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/monero-cpp b/external/monero-cpp index 83832b8..1674b58 160000 --- a/external/monero-cpp +++ b/external/monero-cpp @@ -1 +1 @@ -Subproject commit 83832b89f97cfadd36690a629566584d1336cf41 +Subproject commit 1674b581ebb6e88a8b2f1d0206d420d51402c978 From 489c1dd34ce8024e86772cf7bace60bc33087b1a Mon Sep 17 00:00:00 2001 From: everoddandeven Date: Thu, 29 Oct 2026 21:15:21 +0100 Subject: [PATCH 5/6] bindings: expose ssl_verify of the rpc connection monero-cpp v0.9.10 verifies the TLS certificate and hostname of https connections by default, and serializes the setting as sslVerify in every connection. Without a property there would be no way to connect to a node with a self signed certificate. Add ssl_verify to MoneroRpcConnection, to the stub, and update the exact json expected by the serialization test. The new test covers the default, assignment, serialization, copy and deserialization. Co-Authored-By: Claude Sonnet 5.5 --- src/cpp/common/py_monero_common_bindings.cpp | 3 +++ src/python/monero_rpc_connection.pyi | 2 ++ tests/test_monero_rpc_connection.py | 25 ++++++++++++++++---- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/cpp/common/py_monero_common_bindings.cpp b/src/cpp/common/py_monero_common_bindings.cpp index 463817a..334e7eb 100644 --- a/src/cpp/common/py_monero_common_bindings.cpp +++ b/src/cpp/common/py_monero_common_bindings.cpp @@ -296,6 +296,9 @@ void py_monero_bind_common(py::module_& m, PyMoneroTypes& t) { .def_property("timeout_ms", [](const monero_rpc_connection& self) { return self.m_timeout_ms; }, [](monero_rpc_connection& self, uint64_t val) { self.m_timeout_ms = val; }) + .def_property("ssl_verify", + [](const monero_rpc_connection& self) { return self.m_ssl_verify; }, + [](monero_rpc_connection& self, bool val) { self.m_ssl_verify = val; }) .def("set_attribute", [](monero_rpc_connection& self, const std::string& key, const std::string& value) { MONERO_CATCH_AND_RETHROW(self.set_attribute(key, value)); }, py::arg("key"), py::arg("value")) diff --git a/src/python/monero_rpc_connection.pyi b/src/python/monero_rpc_connection.pyi index d4d27a6..a8605f2 100644 --- a/src/python/monero_rpc_connection.pyi +++ b/src/python/monero_rpc_connection.pyi @@ -14,6 +14,8 @@ class MoneroRpcConnection(SerializableStruct): """ZMQ connection uri.""" timeout_ms: int """Connection timeout (milliseconds).""" + ssl_verify: bool + """Verify the TLS certificate and the hostname of https connections (default true).""" uri: str | None """Connection uri.""" diff --git a/tests/test_monero_rpc_connection.py b/tests/test_monero_rpc_connection.py index 6a537ca..090ba03 100644 --- a/tests/test_monero_rpc_connection.py +++ b/tests/test_monero_rpc_connection.py @@ -68,21 +68,38 @@ def test_validate_uri(self) -> None: def test_rpc_connection_serialization(self, node_connection: MoneroRpcConnection, wallet_connection: MoneroRpcConnection) -> None: # test node connection serialization connection_str: str = node_connection.serialize() - assert '{"uri":"http://127.0.0.1:18081","username":"rpc_daemon_user","password":"abc123","priority":0,"timeoutMs":15000}' == connection_str + assert '{"uri":"http://127.0.0.1:18081","username":"rpc_daemon_user","password":"abc123","priority":0,"timeoutMs":15000,"sslVerify":true}' == connection_str # node wallet connection serialization connection_str = wallet_connection.serialize() - assert '{"uri":"http://127.0.0.1:18082","username":"rpc_user","password":"abc123","priority":0,"timeoutMs":15000}' == connection_str + assert '{"uri":"http://127.0.0.1:18082","username":"rpc_user","password":"abc123","priority":0,"timeoutMs":15000,"sslVerify":true}' == connection_str # test empty connection connection: MoneroRpcConnection = MoneroRpcConnection() connection_str = connection.serialize() - assert '{"priority":0}' == connection.serialize() + assert '{"priority":0,"sslVerify":true}' == connection.serialize() # test connection connection = MoneroRpcConnection("test_node:18081", "user", "abc123", "127.0.0.1:9050", "test_node:18084", 1, 1000) connection_str = connection.serialize() - assert '{"uri":"test_node:18081","username":"user","password":"abc123","proxyUri":"127.0.0.1:9050","zmqUri":"test_node:18084","priority":1,"timeoutMs":1000}' == connection_str + assert '{"uri":"test_node:18081","username":"user","password":"abc123","proxyUri":"127.0.0.1:9050","zmqUri":"test_node:18084","priority":1,"timeoutMs":1000,"sslVerify":true}' == connection_str + + # Can disable the verification of the TLS certificate + @pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") + def test_ssl_verify(self) -> None: + # verified by default + connection: MoneroRpcConnection = MoneroRpcConnection("https://node.example:18081") + assert connection.ssl_verify is True + + # can be disabled, the setting is serialized and copied + connection.ssl_verify = False + assert connection.ssl_verify is False + assert '"sslVerify":false' in connection.serialize() + assert MoneroRpcConnection(connection).ssl_verify is False + assert MoneroRpcConnection.deserialize(connection.serialize()).ssl_verify is False + + # connections that don't mention it stay verified + assert MoneroRpcConnection.deserialize('{"uri":"https://node.example:18081"}').ssl_verify is True # Can copy a rpc connection @pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") From 55fa901d14e2b02fd195c94bb5c96f487b125f91 Mon Sep 17 00:00:00 2001 From: everoddandeven Date: Thu, 29 Oct 2026 21:15:50 +0100 Subject: [PATCH 6/6] bump version to v0.9.10 Same files as the v0.9.4 bump, plus the release examples in the README. The README now names monero v0.18.5.3, which is what the submodule builds. Co-Authored-By: Claude Sonnet 5.5 --- CMakeLists.txt | 2 +- README.md | 6 +++--- debian/bookworm/control | 2 +- debian/jammy/control | 2 +- debian/noble/control | 2 +- debian/trixie/control | 2 +- docker/build.sh | 2 +- pyproject.toml | 2 +- setup.py | 2 +- src/cpp/utils/py_monero_utils.h | 2 +- 10 files changed, 12 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6161886..c691496 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.15) -project(monero VERSION 0.9.4 LANGUAGES CXX) +project(monero VERSION 0.9.10 LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) diff --git a/README.md b/README.md index f21ea18..cb07620 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ [![Docs](https://img.shields.io/badge/docs-stable-blue?logo=readthedocs)](https://libmonero.github.io/monero-python/mkdocs/public/) [![Matrix](https://img.shields.io/badge/matrix-monero--python-blue?logo=matrix)](https://matrix.to/#/#monero-python:monero.social) -A Python library for creating Monero applications using RPC and Python bindings to [monero v0.18.5.1 'Fluorine Fermi'](https://github.com/monero-project/monero/tree/v0.18.5.1). +A Python library for creating Monero applications using RPC and Python bindings to [monero v0.18.5.3 'Fluorine Fermi'](https://github.com/monero-project/monero/tree/v0.18.5.3). * Supports wallet and daemon RPC clients. * Supports client-side wallets using Python bindings. @@ -105,11 +105,11 @@ Every [release](https://github.com/libmonero/monero-python/releases) has prebuil * Debian and Ubuntu: `python3-monero_-11_.deb` for `bookworm`, `trixie`, `jammy` and `noble` on `amd64`, `arm64` and `armhf`, installed for the system Python 3: ```bash - sudo apt install ./python3-monero_0.9.4-1noble1_amd64.deb + sudo apt install ./python3-monero_0.9.10-1noble1_amd64.deb ``` * macOS and Windows: one wheel per Python version, `monero---.whl`: ```bash - pip3 install monero-0.9.4-cp313-cp313-win_amd64.whl + pip3 install monero-0.9.10-cp313-cp313-win_amd64.whl ``` On anything else, build it from source. diff --git a/debian/bookworm/control b/debian/bookworm/control index 6d27a83..53f7588 100644 --- a/debian/bookworm/control +++ b/debian/bookworm/control @@ -11,4 +11,4 @@ Package: python3-monero Architecture: all Description: Python bindings for monero-cpp This package provides Python 3 bindings for the monero-cpp library using pybind11. -Version: 0.9.4 +Version: 0.9.10 diff --git a/debian/jammy/control b/debian/jammy/control index 6d27a83..53f7588 100644 --- a/debian/jammy/control +++ b/debian/jammy/control @@ -11,4 +11,4 @@ Package: python3-monero Architecture: all Description: Python bindings for monero-cpp This package provides Python 3 bindings for the monero-cpp library using pybind11. -Version: 0.9.4 +Version: 0.9.10 diff --git a/debian/noble/control b/debian/noble/control index 6d27a83..53f7588 100644 --- a/debian/noble/control +++ b/debian/noble/control @@ -11,4 +11,4 @@ Package: python3-monero Architecture: all Description: Python bindings for monero-cpp This package provides Python 3 bindings for the monero-cpp library using pybind11. -Version: 0.9.4 +Version: 0.9.10 diff --git a/debian/trixie/control b/debian/trixie/control index 6d27a83..53f7588 100644 --- a/debian/trixie/control +++ b/debian/trixie/control @@ -11,4 +11,4 @@ Package: python3-monero Architecture: all Description: Python bindings for monero-cpp This package provides Python 3 bindings for the monero-cpp library using pybind11. -Version: 0.9.4 +Version: 0.9.10 diff --git a/docker/build.sh b/docker/build.sh index 5045c6b..780b2c3 100755 --- a/docker/build.sh +++ b/docker/build.sh @@ -2,7 +2,7 @@ set -e DISTRO_CODENAME=$(grep VERSION_CODENAME /etc/os-release | cut -d'=' -f2) -DEFAULT_VERSION="0.9.4" +DEFAULT_VERSION="0.9.10" if [ -z "$DISTRO_CODENAME" ]; then DISTRO_CODENAME=$(lsb_release -c -s 2>/dev/null || echo "unknown") diff --git a/pyproject.toml b/pyproject.toml index 5effba2..00e6bf9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "monero" -version = "0.9.4" +version = "0.9.10" description = "A Python library for using Monero" authors = [ { name = "everoddandeven", email = "everoddandeven@protonmail.com" } diff --git a/setup.py b/setup.py index 2e7e257..e6d54eb 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name='monero', - version='0.9.4', + version='0.9.10', author='everoddandeven', author_email="everoddandeven@protonmail.com", maintainer='everoddandeven', diff --git a/src/cpp/utils/py_monero_utils.h b/src/cpp/utils/py_monero_utils.h index ebc419d..5b76a0f 100644 --- a/src/cpp/utils/py_monero_utils.h +++ b/src/cpp/utils/py_monero_utils.h @@ -63,7 +63,7 @@ class PyMoneroUtils { public: PyMoneroUtils() {}; - static std::string get_version() { return std::string("0.9.4"); }; + static std::string get_version() { return std::string("0.9.10"); }; static std::string json_to_binary(const std::string &json); static std::string dict_to_binary(const py::dict &dictionary); static py::dict binary_to_dict(const std::string& bin);