Skip to content
Merged
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 @@ -106,15 +106,15 @@ jobs:
- name: Install monero-python
run: |
mkdir -p build
pip3 install .
pip3 install -vvv .

- name: Setup test environment
run: |
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"
IN_CONTAINER: "true"
run: |
pytest

Expand Down
2 changes: 1 addition & 1 deletion docker/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ cmake --build . &&
make .
cd ../../../

pip3 install . --target build/${PACKAGE_NAME}/usr/lib/python3/dist-packages
pip3 install -vvv . --target build/${PACKAGE_NAME}/usr/lib/python3/dist-packages

cp -R src/python build/${PACKAGE_NAME}/usr/lib/python3/dist-packages/monero
rm -rf build/${PACKAGE_NAME}/usr/lib/python3/dist-packages/pybind11*
Expand Down
18 changes: 15 additions & 3 deletions src/cpp/py_monero.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1662,9 +1662,21 @@ PYBIND11_MODULE(monero, m) {
assert_wallet_is_not_closed(&self);
MONERO_CATCH_AND_RETHROW(self.create_account(label));
}, py::arg("label") = "")
.def("get_subaddress", [](PyMoneroWallet& self, uint32_t account_idx, uint32_t subaddress_idx) {
assert_wallet_is_not_closed(&self);
MONERO_CATCH_AND_RETHROW(self.get_subaddress(account_idx, subaddress_idx));
.def("get_subaddress", [](PyMoneroWallet& wallet, uint32_t account_idx, uint32_t subaddress_idx) {
assert_wallet_is_not_closed(&wallet);
// TODO move this to monero-cpp?
try {
std::vector<uint32_t> subaddress_indices;
subaddress_indices.push_back(subaddress_idx);
auto subaddresses = wallet.get_subaddresses(account_idx, subaddress_indices);
if (subaddresses.empty()) throw std::runtime_error("Subaddress is not initialized");
if (subaddresses.size() != 1) throw std::runtime_error("Only 1 subaddress should be returned");
return subaddresses[0];
} catch (const PyMoneroRpcError& e) {
throw;
} catch (const std::exception& e) {
throw PyMoneroError(e.what());
}
}, py::arg("account_idx"), py::arg("subaddress_idx"))
.def("get_subaddresses", [](PyMoneroWallet& self, uint32_t account_idx) {
assert_wallet_is_not_closed(&self);
Expand Down
5 changes: 3 additions & 2 deletions tests/config/config.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
test_non_relays=True
lite_mode=False
test_notifications=True
network_type=mainnet
network_type=regtest
auto_connect_timeout_ms=3000
mining_address=42U9v3qs5CjZEePHBZHwuSckQXebuZu299NSmVEmQ41YJZQhKcPyujyMSzpDH4VMMVSBo3U3b54JaNvQLwAjqDhKS3rvM3L

[daemon]
rpc_uri=127.0.0.1:18081
Expand All @@ -21,7 +22,7 @@ private_spend_key=be7a2f71097f146bdf0fb5bb8edfe2240a9767e15adee74d95af1b5a64f29a
public_view_key=42e465bdcd00de50516f1c7049bbe26bd3c11195e8dae5cceb38bad92d484269
public_spend_key=b58d33a1dac23d334539cbed3657b69a5c967d6860357e24ab4d11899a312a6b
seed=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
first_receive_height=171
first_receive_height=0
rpc_port_start=18082
rpc_username=rpc_user
rpc_password=abc123
Expand Down
3 changes: 1 addition & 2 deletions tests/test_monero_daemon_rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from utils import TestUtils as Utils, TestContext, BinaryBlockContext, MiningUtils

logger: logging.Logger = logging.getLogger(__name__)
Utils.load_config()


class TestMoneroDaemonRpc:
Expand All @@ -26,7 +25,7 @@ class TestMoneroDaemonRpc:

@pytest.fixture(scope="class", autouse=True)
def before_all(self):
MiningUtils.wait_for_height(101)
MiningUtils.wait_until_blockchain_ready()
MiningUtils.try_stop_mining()

@pytest.fixture(autouse=True)
Expand Down
4 changes: 2 additions & 2 deletions tests/test_monero_wallet_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -771,7 +771,7 @@ def test_get_subaddresses_by_indices(self):
fetched_subaddresses = wallet.get_subaddresses(account.index, subaddress_indices)

# original subaddresses (minus one removed if applicable) is equal to fetched subaddresses
assert TestUtils.assert_subaddresses_equal(subaddresses, fetched_subaddresses)
TestUtils.assert_subaddresses_equal(subaddresses, fetched_subaddresses)

@pytest.mark.skipif(TestUtils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled")
def test_get_subaddress_by_index(self):
Expand Down Expand Up @@ -835,7 +835,7 @@ def test_set_subaddress_label(self):
while subaddress_idx < len(wallet.get_subaddresses(0)):
label = TestUtils.get_random_string()
wallet.set_subaddress_label(0, subaddress_idx, label)
assert (label == wallet.get_subaddress(0, subaddress_idx).label)
assert label == wallet.get_subaddress(0, subaddress_idx).label
subaddress_idx += 1

@pytest.mark.skipif(TestUtils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled")
Expand Down
127 changes: 96 additions & 31 deletions tests/test_monero_wallet_full.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,23 @@
import pytest
import os

from typing import Optional
from typing_extensions import override
from monero import (
MoneroWalletFull, MoneroWalletConfig, MoneroNetworkType, MoneroAccount,
MoneroWalletFull, MoneroWalletConfig, MoneroAccount,
MoneroSubaddress, MoneroDaemonRpc, MoneroWallet
)

from utils import TestUtils as Utils
from test_monero_wallet_common import BaseTestMoneroWallet

Utils.load_config()


# TODO enable full wallet tests
@pytest.mark.skipif(True, reason="TODO")
class TestMoneroWalletFull(BaseTestMoneroWallet):

_daemon: MoneroDaemonRpc = Utils.get_daemon_rpc()
_wallet: MoneroWalletFull = Utils.get_wallet_full() # type: ignore

#region Overrides

@override
def _create_wallet(self, config: Optional[MoneroWalletConfig], start_syncing: bool = True):
# assign defaults
Expand Down Expand Up @@ -56,7 +53,7 @@ def _open_wallet(self, config: Optional[MoneroWalletConfig], start_syncing: bool
config = MoneroWalletConfig()
if config.password is None:
config.password = Utils.WALLET_PASSWORD
if config.network_type is not None:
if config.network_type is None:
config.network_type = Utils.NETWORK_TYPE
if config.server is None and config.connection_manager is None:
config.server = self._daemon.get_rpc_connection()
Expand All @@ -82,33 +79,13 @@ def _get_seed_languages(self) -> list[str]:
def get_test_wallet(self) -> MoneroWallet:
return Utils.get_wallet_full()

def test_wallet_creation_and_close(self):
config_keys = MoneroWalletConfig()
config_keys.language = "English"
config_keys.network_type = MoneroNetworkType.TESTNET
keys_wallet = MoneroWalletFull.create_wallet(config_keys)
seed = keys_wallet.get_seed()

config = MoneroWalletConfig()
config.path = "test_wallet_sync"
config.password = "password"
config.network_type = MoneroNetworkType.TESTNET
config.restore_height = 0
config.seed = seed
config.language = "English"
#endregion

wallet = MoneroWalletFull.create_wallet(config)
assert wallet.is_view_only() is False
wallet.close(save=False)

for ext in ["", ".keys", ".address.txt"]:
try:
os.remove(f"test_wallet_sync{ext}")
except FileNotFoundError:
pass
#region Tests

# Can create a subaddress with and without a label
@pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled")
@override
def test_create_subaddress(self):
# create subaddresses across accounts
accounts: list[MoneroAccount] = self._wallet.get_accounts()
Expand All @@ -122,7 +99,9 @@ def test_create_subaddress(self):
# create subaddress with no label
subaddresses: list[MoneroSubaddress] = self._wallet.get_subaddresses(account_idx)
subaddress: MoneroSubaddress = self._wallet.create_subaddress(account_idx)
Utils.assert_is_none(subaddress.label)
# TODO fix monero-cpp/monero_wallet_full.cpp to return boost::none on empty label
#assert subaddress.label is None
assert subaddress.label is None or subaddress.label == ""
Utils.test_subaddress(subaddress)
subaddresses_new: list[MoneroSubaddress] = self._wallet.get_subaddresses(account_idx)
Utils.assert_equals(len(subaddresses_new) - 1, len(subaddresses))
Expand All @@ -138,3 +117,89 @@ def test_create_subaddress(self):
Utils.assert_equals(len(subaddresses), len(subaddresses_new) - 1)
Utils.assert_equals(subaddress, subaddresses_new[len(subaddresses_new) - 1])
account_idx += 1

#endregion

#region Disabled Tests

@pytest.mark.skipif(Utils.REGTEST, reason="Cannot retrieve accurate height by date from regtest fakechain")
@override
def test_get_height_by_date(self):
return super().test_get_height_by_date()

@pytest.mark.skip(reason="TODO")
@override
def test_create_wallet_random(self) -> None:
return super().test_create_wallet_random()

@pytest.mark.skip(reason="TODO")
@override
def test_create_wallet_from_seed(self, test_config: BaseTestMoneroWallet.Config) -> None:
return super().test_create_wallet_from_seed(test_config)

@pytest.mark.skip(reason="TODO")
@override
def test_create_wallet_from_keys(self) -> None:
return super().test_create_wallet_from_keys()

@pytest.mark.skip(reason="TODO")
@override
def test_create_wallet_from_seed_with_offset(self) -> None:
return super().test_create_wallet_from_seed_with_offset()

@pytest.mark.skip(reason="TODO")
@override
def test_wallet_equality_ground_truth(self):
return super().test_wallet_equality_ground_truth()

@pytest.mark.skip(reason="TODO fix MoneroTxConfig.serialize()")
@override
def test_get_payment_uri(self):
return super().test_get_payment_uri()

@pytest.mark.skip(reason="TODO")
@override
def test_set_tx_note(self) -> None:
return super().test_set_tx_note()

@pytest.mark.skip(reason="TODO")
@override
def test_set_tx_notes(self):
return super().test_set_tx_notes()

@pytest.mark.skip(reason="TODO")
@override
def test_set_daemon_connection(self):
return super().test_set_daemon_connection()

@pytest.mark.skip(reason="TODO")
@override
def test_mining(self):
return super().test_mining()

@pytest.mark.skip(reason="TODO")
@override
def test_export_key_images(self):
return super().test_export_key_images()

@pytest.mark.skip(reason="TODO (monero-project): https://github.com/monero-project/monero/issues/5812")
@override
def test_import_key_images(self):
return super().test_import_key_images()

@pytest.mark.skip(reason="TODO")
@override
def test_get_new_key_images_from_last_import(self):
return super().test_get_new_key_images_from_last_import()

@pytest.mark.skip(reason="TODO")
@override
def test_subaddress_lookahead(self) -> None:
return super().test_subaddress_lookahead()

@pytest.mark.skip(reason="TODO fix segmentation fault")
@override
def test_set_account_label(self) -> None:
super().test_set_account_label()

#endregion
1 change: 0 additions & 1 deletion tests/test_monero_wallet_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
from test_monero_wallet_common import BaseTestMoneroWallet

logger: logging.Logger = logging.getLogger(__name__)
Utils.load_config()


class TestMoneroWalletKeys(BaseTestMoneroWallet):
Expand Down
2 changes: 0 additions & 2 deletions tests/test_monero_wallet_rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
from utils import TestUtils as Utils
from test_monero_wallet_common import BaseTestMoneroWallet

Utils.load_config()


class TestMoneroWalletRpc(BaseTestMoneroWallet):

Expand Down
6 changes: 3 additions & 3 deletions tests/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
from .tx_context import TxContext
from .binary_block_context import BinaryBlockContext
from .sample_connection_listener import SampleConnectionListener
from .string_utils import StringUtils
from .print_height import PrintHeight
from .wallet_equality_utils import WalletEqualityUtils
from .wallet_tx_tracker import WalletTxTracker
from .const import MINING_ADDRESS

__all__ = [
'TestUtils',
Expand All @@ -24,8 +24,8 @@
'TxContext',
'BinaryBlockContext',
'SampleConnectionListener',
'StringUtils',
'PrintHeight',
'WalletEqualityUtils',
'WalletTxTracker',
'MINING_ADDRESS'
'WalletTxTracker'
]
1 change: 0 additions & 1 deletion tests/utils/const.py

This file was deleted.

16 changes: 13 additions & 3 deletions tests/utils/mining_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
from typing import Optional
from time import sleep
from monero import MoneroDaemonRpc
from .const import MINING_ADDRESS
from .test_utils import TestUtils as Utils
from .string_utils import StringUtils

logger: logging.Logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -52,7 +53,7 @@ def start_mining(cls, d: Optional[MoneroDaemonRpc] = None) -> None:
raise Exception("Mining already started")

daemon = cls._get_daemon() if d is None else d
daemon.start_mining(MINING_ADDRESS, 1, False, False)
daemon.start_mining(Utils.MINING_ADDRESS, 1, False, False)

@classmethod
def stop_mining(cls, d: Optional[MoneroDaemonRpc] = None) -> None:
Expand Down Expand Up @@ -105,7 +106,8 @@ def wait_for_height(cls, height: int) -> int:
stop_mining = True

while current_height < height:
logger.info(f"Waiting for blockchain height ({current_height}/{height})")
p = StringUtils.get_percentage(current_height, height)
logger.info(f"[{p}] Waiting for blockchain height ({current_height}/{height})")
block = daemon.wait_for_next_block_header()
assert block.height is not None
current_height = block.height
Expand All @@ -119,3 +121,11 @@ def wait_for_height(cls, height: int) -> int:
logger.info(f"Blockchain height: {current_height}")

return current_height

@classmethod
def wait_until_blockchain_ready(cls) -> None:
"""
Wait until blockchain is ready.
"""
cls.wait_for_height(Utils.MIN_BLOCK_HEIGHT)

9 changes: 9 additions & 0 deletions tests/utils/string_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from abc import ABC


class StringUtils(ABC):

@classmethod
def get_percentage(cls, n: int, m: int, precision: int = 2) -> str:
r: float = (n / m)*100
return f"{round(r, precision)}%"
Loading