Skip to content

Commit bc6ad73

Browse files
Consolidate tests and configuration
* Refactory * Fix performance issues * Make test utils configurable via config.ini
1 parent 8d8cf18 commit bc6ad73

25 files changed

+368
-409
lines changed

README.md

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
[![Tests](https://github.com/everoddandeven/monero-python/actions/workflows/test.yml/badge.svg)](https://github.com/everoddandeven/monero-python/actions/workflows/test.yml)
44
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/aeff91a5b1d543ddb400f88ffce150a8)](https://app.codacy.com/gh/everoddandeven/monero-python/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
55

6-
> [!NOTE]
6+
> [!WARNING]
77
>
88
> monero-python is currently under maintenance, expect bugs and breaking changes.
99
> The maintenance of this project has been generously funded by the [Monero CCS](https://ccs.getmonero.org/proposals/everoddandeven-monero-python-maintenance.html).
@@ -35,7 +35,7 @@ from monero import *
3535

3636
# connect to daemon
3737
daemon: MoneroDaemon = MoneroDaemonRpc("http://localhost:38081", "superuser", "abctesting123")
38-
height: int = daemon.get_height(); # 1523651
38+
height: int = daemon.get_height() # 1523651
3939
txsInPool: list[MoneroTx] = daemon.get_tx_pool() # get transactions in the pool
4040

4141
# create wallet from mnemonic phrase using Python bindings to monero-project
@@ -170,7 +170,7 @@ For example: `export LD_PRELOAD=/path/to/libjemalloc.a` then run your app.
170170
```bash
171171
# With PIP
172172
173-
pip3 install pytest --break-system-packages
173+
pip3 install pytest pytest-rerunfailures --break-system-packages
174174
```
175175
```bash
176176
# System-wide installation Ubuntu/Debian
@@ -185,14 +185,22 @@ For example: `export LD_PRELOAD=/path/to/libjemalloc.a` then run your app.
185185
2. Clone the project repository:
186186
```bash
187187
git clone --recurse-submodules https://github.com/everoddandeven/monero-python.git
188+
189+
cd monero-python
188190
```
189-
3. `cd monero-python`
190-
4. Start RPC servers:
191+
3. Setup docker test environment
192+
```bash
193+
docker compose -f tests/docker-compose.yml up -d node_1 node_2 xmr_wallet_1 xmr_wallet_2
194+
```
195+
4. Or start RPC servers locally:
191196
1. Download and install [Monero CLI](https://web.getmonero.org/downloads/).
192197
2. Start monerod, e.g.: `./monerod --stagenet` (or use a remote daemon).
193198
3. Start monero-wallet-rpc, e.g.: `./monero-wallet-rpc --daemon-address http://localhost:38081 --stagenet --rpc-bind-port 38083 --rpc-login rpc_user:abc123 --wallet-dir ./`
194-
5. Configure the appropriate RPC endpoints, authentication, and other settings in [monero_test_utils.py](tests/utils/monero_test_utils.py).
195-
6. Run all *.py files in tests folder with `pytest`.
199+
5. Configure the appropriate RPC endpoints, authentication, and other settings in [config.ini](tests/config/config.ini).
200+
6. Run all python tests with:
201+
```bash
202+
pytest
203+
```
196204

197205

198206
## Related projects

pytest.ini

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
[pytest]
22
minversion = 6.0
3-
addopts = -ra -q
3+
addopts = -v
4+
log_level = INFO
5+
log_cli = True
46
log_cli_level = INFO
7+
console_output_style = progress
58
testpaths =
69
tests

src/cpp/daemon/py_monero_daemon_model.cpp

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,6 @@ void PyMoneroBlock::from_property_tree(const boost::property_tree::ptree& node,
340340
void PyMoneroBlock::from_property_tree(const boost::property_tree::ptree& node, const std::vector<uint64_t>& heights, std::vector<std::shared_ptr<monero::monero_block>>& blocks) {
341341
const auto& rpc_blocks = node.get_child("blocks");
342342
const auto& rpc_txs = node.get_child("txs");
343-
344343
if (rpc_blocks.size() != rpc_txs.size()) {
345344
throw std::runtime_error("blocks and txs size mismatch");
346345
}
@@ -358,18 +357,14 @@ void PyMoneroBlock::from_property_tree(const boost::property_tree::ptree& node,
358357
PyMoneroBlock::from_property_tree(block_n, block);
359358
block->m_height = heights.at(idx);
360359
blocks.push_back(block);
361-
362360
std::vector<std::string> tx_hashes;
363361
if (auto hashes = it_block->second.get_child_optional("tx_hashes")) {
364-
for (const auto& h : *hashes) {
365-
tx_hashes.push_back(h.second.get_value<std::string>());
366-
}
362+
for (const auto& h : *hashes) tx_hashes.push_back(h.second.get_value<std::string>());
367363
}
368364

369365
// build transactions
370366
std::vector<std::shared_ptr<monero::monero_tx>> txs;
371367
size_t tx_idx = 0;
372-
373368
for (const auto& tx_node : it_txs->second) {
374369
auto tx = std::make_shared<monero::monero_tx>();
375370
tx->m_hash = tx_hashes.at(tx_idx++);
@@ -390,9 +385,7 @@ void PyMoneroBlock::from_property_tree(const boost::property_tree::ptree& node,
390385
// merge into one block
391386
block->m_txs.clear();
392387
for (auto& tx : txs) {
393-
if (tx->m_block != boost::none) {
394-
block->merge(block, tx->m_block.get());
395-
}
388+
if (tx->m_block != boost::none) block->merge(block, tx->m_block.get());
396389
else {
397390
tx->m_block = block;
398391
block->m_txs.push_back(tx);
@@ -401,7 +394,6 @@ void PyMoneroBlock::from_property_tree(const boost::property_tree::ptree& node,
401394
}
402395
}
403396

404-
405397
void PyMoneroOutput::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr<monero_output>& output) {
406398
for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) {
407399
std::string key = it->first;
@@ -712,10 +704,15 @@ void PyMoneroMiningStatus::from_property_tree(const boost::property_tree::ptree&
712704
std::string key = it->first;
713705
if (key == std::string("active")) status->m_is_active = it->second.get_value<bool>();
714706
else if (key == std::string("is_background_mining_enabled")) status->m_is_background = it->second.get_value<bool>();
715-
else if (key == std::string("address")) status->m_address = it->second.data();
707+
else if (key == std::string("address") && !it->second.data().empty()) status->m_address = it->second.data();
716708
else if (key == std::string("speed")) status->m_speed = it->second.get_value<uint64_t>();
717709
else if (key == std::string("threads_count")) status->m_num_threads = it->second.get_value<int>();
718710
}
711+
712+
if (status->m_is_active != boost::none && *status->m_is_active == false) {
713+
status->m_is_background = boost::none;
714+
status->m_address = boost::none;
715+
}
719716
}
720717

721718
std::vector<std::string> PyMoneroTxHashes::from_property_tree(const boost::property_tree::ptree& node) {
@@ -973,15 +970,9 @@ void PyMoneroDaemonInfo::from_property_tree(const boost::property_tree::ptree& n
973970
else if (key == std::string("height_without_bootstrap")) info->m_height_without_bootstrap = it->second.get_value<uint64_t>();
974971
else if (key == std::string("nettype")) {
975972
std::string nettype = it->second.data();
976-
if (nettype == std::string("mainnet") || nettype == std::string("fakechain")) {
977-
info->m_network_type = monero::monero_network_type::MAINNET;
978-
}
979-
else if (nettype == std::string("testnet")) {
980-
info->m_network_type = monero::monero_network_type::TESTNET;
981-
}
982-
else if (nettype == std::string("stagenet")) {
983-
info->m_network_type = monero::monero_network_type::STAGENET;
984-
}
973+
if (nettype == std::string("mainnet") || nettype == std::string("fakechain")) info->m_network_type = monero::monero_network_type::MAINNET;
974+
else if (nettype == std::string("testnet")) info->m_network_type = monero::monero_network_type::TESTNET;
975+
else if (nettype == std::string("stagenet")) info->m_network_type = monero::monero_network_type::STAGENET;
985976
}
986977
else if (key == std::string("offline")) info->m_is_offline = it->second.get_value<bool>();
987978
else if (key == std::string("incoming_connections_count")) info->m_num_incoming_connections = it->second.get_value<int>();

src/cpp/wallet/py_monero_wallet_model.cpp

Lines changed: 43 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,19 +1215,18 @@ rapidjson::Value PyMoneroWalletRelayTxParams::to_rapidjson_val(rapidjson::Docume
12151215
return root;
12161216
}
12171217

1218-
PyMoneroSweepParams::PyMoneroSweepParams(const monero_tx_config& config) {
1219-
m_address = config.m_address;
1220-
m_account_index = config.m_account_index;
1221-
m_subaddr_indices = config.m_subaddress_indices;
1222-
m_key_image = config.m_key_image;
1223-
m_relay = config.m_relay;
1224-
m_priority = config.m_priority;
1225-
m_payment_id = config.m_payment_id;
1226-
m_below_amount = config.m_below_amount;
1227-
m_get_tx_key = true;
1228-
m_get_tx_hex = true;
1229-
m_get_tx_metadata = true;
1230-
}
1218+
PyMoneroSweepParams::PyMoneroSweepParams(const monero_tx_config& config):
1219+
m_address(config.m_address),
1220+
m_account_index(config.m_account_index),
1221+
m_subaddr_indices(config.m_subaddress_indices),
1222+
m_key_image(config.m_key_image),
1223+
m_relay(config.m_relay),
1224+
m_priority(config.m_priority),
1225+
m_payment_id(config.m_payment_id),
1226+
m_below_amount(config.m_below_amount),
1227+
m_get_tx_key(true),
1228+
m_get_tx_hex(true),
1229+
m_get_tx_metadata(true) { }
12311230

12321231
rapidjson::Value PyMoneroSweepParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const {
12331232
rapidjson::Value root(rapidjson::kObjectType);
@@ -1301,37 +1300,30 @@ rapidjson::Value PyMoneroImportExportKeyImagesParams::to_rapidjson_val(rapidjson
13011300
return root;
13021301
}
13031302

1304-
PyMoneroCreateOpenWalletParams::PyMoneroCreateOpenWalletParams(const boost::optional<std::string>& filename, const boost::optional<std::string> &password) {
1305-
m_filename = filename;
1306-
m_password = password;
1307-
}
1303+
PyMoneroCreateOpenWalletParams::PyMoneroCreateOpenWalletParams(const boost::optional<std::string>& filename, const boost::optional<std::string> &password):
1304+
m_filename(filename), m_password(password) { }
13081305

1309-
PyMoneroCreateOpenWalletParams::PyMoneroCreateOpenWalletParams(const boost::optional<std::string>& filename, const boost::optional<std::string> &password, const boost::optional<std::string> &language) {
1310-
m_filename = filename;
1311-
m_password = password;
1312-
m_language = language;
1313-
}
1306+
PyMoneroCreateOpenWalletParams::PyMoneroCreateOpenWalletParams(const boost::optional<std::string>& filename, const boost::optional<std::string> &password, const boost::optional<std::string> &language):
1307+
m_filename(filename), m_password(password), m_language(language) { }
13141308

1315-
PyMoneroCreateOpenWalletParams::PyMoneroCreateOpenWalletParams(const boost::optional<std::string>& filename, const boost::optional<std::string> &password, const boost::optional<std::string> &seed, const boost::optional<std::string> &seed_offset, const boost::optional<uint64_t> &restore_height, const boost::optional<std::string> &language, const boost::optional<bool> &autosave_current, const boost::optional<bool> &enable_multisig_experimental) {
1316-
m_filename = filename;
1317-
m_password = password;
1318-
m_seed = seed;
1319-
m_seed_offset = seed_offset;
1320-
m_restore_height = restore_height;
1321-
m_language = language;
1322-
m_autosave_current = autosave_current;
1323-
m_enable_multisig_experimental = enable_multisig_experimental;
1324-
}
1309+
PyMoneroCreateOpenWalletParams::PyMoneroCreateOpenWalletParams(const boost::optional<std::string>& filename, const boost::optional<std::string> &password, const boost::optional<std::string> &seed, const boost::optional<std::string> &seed_offset, const boost::optional<uint64_t> &restore_height, const boost::optional<std::string> &language, const boost::optional<bool> &autosave_current, const boost::optional<bool> &enable_multisig_experimental):
1310+
m_filename(filename),
1311+
m_password(password),
1312+
m_seed(seed),
1313+
m_seed_offset(seed_offset),
1314+
m_restore_height(restore_height),
1315+
m_language(language),
1316+
m_autosave_current(autosave_current),
1317+
m_enable_multisig_experimental(enable_multisig_experimental) { }
13251318

1326-
PyMoneroCreateOpenWalletParams::PyMoneroCreateOpenWalletParams(const boost::optional<std::string>& filename, const boost::optional<std::string> &password, const boost::optional<std::string> &address, const boost::optional<std::string> &view_key, const boost::optional<std::string> &spend_key, const boost::optional<uint64_t> &restore_height, const boost::optional<bool> &autosave_current) {
1327-
m_filename = filename;
1328-
m_password = password;
1329-
m_address = address;
1330-
m_view_key = view_key;
1331-
m_spend_key = spend_key;
1332-
m_restore_height = restore_height;
1333-
m_autosave_current = autosave_current;
1334-
}
1319+
PyMoneroCreateOpenWalletParams::PyMoneroCreateOpenWalletParams(const boost::optional<std::string>& filename, const boost::optional<std::string> &password, const boost::optional<std::string> &address, const boost::optional<std::string> &view_key, const boost::optional<std::string> &spend_key, const boost::optional<uint64_t> &restore_height, const boost::optional<bool> &autosave_current):
1320+
m_filename(filename),
1321+
m_password(password),
1322+
m_address(address),
1323+
m_view_key(view_key),
1324+
m_spend_key(spend_key),
1325+
m_restore_height(restore_height),
1326+
m_autosave_current(autosave_current) { }
13351327

13361328
rapidjson::Value PyMoneroCreateOpenWalletParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const {
13371329
rapidjson::Value root(rapidjson::kObjectType);
@@ -1351,34 +1343,20 @@ rapidjson::Value PyMoneroCreateOpenWalletParams::to_rapidjson_val(rapidjson::Doc
13511343
return root;
13521344
}
13531345

1354-
PyMoneroReserveProofParams::PyMoneroReserveProofParams(const std::string &message, bool all) {
1355-
m_all = all;
1356-
m_message = message;
1357-
}
1346+
PyMoneroReserveProofParams::PyMoneroReserveProofParams(const std::string &message, bool all):
1347+
m_all(all), m_message(message) { }
13581348

1359-
PyMoneroReserveProofParams::PyMoneroReserveProofParams(const std::string &address, const std::string &message, const std::string &signature) {
1360-
m_address = address;
1361-
m_message = message;
1362-
m_signature = signature;
1363-
}
1349+
PyMoneroReserveProofParams::PyMoneroReserveProofParams(const std::string &address, const std::string &message, const std::string &signature):
1350+
m_address(address), m_message(message), m_signature(signature) { }
13641351

1365-
PyMoneroReserveProofParams::PyMoneroReserveProofParams(const std::string &tx_hash, const std::string &address, const std::string &message, const std::string &signature) {
1366-
m_tx_hash = tx_hash;
1367-
m_address = address;
1368-
m_message = message;
1369-
m_signature = signature;
1370-
}
1352+
PyMoneroReserveProofParams::PyMoneroReserveProofParams(const std::string &tx_hash, const std::string &address, const std::string &message, const std::string &signature):
1353+
m_tx_hash(tx_hash), m_address(address), m_message(message), m_signature(signature) { }
13711354

1372-
PyMoneroReserveProofParams::PyMoneroReserveProofParams(const std::string &tx_hash, const std::string &message) {
1373-
m_tx_hash = tx_hash;
1374-
m_message = message;
1375-
}
1355+
PyMoneroReserveProofParams::PyMoneroReserveProofParams(const std::string &tx_hash, const std::string &message):
1356+
m_tx_hash(tx_hash), m_message(message) { }
13761357

1377-
PyMoneroReserveProofParams::PyMoneroReserveProofParams(uint32_t account_index, uint64_t amount, const std::string &message) {
1378-
m_account_index = account_index;
1379-
m_amount = amount;
1380-
m_message = message;
1381-
}
1358+
PyMoneroReserveProofParams::PyMoneroReserveProofParams(uint32_t account_index, uint64_t amount, const std::string &message):
1359+
m_account_index(account_index), m_amount(amount), m_message(message) { }
13821360

13831361
rapidjson::Value PyMoneroReserveProofParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const {
13841362
rapidjson::Value root(rapidjson::kObjectType);

src/cpp/wallet/py_monero_wallet_rpc.cpp

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -602,8 +602,10 @@ monero_account PyMoneroWalletRpc::get_account(const uint32_t account_idx, bool i
602602
monero_account PyMoneroWalletRpc::get_account(const uint32_t account_idx, bool include_subaddresses, bool skip_balances) const {
603603
for(auto& account : monero::monero_wallet::get_accounts()) {
604604
if (account.m_index.get() == account_idx) {
605-
std::vector<uint32_t> empty_indices;
606-
if (include_subaddresses) account.m_subaddresses = get_subaddresses(account_idx, empty_indices, skip_balances);
605+
if (include_subaddresses) {
606+
std::vector<uint32_t> empty_indices;
607+
account.m_subaddresses = get_subaddresses(account_idx, empty_indices, skip_balances);
608+
}
607609
return account;
608610
}
609611
}
@@ -701,13 +703,11 @@ std::vector<monero_subaddress> PyMoneroWalletRpc::get_subaddresses(const uint32_
701703
auto response = m_rpc->send_json_request(request);
702704
if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response");
703705
auto node = response->m_result.get();
704-
705706
std::vector<monero_subaddress> subaddresses;
706707

707708
// initialize subaddresses
708709
for (auto it = node.begin(); it != node.end(); ++it) {
709710
std::string key = it->first;
710-
711711
if (key == std::string("addresses")) {
712712
auto node2 = it->second;
713713
for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) {
@@ -718,12 +718,10 @@ std::vector<monero_subaddress> PyMoneroWalletRpc::get_subaddresses(const uint32_
718718
}
719719
break;
720720
}
721-
722721
}
723722

724723
// fetch and initialize subaddress balances
725724
if (!skip_balances) {
726-
727725
// these fields are not initialized if subaddress is unused and therefore not returned from `get_balance`
728726
for (auto &subaddress : subaddresses) {
729727
subaddress.m_balance = 0;
@@ -737,7 +735,6 @@ std::vector<monero_subaddress> PyMoneroWalletRpc::get_subaddresses(const uint32_
737735
auto response2 = m_rpc->send_json_request(request);
738736
if (response2->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response");
739737
auto node2 = response2->m_result.get();
740-
741738
std::vector<std::shared_ptr<monero::monero_subaddress>> subaddresses2;
742739
PyMoneroSubaddress::from_rpc_property_tree(node2, subaddresses2);
743740

tests/config/config.ini

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
[general]
2+
test_non_relays=True
3+
lite_mode=False
4+
test_notifications=True
5+
network_type=mainnet
6+
auto_connect_timeout_ms=3000
7+
8+
[daemon]
9+
rpc_uri=127.0.0.1:18081
10+
rpc_username=
11+
rpc_password=
12+
13+
[wallet]
14+
name=test_wallet_1
15+
password=supersecretpassword123
16+
dir=./test_wallets
17+
language=English
18+
address=48W9YHwPzRz9aPTeXCA6kmSpW6HsvmWx578jj3of2gT3JwZzwTf33amESBoNDkL6SVK34Q2HTKqgYbGyE1hBws3wCrcBDR2
19+
private_view_key=e8c2288181bad9ec410d7322efd65f663c6da57bd1d1198636278a039743a600
20+
private_spend_key=be7a2f71097f146bdf0fb5bb8edfe2240a9767e15adee74d95af1b5a64f29a0c
21+
public_view_key=42e465bdcd00de50516f1c7049bbe26bd3c11195e8dae5cceb38bad92d484269
22+
public_spend_key=b58d33a1dac23d334539cbed3657b69a5c967d6860357e24ab4d11899a312a6b
23+
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
24+
first_receive_height=171
25+
rpc_port_start=18082
26+
rpc_username=rpc_user
27+
rpc_password=abc123
28+
rpc_access_control_origins="http:#localhost:8080"
29+
rpc_domain=localhost
30+
rpc_zmq_enabled=False
31+
rpc_zmq_port_start=48083
32+
rpc_zmq_bind_port_start=48083
33+
rpc_zmq_domain=127.0.0.1
34+
sync_period_in_ms=5000

tests/config/test_monero_utils.ini

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,5 @@ public_spend_key=3e48df9e9d8038dbf6f5382fac2becd8686273cda5bd87187e45dca7ec5af37
5353
invalid_private_view_key=5B8s3obCY2ETeQB3GNAGPK2zRGen5UeW1WzegSizVsmf6z5NvM2GLoN6zzk1vHyzGAAfA8pGhuYAeCFZjHAp59jRVQkunGS
5454
invalid_public_view_key=z86cf351d10894769feba29b9e201e12fb100b85bb52fc5825c864eef55c5840d
5555
invalid_private_spend_key=z86cf351d10894769feba29b9e201e12fb100b85bb52fc5825c864eef55c5840d
56-
invalid_public_spend_key=z86cf351d10894769feba29b9e201e12fb100b85bb52fc5825c864eef55c5840d
56+
invalid_public_spend_key=z86cf351d10894769feba29b9e201e12fb100b85bb52fc5825c864eef55c5840d
57+
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

tests/test_monero_connection_manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from monero import (
55
MoneroWallet, MoneroConnectionManager, MoneroRpcConnection, MoneroConnectionPollType
66
)
7-
from utils import ConnectionChangeCollector, MoneroTestUtils as Utils
7+
from utils import ConnectionChangeCollector, TestUtils as Utils
88

99

1010
# TODO enable connection manager tests

0 commit comments

Comments
 (0)