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
3 changes: 1 addition & 2 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import pytest

from monero import MoneroError

def pytest_runtest_call(item: pytest.Item):
# get not_supported marked
Expand All @@ -17,7 +16,7 @@ def pytest_runtest_call(item: pytest.Item):
try:
# run test
item.runtest()
except MoneroError as e:
except RuntimeError as e:
e_str = str(e).lower()
if "not supported" in e_str or "does not support" in e_str:
# Ok
Expand Down
35 changes: 13 additions & 22 deletions src/cpp/py_monero.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,33 +152,24 @@ PYBIND11_MODULE(monero, m) {
py::implicitly_convertible<py::iterable, std::vector<std::shared_ptr<monero::monero_destination>>>();

// monero_error
py::register_exception<PyMoneroError>(m, "MoneroError");
py::exception<PyMoneroError> pyMoneroError(m, "MoneroError");

py::exec(R"pybind(
class MoneroRpcError(RuntimeError):
def __init__(self, code: int, aMessage: str):
// python subclass
py::exec(R"(
class MoneroRpcError(MoneroError):
def __init__(self, message, code=-1):
super().__init__(message)
self.code = code
self.message = aMessage
super().__init__(aMessage)
def get_code(self) -> int:
return self.code
def get_message(self) -> str:
return self.message
)pybind",
m.attr("__dict__"), m.attr("__dict__"));
)", m.attr("__dict__"));

py::register_exception_translator([](std::exception_ptr p) {
const auto setPyException = [](const char* pyTypeName, const auto& exc) {
const py::object pyClass = py::module_::import("monero").attr(pyTypeName);
const py::object pyInstance = pyClass(exc.code, exc.what());
PyErr_SetObject(pyClass.ptr(), pyInstance.ptr());
};

try {
if (p) std::rethrow_exception(p);
}
catch (const PyMoneroRpcError& exc) {
setPyException("MoneroRpcError", exc);
catch (const PyMoneroRpcError& e) {
py::object cls = py::module_::import("monero").attr("MoneroRpcError");
py::object exc = cls(e.what(), e.code);
PyErr_SetObject(cls.ptr(), exc.ptr());
}
});

Expand Down Expand Up @@ -1892,13 +1883,13 @@ PYBIND11_MODULE(monero, m) {
}, py::arg("tx_hash"), py::arg("tx_key"), py::arg("address"))
.def("get_tx_proof", [](PyMoneroWallet& self, const std::string& tx_hash, const std::string& address, const std::string& message) {
MONERO_CATCH_AND_RETHROW(self.get_tx_proof(tx_hash, address, message));
}, py::arg("tx_hash"), py::arg("address"), py::arg("message"))
}, py::arg("tx_hash"), py::arg("address"), py::arg("message") = "")
.def("check_tx_proof", [](PyMoneroWallet& self, const std::string& tx_hash, const std::string& address, const std::string& message, const std::string& signature) {
MONERO_CATCH_AND_RETHROW(self.check_tx_proof(tx_hash, address, message, signature));
}, py::arg("tx_hash"), py::arg("address"), py::arg("message"), py::arg("signature"))
.def("get_spend_proof", [](PyMoneroWallet& self, const std::string& tx_hash, const std::string& message) {
MONERO_CATCH_AND_RETHROW(self.get_spend_proof(tx_hash, message));
}, py::arg("tx_hash"), py::arg("message"))
}, py::arg("tx_hash"), py::arg("message") = "")
.def("check_spend_proof", [](PyMoneroWallet& self, const std::string& tx_hash, const std::string& message, const std::string& signature) {
MONERO_CATCH_AND_RETHROW(self.check_spend_proof(tx_hash, message, signature));
}, py::arg("tx_hash"), py::arg("message"), py::arg("signature"))
Expand Down
6 changes: 6 additions & 0 deletions src/cpp/wallet/py_monero_wallet_model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1766,6 +1766,12 @@ void PyMoneroCheckReserve::from_property_tree(const boost::property_tree::ptree&
else if (key == std::string("total")) check->m_total_amount = it->second.get_value<uint64_t>();
else if (key == std::string("spent")) check->m_unconfirmed_spent_amount = it->second.get_value<uint64_t>();
}

if (!bool_equals_2(true, check->m_is_good)) {
// normalize invalid check reserve
check->m_total_amount = boost::none;
check->m_unconfirmed_spent_amount = boost::none;
}
}

