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
3 changes: 3 additions & 0 deletions src/cpp/py_monero_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ PYBIND11_MAKE_OPAQUE(VectorUint64);
#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 @@ -156,6 +157,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_<PyGenUtils> py_gen_utils;

Expand Down Expand Up @@ -209,6 +211,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_gen_utils(m, "GenUtils"),
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 @@ -1104,6 +1104,24 @@ void py_monero_bind_wallet(py::module_& m, PyMoneroTypes& t) {
MONERO_CATCH_AND_RETHROW(py::bytes(self.get_cache_file_buffer()));
});

// 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 @@ -140,6 +140,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 @@ -226,6 +227,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.
"""
...
2 changes: 2 additions & 0 deletions tests/config/config.ini
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ rpc_password=abc123
zmq_uri=tcp://127.0.0.1:18085
zmq_pub_uri=tcp://127.0.0.1:18086
log_level=3
lws_uri=http://127.0.0.1:8443
lws_admin_uri=http://127.0.0.1:8444

[wallet]
name=test_wallet_1
Expand Down
29 changes: 28 additions & 1 deletion tests/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ services:
- xmr_wallet_1
- xmr_wallet_2
- xmr_wallet_3
- xmr_lws

node_1:
image: lalanza808/monero:v0.18.5.1
Expand Down Expand Up @@ -161,9 +162,35 @@ 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/
--admin-rest-server=http://0.0.0.0:8444/
--access-control-origin=*
--confirm-external-bind
--max-subaddresses=1000
--auto-accept-creation
--regtest
volumes:
- xmr_lws_data:/data
ports:
- "8443:8443"
- "8444:8444"
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
8 changes: 5 additions & 3 deletions tests/test_monero_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
MoneroBlock, MoneroTxWallet, MoneroIncomingTransfer, MoneroOutputWallet,
MoneroTx
)
from utils import AddressBook, KeysBook, WalletUtils, BaseTestClass, WalletErrorUtils
from utils import AddressBook, KeysBook, WalletUtils, BaseTestClass

logger: logging.Logger = logging.getLogger("TestMoneroUtils")

Expand Down Expand Up @@ -435,7 +435,8 @@ def test_payment_uri_invalid_network_type(self, config: TestMoneroUtils.Config)
tx_config: MoneroTxConfig = WalletUtils.build_payment_uri_config(address)
with pytest.raises(Exception) as exc_info:
MoneroUtils.get_payment_uri(tx_config)
WalletErrorUtils.test_invalid_address_error(exc_info.value, address)
# get_payment_uri() wraps make_uri()'s error with context, unlike e.g. validate_address()
assert str(exc_info.value) == f"Cannot make URI from supplied parameters: wrong address: {address}"

# Test deprecated standalone payment id
def test_payment_uri_deprecated_payment_uri(self, config: TestMoneroUtils.Config) -> None:
Expand All @@ -444,7 +445,8 @@ def test_payment_uri_deprecated_payment_uri(self, config: TestMoneroUtils.Config
tx_config.payment_id = "03284e41c342f03603284e41c342f03603284e41c342f03603284e41c342f036"
with pytest.raises(Exception) as exc_info:
MoneroUtils.get_payment_uri(tx_config, MoneroNetworkType.TESTNET)
WalletErrorUtils.test_deprecated_payment_id_error(exc_info.value)
# get_payment_uri() wraps make_uri()'s error with context, unlike e.g. validate_address()
assert str(exc_info.value) == "Cannot make URI from supplied parameters: Standalone payment id deprecated, use integrated address instead"

# Can get version
def test_get_version(self) -> None:
Expand Down
35 changes: 29 additions & 6 deletions tests/test_monero_wallet_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
MoneroIntegratedAddress, MoneroCheckTx, MoneroCheckReserve, MoneroAddressBookEntry,
MoneroSubmitTxResult, MoneroAccountTag, MoneroKeyImageExportResult, MoneroWalletFull,
MoneroKeyImageImportResult, MoneroMessageSignatureResult,
MoneroMiningStatus, MoneroVersion, MoneroSyncResult,
MoneroMiningStatus, MoneroVersion, MoneroSyncResult, MoneroWalletLight
)
from utils import (
MultisigSampleCodeTester, TestUtils, WalletEqualityUtils,
MultisigSampleCodeTester, TestUtils, WalletEqualityUtils, BlockchainUtils,
StringUtils, AssertUtils, TxContext, GenUtils, WalletUtils,
WalletType, IntegrationTestUtils, ViewOnlyAndOfflineWalletTester,
WalletNotificationCollector, MiningUtils, BaseTestClass,
Expand Down Expand Up @@ -98,6 +98,8 @@ def get_test_wallet(cls) -> MoneroWallet:
cls._test_wallet = TestUtils.get_wallet_rpc()
elif wallet_type == WalletType.KEYS:
cls._test_wallet = TestUtils.get_wallet_keys()
elif wallet_type == WalletType.LIGHT:
cls._test_wallet = TestUtils.get_wallet_light()
else:
raise Exception("Cannot get test wallet: No wallet type setup for tests")

Expand Down Expand Up @@ -460,6 +462,10 @@ def test_send_to_external(self, wallet: MoneroWallet) -> None:
txs: list[MoneroTxWallet] = wallet.get_txs(tx_query)
assert len(txs) > 0

if isinstance(recipient, MoneroWalletLight):
# lws doesn't report unconfirmed txs, must mine some blocks
BlockchainUtils.wait_for_blocks(5)

# test recipient balance after
recipient.sync()
assert amount == recipient.get_balance()
Expand Down Expand Up @@ -903,19 +909,32 @@ def test_subaddress_lookahead(self, wallet: MoneroWallet) -> None:
config: MoneroWalletConfig = MoneroWalletConfig()
config.account_lookahead = 1
config.subaddress_lookahead = 100000
subaddress_idx: int = 85000

if isinstance(wallet, MoneroWalletLight):
subaddress_lookahead: int = int(TestUtils.MAX_LWS_SUBADDRESSES / 2)
config.subaddress_lookahead = subaddress_lookahead
subaddress_idx = subaddress_lookahead - 1

receiver = self._create_wallet(config)

# transfer funds to subaddress with high index
tx_config: MoneroTxConfig = MoneroTxConfig()
tx_config.account_index = 0
dest: MoneroDestination = MoneroDestination()
dest.address = receiver.get_subaddress(0, 85000).address
dest.address = receiver.get_subaddress(0, subaddress_idx).address
dest.amount = TxWalletUtils.MAX_FEE
tx_config.destinations.append(dest)
tx_config.relay = True

wallet.create_tx(tx_config)

if isinstance(receiver, MoneroWalletLight):
# lws doesn't report unconfirmed txs, must mine some blocks
current_height: int = BlockchainUtils.wait_for_blocks(10)
while receiver.get_height() < current_height:
receiver.sync()

# observe unconfirmed funds
GenUtils.wait_for(1000)
receiver.sync()
Expand Down Expand Up @@ -2914,8 +2933,8 @@ def test_get_new_key_images_from_last_import(self, wallet: MoneroWallet) -> None
assert image.signature is not None and len(image.signature) > 0

# Can import key images
# TODO monero-project: importing key images can cause erasure of incoming transfers per wallet2.cpp:11957
@pytest.mark.skipif(TestUtils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled")
@pytest.mark.skip(reason="TODO monero-project: importing key images can cause erasure of incoming transfers per wallet2.cpp:11957")
def test_import_key_images(self, wallet: MoneroWallet) -> None:
export_result: MoneroKeyImageExportResult = wallet.export_key_images()
assert len(export_result.key_images) > 0, "Wallet does not have any key images run send tests"
Expand Down Expand Up @@ -3161,7 +3180,7 @@ def test_get_payment_uri(self, wallet: MoneroWallet) -> None:

# test with standalone payment id
config1.payment_id = "03284e41c342f03603284e41c342f03603284e41c342f03603284e41c342f036"
with pytest.raises(Exception, match="Cannot make URI from supplied parameters"):
with pytest.raises(Exception, match="Standalone payment id deprecated, use integrated address instead"):
wallet.get_payment_uri(config1)

# Can start and stop mining
Expand Down Expand Up @@ -3308,7 +3327,11 @@ def test_freeze_outputs(self, wallet: MoneroWallet) -> None:
tx_config.key_image = output.key_image.hex
with pytest.raises(Exception) as exc_info:
wallet.sweep_output(tx_config)
assert str(exc_info.value) == "No outputs found"
raise Exception("Should have thrown error")

if not isinstance(wallet, MoneroWalletLight):
# TODO MoneroWalletLight does not support sweeping
assert str(exc_info.value) == "No outputs found"

# try to freeze empty key image
with pytest.raises(Exception) as exc_info:
Expand Down
Loading
Loading