Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ jobs:

- name: Setup test environment
run: |
docker compose -f tests/docker-compose.yml up -d node_1 node_2 xmr_wallet_1 xmr_wallet_2 xmr_wallet_3
docker compose -f tests/docker-compose.yml up -d node_1 node_2 xmr_wallet_1 xmr_wallet_2 xmr_wallet_3 xmr_lws
sleep 10

- name: Reset coverage counters
Expand All @@ -97,7 +97,7 @@ jobs:
if: always()
run: |
mkdir -p container-logs
for svc in node_1 node_2 xmr_wallet_1 xmr_wallet_2 xmr_wallet_3; do
for svc in node_1 node_2 xmr_wallet_1 xmr_wallet_2 xmr_wallet_3 xmr_lws; do
docker compose -f tests/docker-compose.yml logs --no-color --timestamps "$svc" > "container-logs/$svc.log" 2>&1 || true
done

Expand Down
2 changes: 1 addition & 1 deletion bin/setup_test_environment.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env bash

# start docker containers
sudo docker compose -f tests/docker-compose.yml up -d node_1 node_2 xmr_wallet_1 xmr_wallet_2 xmr_wallet_3
sudo docker compose -f tests/docker-compose.yml up -d node_1 node_2 xmr_wallet_1 xmr_wallet_2 xmr_wallet_3 xmr_lws
4 changes: 2 additions & 2 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ def pytest_runtest_call(item: pytest.Item):
try:
# run test
item.runtest()
except RuntimeError as e:
except Exception as e:
e_str = str(e).lower()
if "not supported" in e_str or "does not support" in e_str:
if "not supported" in e_str or "does not support" in e_str or "doesn't support" in e_str:
# Ok
pytest.xfail(str(e))
if not_implemented and "not implemented" in e_str:
Expand Down
4 changes: 4 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ log_level = INFO
log_cli = True
log_cli_level = INFO
console_output_style = progress
log_file = tests.log
log_file_level = DEBUG
log_file_format = %(asctime)s %(levelname)-8s %(name)s:%(lineno)s %(message)s
log_file_date_format = %Y-%m-%d %H:%M:%S
testpaths =
tests
markers =
Expand Down
3 changes: 3 additions & 0 deletions src/cpp/py_monero_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
#include "wallet/monero_wallet_rpc.h"
#include "wallet/monero_wallet_keys.h"
#include "wallet/monero_wallet_full.h"
#include "wallet/monero_wallet_light.h"
#include "utils/py_monero_utils.h"