void PyMoneroCheckTxProof::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr<monero::monero_check_tx>& check) {
Expand Down
132 changes: 92 additions & 40 deletions src/cpp/wallet/py_monero_wallet_rpc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1238,23 +1238,32 @@ monero_message_signature_result PyMoneroWalletRpc::verify_message(const std::str
}

std::string PyMoneroWalletRpc::get_tx_key(const std::string& tx_hash) const {
auto params = std::make_shared<PyMoneroCheckTxKeyParams>(tx_hash);
PyMoneroJsonRequest request("get_tx_key", params);
auto response = m_rpc->send_json_request(request);
if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response");
auto node = response->m_result.get();
for (auto it = node.begin(); it != node.end(); ++it) {
std::string key = it->first;
try {
auto params = std::make_shared<PyMoneroCheckTxKeyParams>(tx_hash);
PyMoneroJsonRequest request("get_tx_key", params);
auto response = m_rpc->send_json_request(request);
if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response");
auto node = response->m_result.get();
for (auto it = node.begin(); it != node.end(); ++it) {
std::string key = it->first;

if (key == std::string("tx_key")) {
return it->second.data();
if (key == std::string("tx_key")) {
return it->second.data();
}
}
}

throw std::runtime_error("Could not get tx key");
throw std::runtime_error("Could not get tx key");
} catch (const PyMoneroRpcError& ex) {
if (ex.code == -8 && ex.what() == std::string("TX ID has invalid format")) {
// normalize error message
throw PyMoneroRpcError(-8, "TX hash has invalid format");
}
throw;
}
}

std::shared_ptr<monero_check_tx> PyMoneroWalletRpc::check_tx_key(const std::string& tx_hash, const std::string& tx_key, const std::string& address) const {
try {
auto params = std::make_shared<PyMoneroCheckTxKeyParams>(tx_hash, tx_key, address);
PyMoneroJsonRequest request("check_tx_key", params);
auto response = m_rpc->send_json_request(request);
Expand All @@ -1263,47 +1272,90 @@ std::shared_ptr<monero_check_tx> PyMoneroWalletRpc::check_tx_key(const std::stri
auto check = std::make_shared<monero::monero_check_tx>();
PyMoneroCheckTxProof::from_property_tree(node, check);
return check;
} catch (const PyMoneroRpcError& ex) {
if (ex.code == -8 && ex.what() == std::string("TX ID has invalid format")) {
// normalize error message
throw PyMoneroRpcError(-8, "TX hash has invalid format");
}
throw;
}
}

std::string PyMoneroWalletRpc::get_tx_proof(const std::string& tx_hash, const std::string& address, const std::string& message) const {
auto params = std::make_shared<PyMoneroReserveProofParams>(tx_hash, message);
params->m_address = address;
PyMoneroJsonRequest request("get_tx_proof", params);
auto response = m_rpc->send_json_request(request);
if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response");
auto node = response->m_result.get();
return PyMoneroReserveProofSignature::from_property_tree(node);
try {
auto params = std::make_shared<PyMoneroReserveProofParams>(tx_hash, message);
params->m_address = address;
PyMoneroJsonRequest request("get_tx_proof", params);
auto response = m_rpc->send_json_request(request);
if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response");
auto node = response->m_result.get();
return PyMoneroReserveProofSignature::from_property_tree(node);
} catch (const PyMoneroRpcError& ex) {
if (ex.code == -8 && ex.what() == std::string("TX ID has invalid format")) {
// normalize error message
throw PyMoneroRpcError(-8, "TX hash has invalid format");
}
throw;
}
}

std::shared_ptr<monero_check_tx> PyMoneroWalletRpc::check_tx_proof(const std::string& tx_hash, const std::string& address, const std::string& message, const std::string& signature) const {
auto params = std::make_shared<PyMoneroReserveProofParams>(tx_hash, address, message, signature);
PyMoneroJsonRequest request("check_tx_proof", params);
auto response = m_rpc->send_json_request(request);
if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response");
auto node = response->m_result.get();
auto check = std::make_shared<monero::monero_check_tx>();
PyMoneroCheckTxProof::from_property_tree(node, check);
return check;
try {
auto params = std::make_shared<PyMoneroReserveProofParams>(tx_hash, address, message, signature);
PyMoneroJsonRequest request("check_tx_proof", params);
auto response = m_rpc->send_json_request(request);
if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response");
auto node = response->m_result.get();
auto check = std::make_shared<monero::monero_check_tx>();
PyMoneroCheckTxProof::from_property_tree(node, check);
return check;
} catch (const PyMoneroRpcError& ex) {
if (ex.code == -1 && ex.what() == std::string("basic_string")) {
throw PyMoneroRpcError(-1, "Must provide signature to check tx proof");
}
if (ex.code == -8 && ex.what() == std::string("TX ID has invalid format")) {
// normalize error message
throw PyMoneroRpcError(-8, "TX hash has invalid format");
}
throw;
}
}

std::string PyMoneroWalletRpc::get_spend_proof(const std::string& tx_hash, const std::string& message) const {
auto params = std::make_shared<PyMoneroReserveProofParams>(tx_hash, message);
PyMoneroJsonRequest request("get_spend_proof", params);
auto response = m_rpc->send_json_request(request);
if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response");
auto node = response->m_result.get();
return PyMoneroReserveProofSignature::from_property_tree(node);
try {
auto params = std::make_shared<PyMoneroReserveProofParams>(tx_hash, message);
PyMoneroJsonRequest request("get_spend_proof", params);
auto response = m_rpc->send_json_request(request);
if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response");
auto node = response->m_result.get();
return PyMoneroReserveProofSignature::from_property_tree(node);
} catch (const PyMoneroRpcError& ex) {
if (ex.code == -8 && ex.what() == std::string("TX ID has invalid format")) {
// normalize error message
throw PyMoneroRpcError(-8, "TX hash has invalid format");
}
throw;
}
}

bool PyMoneroWalletRpc::check_spend_proof(const std::string& tx_hash, const std::string& message, const std::string& signature) const {
auto params = std::make_shared<PyMoneroReserveProofParams>(tx_hash, message, signature);
PyMoneroJsonRequest request("check_spend_proof", params);
auto response = m_rpc->send_json_request(request);
if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response");
auto node = response->m_result.get();
auto proof = std::make_shared<monero::monero_check_reserve>();
PyMoneroCheckReserve::from_property_tree(node, proof);
return proof->m_is_good;
try {
auto params = std::make_shared<PyMoneroReserveProofParams>(tx_hash, message);
params->m_signature = signature;
PyMoneroJsonRequest request("check_spend_proof", params);
auto response = m_rpc->send_json_request(request);
if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response");
auto node = response->m_result.get();
auto proof = std::make_shared<monero::monero_check_reserve>();
PyMoneroCheckReserve::from_property_tree(node, proof);
return proof->m_is_good;
} catch (const PyMoneroRpcError& ex) {
if (ex.code == -8 && ex.what() == std::string("TX ID has invalid format")) {
// normalize error message
throw PyMoneroRpcError(-8, "TX hash has invalid format");
}
throw;
}
}

std::string PyMoneroWalletRpc::get_reserve_proof_wallet(const std::string& message) const {
Expand Down
19 changes: 8 additions & 11 deletions src/python/monero_rpc_error.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,16 @@ class MoneroRpcError(RuntimeError):
"""
Exception when interacting with the Monero daemon or wallet RPC API.
"""
def __init__(self, code: int, aMessage: str) -> None:
...
def get_code(self) -> int:
"""
JSON-RPC error code.

:return int: Error code.
"""
...
def get_message(self) -> str:
code: int
"""JSON-RPC error code"""

def __init__(self, message: str, code: int = -1) -> None:
"""
JSON-RPC error message.
Initialize a new monero rpc error

:return str: Error message.
:param str message: rpc error message
:param int code: rpc error code
"""
...

6 changes: 3 additions & 3 deletions src/python/monero_ssl_options.pyi
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
class MoneroSslOptions:
ssl_private_key_path: str
ssl_private_key_path: str | None
"""Path to private ssl key"""
ssl_certificate_path: str
ssl_certificate_path: str | None
"""Path to private ssl certificate"""
ssl_ca_file: str
ssl_ca_file: str | None
"""Path to ssl CA file"""
ssl_allowed_fingerprints: list[str]
"""Allowed ssl fingerprints"""
Expand Down
4 changes: 2 additions & 2 deletions src/python/monero_wallet.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@ class MoneroWallet:
:return str: the language of the wallet's mnemonic phrase or seed.
"""
...
def get_spend_proof(self, tx_hash: str, message: str) -> str:
def get_spend_proof(self, tx_hash: str, message: str = "") -> str:
"""
Generate a signature to prove a spend. Unlike proving a transaction, it does not require the destination public address.

Expand Down Expand Up @@ -625,7 +625,7 @@ class MoneroWallet:
:returns list[str]: notes for the transactions
"""
...
def get_tx_proof(self, tx_hash: str, address: str, message: str) -> str:
def get_tx_proof(self, tx_hash: str, address: str, message: str = "") -> str:
"""
Get a transaction signature to prove it.

Expand Down
34 changes: 34 additions & 0 deletions tests/test_monero_common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import pytest
import logging

from monero import (
MoneroError, MoneroRpcError
)

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


@pytest.mark.unit
class TestMoneroCommon:
"""Monero common unit tests"""

@pytest.fixture(autouse=True)
def setup_and_teardown(self, request: pytest.FixtureRequest):
logger.info(f"Before {request.node.name}") # type: ignore
yield
logger.info(f"After {request.node.name}") # type: ignore

# test monero error inheritance
def test_monero_error(self) -> None:
monero_err: MoneroError = MoneroError("Test monero error")
monero_rpc_err: MoneroRpcError = MoneroRpcError("Test monero rpc error")

# test monero error
assert isinstance(monero_err, Exception)
assert str(monero_err) == "Test monero error"

# test monero rpc error
assert isinstance(monero_rpc_err, Exception)
assert isinstance(monero_rpc_err, MoneroError)
assert str(monero_rpc_err) == "Test monero rpc error"
assert monero_rpc_err.code == -1
Loading