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
29 changes: 29 additions & 0 deletions src/cpp/py_monero.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1798,9 +1798,28 @@ PYBIND11_MODULE(monero, m) {
.def("get_transfers", [](PyMoneroWallet& self, const monero::monero_transfer_query& query) {
MONERO_CATCH_AND_RETHROW(self.get_transfers(query));
}, py::arg("query"))
.def("get_transfers", [](PyMoneroWallet& self) {
monero::monero_transfer_query query;
MONERO_CATCH_AND_RETHROW(self.get_transfers(query));
})
.def("get_transfers", [](PyMoneroWallet& self, uint32_t account_index) {
monero::monero_transfer_query query;
query.m_account_index = account_index;
MONERO_CATCH_AND_RETHROW(self.get_transfers(query));
}, py::arg("account_index"))
.def("get_transfers", [](PyMoneroWallet& self, uint32_t account_index, uint32_t subaddress_index) {
monero::monero_transfer_query query;
query.m_account_index = account_index;
query.m_subaddress_index = subaddress_index;
MONERO_CATCH_AND_RETHROW(self.get_transfers(query));
}, py::arg("account_index"), py::arg("subaddress_index"))
.def("get_outputs", [](PyMoneroWallet& self, const monero::monero_output_query& query) {
MONERO_CATCH_AND_RETHROW(self.get_outputs(query));
}, py::arg("query"))
.def("get_outputs", [](PyMoneroWallet& self) {
monero::monero_output_query query;
MONERO_CATCH_AND_RETHROW(self.get_outputs(query));
})
.def("export_outputs", [](PyMoneroWallet& self, bool all) {
MONERO_CATCH_AND_RETHROW(self.export_outputs(all));
}, py::arg("all") = false)
Expand Down Expand Up @@ -1858,6 +1877,16 @@ PYBIND11_MODULE(monero, m) {
.def("describe_tx_set", [](PyMoneroWallet& self, const monero::monero_tx_set& tx_set) {
MONERO_CATCH_AND_RETHROW(self.describe_tx_set(tx_set));
}, py::arg("tx_set"))
.def("describe_unsigned_tx_set", [](PyMoneroWallet& self, const std::string& unsigned_tx_hex) {
monero::monero_tx_set tx_set;
tx_set.m_unsigned_tx_hex = unsigned_tx_hex;
MONERO_CATCH_AND_RETHROW(self.describe_tx_set(tx_set));
}, py::arg("unsigned_tx_hex"))
.def("describe_multisig_tx_set", [](PyMoneroWallet& self, const std::string& multisig_tx_hex) {
monero::monero_tx_set tx_set;
tx_set.m_multisig_tx_hex = multisig_tx_hex;
MONERO_CATCH_AND_RETHROW(self.describe_tx_set(tx_set));
}, py::arg("multisig_tx_hex"))
.def("sign_txs", [](PyMoneroWallet& self, const std::string& unsigned_tx_hex) {
MONERO_CATCH_AND_RETHROW(self.sign_txs(unsigned_tx_hex));
}, py::arg("unsigned_tx_hex"))
Expand Down
85 changes: 85 additions & 0 deletions src/python/monero_wallet.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,22 @@ class MoneroWallet:
:param int index: is the index of the entry to delete
"""
...
def describe_unsigned_tx_set(self, unsigned_tx_hex: str) -> MoneroTxSet:
"""
Describe a tx set from unsigned tx hex.

:param str unsigned_tx_hex: unsigned tx hex
:returns MoneroTxSet: the tx set containing structured transactions
"""
...
def describe_multisig_tx_set(self, multisig_tx_hex: str) -> MoneroTxSet:
"""
Describe a tx set containing unsigned or multisig tx hex to a new tx set containing structured transactions.

:param str multisig_tx_hex: multisig tx hex
:returns MoneroTxSet: the tx set containing structured transactions
"""
...
def describe_tx_set(self, tx_set: MoneroTxSet) -> MoneroTxSet:
"""
Describes a tx set containing unsigned or multisig tx hex to a new tx set containing structured transactions.
Expand Down Expand Up @@ -443,6 +459,17 @@ class MoneroWallet:
:return list[MoneroKeyImage]: the key images from the last imported outputs.
"""
...
@typing.overload
def get_outputs(self) -> list[MoneroOutputWallet]:
"""
Get outputs created from previous transactions that belong to the wallet
(i.e. that the wallet can spend one time). Outputs are part of
transactions which are stored in blocks on the blockchain.

:return list[MoneroOutputWallet]: all wallet outputs
"""
...
@typing.overload
def get_outputs(self, query: MoneroOutputQuery) -> list[MoneroOutputWallet]:
"""
Get outputs created from previous transactions that belong to the wallet
Expand Down Expand Up @@ -583,6 +610,25 @@ class MoneroWallet:
:return list[MoneroSubaddress]: the retrieved subaddresses
"""
...
@typing.overload
def get_transfers(self) -> list[MoneroTransfer]:
"""
Get incoming and outgoing transfers to and from this wallet. An outgoing
transfer represents a total amount sent from one or more subaddresses
within an account to individual destination addresses, each with their
own amount. An incoming transfer represents a total amount received into
a subaddress within an account. Transfers belong to transactions which
are stored on the blockchain.

Query results can be filtered by passing in a monero_transfer_query.
Transfers must meet every criteria defined in the query in order to be
returned. All filtering is optional and no filtering is applied when not
defined.

:return list[MoneroTransfer]: wallet transfers per the query (free memory using MoneroUtils.free())
"""
...
@typing.overload
def get_transfers(self, query: MoneroTransferQuery) -> list[MoneroTransfer]:
"""
Get incoming and outgoing transfers to and from this wallet. An outgoing
Expand All @@ -601,6 +647,45 @@ class MoneroWallet:
:return list[MoneroTransfer]: wallet transfers per the query (free memory using MoneroUtils.free())
"""
...
@typing.overload
def get_transfers(self, account_idx: int) -> list[MoneroTransfer]:
"""
Get incoming and outgoing transfers to and from this wallet. An outgoing
transfer represents a total amount sent from one or more subaddresses
within an account to individual destination addresses, each with their
own amount. An incoming transfer represents a total amount received into
a subaddress within an account. Transfers belong to transactions which
are stored on the blockchain.

Query results can be filtered by passing in a monero_transfer_query.
Transfers must meet every criteria defined in the query in order to be
returned. All filtering is optional and no filtering is applied when not
defined.

:param int account_idx: is the index of the account to get transfers from
:return list[MoneroTransfer]: transfers to/from the account
"""
...
@typing.overload
def get_transfers(self, account_idx: int, subaddress_idx: int) -> list[MoneroTransfer]:
"""
Get incoming and outgoing transfers to and from this wallet. An outgoing
transfer represents a total amount sent from one or more subaddresses
within an account to individual destination addresses, each with their
own amount. An incoming transfer represents a total amount received into
a subaddress within an account. Transfers belong to transactions which
are stored on the blockchain.

Query results can be filtered by passing in a monero_transfer_query.
Transfers must meet every criteria defined in the query in order to be
returned. All filtering is optional and no filtering is applied when not
defined.

:param int account_idx: is the index of the account to get transfers from
:param int subaddress_idx: is the index of the subaddress to get transfers from
:return list[MoneroTransfer]: transfers to/from the accsubaddressount
"""
...
def get_tx_key(self, tx_hash: str) -> str:
"""
Get a transaction's secret key from its hash.
Expand Down
32 changes: 31 additions & 1 deletion tests/test_monero_wallet_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
TestUtils, WalletEqualityUtils,
StringUtils, AssertUtils, TxUtils,
TxContext, GenUtils, WalletUtils,
WalletType, IntegrationTestUtils
WalletType, IntegrationTestUtils,
ViewOnlyAndOfflineWalletTester
)

logger: logging.Logger = logging.getLogger("TestMoneroWalletCommon")
Expand Down Expand Up @@ -2732,6 +2733,35 @@ def test_import_key_images(self, wallet: MoneroWallet) -> None:
GenUtils.test_unsigned_big_integer(result.spent_amount, has_spent)
GenUtils.test_unsigned_big_integer(result.unspent_amount, has_unspent)

# Supports view-only and offline wallets to create, sign and submit transactions
@pytest.mark.skipif(TestUtils.LITE_MODE, reason="LITE_MODE enabled")
@pytest.mark.skipif(TestUtils.TEST_NON_RELAYS is False and TestUtils.TEST_RELAYS is False, reason="TEST_NON_RELAYS and TEST_RELAYS disabled")
def test_view_only_and_offline_wallets(self, wallet: MoneroWallet) -> None:
# create view-only and offline wallets
config: MoneroWalletConfig = MoneroWalletConfig()
config.primary_address = wallet.get_primary_address()
config.private_view_key = wallet.get_private_view_key()
config.restore_height = TestUtils.FIRST_RECEIVE_HEIGHT
view_only_wallet: MoneroWallet = self._create_wallet(config)

config = MoneroWalletConfig()
config.primary_address = wallet.get_primary_address()
config.private_view_key = wallet.get_private_view_key()
config.private_spend_key = wallet.get_private_spend_key()
config.server = MoneroRpcConnection(TestUtils.OFFLINE_SERVER_URI)
config.restore_height = 0
offline_wallet: MoneroWallet = self._create_wallet(config)
assert offline_wallet.is_connected_to_daemon() is False
view_only_wallet.sync()

# test tx signing with wallets
try:
tester = ViewOnlyAndOfflineWalletTester(wallet, view_only_wallet, offline_wallet)
tester.test()
finally:
self._close_wallet(view_only_wallet)
self._close_wallet(offline_wallet)

# Can sign and verify messages
# TODO test with view-only wallet
@pytest.mark.skipif(TestUtils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled")
Expand Down
5 changes: 5 additions & 0 deletions tests/test_monero_wallet_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,11 @@ def test_get_reserve_proof_wallet(self, wallet: MoneroWallet) -> None:
def test_get_reserve_proof_account(self, wallet: MoneroWallet) -> None:
return super().test_get_reserve_proof_account(wallet)

@pytest.mark.not_supported
@override
def test_view_only_and_offline_wallets(self, wallet: MoneroWallet) -> None:
return super().test_view_only_and_offline_wallets(wallet)

#endregion

#region Tests
Expand Down
4 changes: 4 additions & 0 deletions tests/test_monero_wallet_rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,4 +362,8 @@ def test_check_tx_key(self, wallet: MoneroWallet) -> None:
def test_check_tx_proof(self, wallet: MoneroWallet) -> None:
return super().test_check_tx_proof(wallet)

@pytest.mark.skip(reason="TODO setup another docker monero-wallet-rpc resource")
def test_view_only_and_offline_wallets(self, wallet: MoneroWallet) -> None:
return super().test_view_only_and_offline_wallets(wallet)

#endregion
4 changes: 3 additions & 1 deletion tests/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from .blockchain_utils import BlockchainUtils
from .integration_test_utils import IntegrationTestUtils
from .wallet_type import WalletType
from .view_only_and_offline_wallet_tester import ViewOnlyAndOfflineWalletTester

__all__ = [
'WalletUtils',
Expand Down Expand Up @@ -51,5 +52,6 @@
'TxSpammer',
'BlockchainUtils',
'IntegrationTestUtils',
'WalletType'
'WalletType',
'ViewOnlyAndOfflineWalletTester'
]
43 changes: 43 additions & 0 deletions tests/utils/tx_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,49 @@ def test_check_reserve(cls, check: MoneroCheckReserve) -> None:
assert check.total_amount is None
assert check.unconfirmed_spent_amount is None

@classmethod
def test_described_tx_set(cls, described_tx_set: MoneroTxSet) -> None:
"""
Test described tx set

:param MoneroTxSet described_tx_set: described tx set to test
"""
assert len(described_tx_set.txs) > 0
assert described_tx_set.signed_tx_hex is None
assert described_tx_set.unsigned_tx_hex is None

# test each transaction
# TODO use common tx wallet test?
assert described_tx_set.multisig_tx_hex is None
for parsed_tx in described_tx_set.txs:
# TODO monero-cpp full wallet is not assigning tx set to parsed txs
#assert parsed_tx.tx_set is not None
#assert parsed_tx.tx_set == described_tx_set, f"{parsed_tx.tx_set.serialize()} != {described_tx_set.serialize()}"
GenUtils.test_unsigned_big_integer(parsed_tx.input_sum, True)
GenUtils.test_unsigned_big_integer(parsed_tx.output_sum, True)
GenUtils.test_unsigned_big_integer(parsed_tx.fee)
GenUtils.test_unsigned_big_integer(parsed_tx.change_amount)
if parsed_tx.change_amount == 0:
assert parsed_tx.change_address is None
else:
assert parsed_tx.change_address is not None
MoneroUtils.validate_address(parsed_tx.change_address, TestUtils.NETWORK_TYPE)
assert parsed_tx.ring_size is not None
assert parsed_tx.ring_size > 1
assert parsed_tx.unlock_time is not None
assert parsed_tx.unlock_time >= 0
assert parsed_tx.num_dummy_outputs is not None
assert parsed_tx.num_dummy_outputs >= 0
assert parsed_tx.extra_hex is not None
assert len(parsed_tx.extra_hex) > 0
assert parsed_tx.payment_id is None or len(parsed_tx.payment_id) > 0
assert parsed_tx.is_outgoing is True
assert parsed_tx.outgoing_transfer is not None
assert len(parsed_tx.outgoing_transfer.destinations) > 0
assert parsed_tx.is_incoming is None
for destination in parsed_tx.outgoing_transfer.destinations:
cls.test_destination(destination)

@classmethod
def test_invalid_address_error(cls, ex: Exception) -> None:
"""
Expand Down
Loading