#define MONERO_CATCH_AND_RETHROW(expr) \
Expand Down Expand Up @@ -148,6 +149,7 @@ struct PyMoneroTypes {
py::class_<monero_wallet_keys, monero_wallet, std::shared_ptr<monero_wallet_keys>> py_monero_wallet_keys;
py::class_<monero_wallet_full, monero_wallet, std::shared_ptr<monero_wallet_full>> py_monero_wallet_full;
py::class_<monero_wallet_rpc, monero_wallet, std::shared_ptr<monero_wallet_rpc>> py_monero_wallet_rpc;
py::class_<monero_wallet_light, monero_wallet_keys, std::shared_ptr<monero_wallet_light>> py_monero_wallet_light;
py::class_<PyMoneroUtils> py_monero_utils;

py::class_<monero_tx_height_comparator, std::shared_ptr<monero_tx_height_comparator>> py_tx_height_comparator;
Expand Down Expand Up @@ -200,6 +202,7 @@ struct PyMoneroTypes {
py_monero_wallet(m, "MoneroWallet"),
py_monero_wallet_keys(m, "MoneroWalletKeys"),
py_monero_wallet_full(m, "MoneroWalletFull"),
py_monero_wallet_light(m, "MoneroWalletLight"),
py_monero_wallet_rpc(m, "MoneroWalletRpc"),
py_monero_utils(m, "MoneroUtils"),
py_tx_height_comparator(m, "TxHeightComparator"),
Expand Down
18 changes: 18 additions & 0 deletions src/cpp/wallet/py_monero_wallet_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1037,6 +1037,24 @@ void py_monero_bind_wallet(py::module_& m, PyMoneroTypes& t) {
MONERO_CATCH_AND_RETHROW(self.get_cache_file_buffer());
}, py::call_guard<py::gil_scoped_release>());

// monero_wallet_light
t.py_monero_wallet_light
.def_static("wallet_exists", [](const std::string& primary_address, const std::string& private_view_key, const std::shared_ptr<monero_rpc_connection>& rpc) {
MONERO_CATCH_AND_RETHROW(monero_wallet_light::wallet_exists(primary_address, private_view_key, rpc));
}, py::arg("primary_address"), py::arg("private_view_key"), py::arg("rpc"), py::call_guard<py::gil_scoped_release>())
.def_static("wallet_exists", [](const monero_wallet_config& config, const std::shared_ptr<monero_rpc_connection>& rpc) {
MONERO_CATCH_AND_RETHROW(monero_wallet_light::wallet_exists(config, rpc));
}, py::arg("config"), py::arg("rpc"), py::call_guard<py::gil_scoped_release>())
.def_static("open_wallet", [](const monero_wallet_config& config, const std::shared_ptr<monero_rpc_connection>& rpc) {
MONERO_CATCH_AND_RETHROW(monero_wallet_light::open_wallet(config, rpc));
}, py::arg("config"), py::arg("rpc"), py::call_guard<py::gil_scoped_release>())
.def_static("create_wallet", [](const monero_wallet_config& config, const std::shared_ptr<monero_rpc_connection>& rpc) {
MONERO_CATCH_AND_RETHROW(monero_wallet_light::create_wallet(config, rpc));
}, py::arg("config"), py::arg("rpc"), py::call_guard<py::gil_scoped_release>())
.def("get_rpc_connection", [](monero_wallet_rpc& self) {
MONERO_CATCH_AND_RETHROW(self.get_rpc_connection());
}, py::call_guard<py::gil_scoped_release>());

// monero_wallet_rpc
t.py_monero_wallet_rpc
.def(py::init<const std::shared_ptr<monero_rpc_connection>&>(), py::arg("rpc_connection"), py::call_guard<py::gil_scoped_release>())
Expand Down
2 changes: 2 additions & 0 deletions src/python/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ from .monero_wallet_full import MoneroWalletFull
from .monero_wallet_keys import MoneroWalletKeys
from .monero_wallet_listener import MoneroWalletListener
from .monero_wallet_rpc import MoneroWalletRpc
from .monero_wallet_light import MoneroWalletLight


__all__ = [
Expand Down Expand Up @@ -206,6 +207,7 @@ __all__ = [
'MoneroWalletKeys',
'MoneroWalletListener',
'MoneroWalletRpc',
'MoneroWalletLight',
'SslOptions',
'SerializableStruct'
]
68 changes: 68 additions & 0 deletions src/python/monero_wallet_light.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from typing import overload

from .monero_rpc_connection import MoneroRpcConnection
from .monero_wallet_config import MoneroWalletConfig
from .monero_wallet_keys import MoneroWalletKeys


class MoneroWalletLight(MoneroWalletKeys):
"""
Implements a Monero wallet using `monero-lws`_.

.. _monero-lws: https://github.com/vtnerd/monero-lws
"""

@staticmethod
@overload
def wallet_exists(primary_address: str, private_view_key: str, rpc: MoneroRpcConnection) -> bool:
"""
Check if a wallet exists on the server.

:param primary_address: The primary address of the wallet.
:param private_view_key: The private view key of the wallet.
:param rpc: The RPC connection to the Monero server.
:return: True if the wallet exists, False otherwise.
"""
...

@staticmethod
@overload
def wallet_exists(config: MoneroWalletConfig, rpc: MoneroRpcConnection) -> bool:
"""
Check if a wallet exists on the server.

:param config: The wallet configuration.
:param rpc: The RPC connection to the Monero server.
:return: True if the wallet exists, False otherwise.
"""
...

@staticmethod
def open_wallet(config: MoneroWalletConfig, rpc: MoneroRpcConnection) -> MoneroWalletLight:
"""
Open an existing light wallet with the given configuration.

:param config: The configuration for opening the wallet.
:param rpc: The RPC connection to the Monero server.
:return: An instance of MoneroWalletLight.
"""
...

@staticmethod
def create_wallet(config: MoneroWalletConfig, rpc: MoneroRpcConnection) -> MoneroWalletLight:
"""
Create a new light wallet with the given configuration.

:param config: The configuration for creating the wallet.
:param rpc: The RPC connection to the Monero server.
:return: An instance of MoneroWalletLight.
"""
...

def get_rpc_connection(self) -> MoneroRpcConnection | None:
"""
Get the wallet's RPC connection.

:returns MoneroRpcConnection | None: the wallet's rpc connection.
"""
...
3 changes: 3 additions & 0 deletions tests/config/config.ini
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ auto_connect_timeout_ms=3000
rpc_uri=http://127.0.0.1:18081
rpc_username=rpc_daemon_user
rpc_password=abc123
zmq_uri=tcp://127.0.0.1:18085
zmq_pub_uri=tcp://127.0.0.1:18086
lws_uri=http://127.0.0.1:8443

[wallet]
name=test_wallet_1
Expand Down
59 changes: 46 additions & 13 deletions tests/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,67 +10,76 @@ services:
- xmr_wallet_1
- xmr_wallet_2
- xmr_wallet_3
- xmr_lws

node_1:
image: lalanza808/monero:v0.18.5.0
container_name: node_1
command: [
"monerod",
"--fixed-difficulty=500",
"--log-level=2",
"--log-level=3",
"--p2p-bind-ip=0.0.0.0",
"--p2p-bind-port=48080",
"--rpc-bind-port=18089",
"--rpc-bind-ip=0.0.0.0",
"--confirm-external-bind",
"--rpc-access-control-origins=*",
"--rpc-ssl=disabled",
"--rpc-login=rpc_daemon_user:abc123",
"--rpc-max-connections-per-private-ip=100",
"--disable-rpc-ban",
"--confirm-external-bind",
"--add-exclusive-node=node_2:18080",
"--regtest",
"--no-igd",
"--hide-my-port",
"--no-zmq",
"--max-connections-per-ip=100",
"--rpc-max-connections-per-private-ip=100",
"--mining-threads=1",
"--rpc-login=rpc_daemon_user:abc123",
"--non-interactive"
]
volumes:
- xmr_node_1_data:/data
ports:
- "48080:48080"
- "18089:18089"
restart: on-failure

node_2:
image: lalanza808/monero:v0.18.5.0
container_name: node_2
command: [
"monerod",
"--fixed-difficulty=500",
"--log-level=2",
"--log-level=3",
"--p2p-bind-ip=0.0.0.0",
"--p2p-bind-port=18080",
"--rpc-bind-ip=0.0.0.0",
"--confirm-external-bind",
"--rpc-bind-port=18081",
"--rpc-access-control-origins=*",
"--rpc-ssl=disabled",
"--rpc-max-connections-per-private-ip=100",
"--rpc-login=rpc_daemon_user:abc123",
"--zmq-rpc-bind-ip=0.0.0.0",
"--zmq-rpc-bind-port=18085",
"--zmq-pub=tcp://0.0.0.0:18086",
"--disable-rpc-ban",
"--confirm-external-bind",
"--confirm-zmq-rpc-external-bind",
"--add-exclusive-node=node_1:48080",
"--regtest",
"--no-igd",
"--hide-my-port",
"--no-zmq",
"--max-connections-per-ip=100",
"--rpc-max-connections-per-private-ip=100",
"--rpc-login=rpc_daemon_user:abc123",
"--non-interactive"
]
volumes:
- xmr_node_2_data:/data
ports:
- "18080:18080"
- "18081:18081"
- "18085:18085"
- "18086:18086"
depends_on:
- node_1
restart: on-failure

xmr_wallet_1:
image: lalanza808/monero:v0.18.5.0
Expand Down Expand Up @@ -153,9 +162,33 @@ services:
- node_1
- node_2

xmr_lws:
image: vtnerd/monero-lws:master
container_name: xmr_lws
command: >
--daemon=tcp://node_2:18085
--sub=tcp://node_2:18086
--log-level=4
--webhook-ssl-verification=none
--disable-admin-auth
--rest-server=http://0.0.0.0:8443/
--access-control-origin=*
--confirm-external-bind
--max-subaddresses=200
--auto-accept-creation
--regtest
volumes:
- xmr_lws_data:/data
ports:
- "8443:8443"
depends_on:
- node_1
- node_2

volumes:
xmr_node_1_data:
xmr_node_2_data:
xmr_wallet_1_data:
xmr_wallet_2_data:
xmr_wallet_3_data:
xmr_wallet_3_data:
xmr_lws_data:
11 changes: 11 additions & 0 deletions tests/test_monero_rpc_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ def wallet_connection(self) -> MoneroRpcConnection:
"""Rpc connection test instance."""
return MoneroRpcConnection(Utils.WALLET_RPC_URI, Utils.WALLET_RPC_USERNAME, Utils.WALLET_RPC_PASSWORD, timeout_ms=self.TIMEOUT_MS)

# Light wallet server rpc connection fixture
@pytest.fixture(scope="class")
def lws_connection(self) -> MoneroRpcConnection:
"""Rpc connection test instance."""
return MoneroRpcConnection(Utils.LWS_RPC_URI, timeout_ms=self.TIMEOUT_MS)

#endregion

#region Tests
Expand Down Expand Up @@ -101,6 +107,11 @@ def test_node_rpc_connection(self, node_connection: MoneroRpcConnection) -> None
def test_wallet_rpc_connection(self, wallet_connection: MoneroRpcConnection) -> None:
RpcConnectionUtils.test_rpc_connection(wallet_connection, Utils.WALLET_RPC_URI, True, MoneroConnectionType.IPV4)

# Test light wallet server rpc connection
@pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled")
def test_lws_rpc_connection(self, lws_connection: MoneroRpcConnection) -> None:
RpcConnectionUtils.test_rpc_connection(lws_connection, Utils.LWS_RPC_URI, True, MoneroConnectionType.IPV4)

# Test invalid connection
@pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled")
def test_invalid_connection(self) -> None:
Expand Down
9 changes: 5 additions & 4 deletions tests/test_monero_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ def test_atomic_unit_conversion(self) -> None:

# Can get payment uri
def test_get_payment_uri(self, config: TestMoneroUtils.Config) -> None:
address = config.mainnet.primary_address_1
address: str = config.mainnet.primary_address_1
tx_config: MoneroTxConfig = WalletUtils.build_payment_uri_config(address)
payment_uri = MoneroUtils.get_payment_uri(tx_config)
logger.debug(f"Testing payment uri: {payment_uri}")
Expand All @@ -358,17 +358,18 @@ def test_get_payment_uri(self, config: TestMoneroUtils.Config) -> None:

# Test invalid payment uri address network type
def test_payment_uri_invalid_network_type(self, config: TestMoneroUtils.Config) -> None:
address = config.testnet.primary_address_1
address: str = config.testnet.primary_address_1
tx_config: MoneroTxConfig = WalletUtils.build_payment_uri_config(address)
try:
MoneroUtils.get_payment_uri(tx_config)
raise Exception("Should have failed")
except Exception as e:
WalletErrorUtils.test_invalid_address_error(e, address)
e_msg: str = str(e)
assert f"Cannot make URI from supplied parameters: wrong address: {address}" == e_msg, e_msg

# Test deprecated standalone payment id
def test_payment_uri_deprecated_payment_uri(self, config: TestMoneroUtils.Config) -> None:
address = config.testnet.primary_address_1
address: str = config.testnet.primary_address_1
tx_config: MoneroTxConfig = WalletUtils.build_payment_uri_config(address)
tx_config.payment_id = "03284e41c342f03603284e41c342f03603284e41c342f03603284e41c342f036"
try:
Expand Down
Loading
Loading