From a19114b432af2bf63910e0334a3aee56806e5c9c Mon Sep 17 00:00:00 2001 From: everoddandeven Date: Wed, 22 Apr 2026 14:54:40 +0200 Subject: [PATCH] Refactory and code style fixes * Refactory common, daemon and wallet data model * Refactory tests code * Set monero-cpp code style for cpp code * Remove unused code * Check response status on daemon rpc calls * Add copyright to files * Add MTRACE, MWARNING logging --- src/cpp/common/py_monero_common.cpp | 487 ++++--- src/cpp/common/py_monero_common.h | 345 ++--- src/cpp/daemon/py_monero_daemon.h | 211 +-- src/cpp/daemon/py_monero_daemon_model.cpp | 570 +++++--- src/cpp/daemon/py_monero_daemon_model.h | 445 +++--- src/cpp/daemon/py_monero_daemon_rpc.cpp | 514 ++++--- src/cpp/daemon/py_monero_daemon_rpc.h | 144 +- src/cpp/py_monero.cpp | 877 ++++++------ src/cpp/utils/py_monero_utils.cpp | 54 +- src/cpp/utils/py_monero_utils.h | 53 + src/cpp/wallet/py_monero_wallet.cpp | 87 +- src/cpp/wallet/py_monero_wallet.h | 82 +- src/cpp/wallet/py_monero_wallet_model.cpp | 1244 +++++++++-------- src/cpp/wallet/py_monero_wallet_model.h | 566 ++++---- src/cpp/wallet/py_monero_wallet_rpc.cpp | 988 +++++++------ src/cpp/wallet/py_monero_wallet_rpc.h | 153 +- src/python/__init__.pyi | 8 +- .../monero_connection_priority_comparator.pyi | 14 - src/python/monero_daemon.pyi | 15 +- src/python/monero_daemon_info.pyi | 7 +- src/python/monero_daemon_sync_info.pyi | 7 +- src/python/monero_hard_fork_info.pyi | 9 +- src/python/monero_rpc_connection.pyi | 16 +- src/python/monero_rpc_payment_info.pyi | 11 + src/python/monero_submit_tx_result.pyi | 8 +- src/python/monero_wallet_rpc.pyi | 6 +- src/python/serializable_struct.pyi | 9 +- ...monero_ssl_options.pyi => ssl_options.pyi} | 5 +- tests/test_monero_common.py | 2 +- tests/test_monero_daemon_rpc.py | 37 +- tests/test_monero_rpc_connection.py | 27 +- tests/test_monero_wallet_common.py | 361 +++-- tests/test_monero_wallet_full.py | 13 +- tests/test_monero_wallet_rpc.py | 10 +- tests/utils/__init__.py | 22 +- tests/utils/block_utils.py | 21 +- tests/utils/context/__init__.py | 9 + .../{ => context}/binary_block_context.py | 0 tests/utils/{ => context}/test_context.py | 1 + tests/utils/{ => context}/tx_context.py | 0 tests/utils/daemon_utils.py | 76 +- tests/utils/docker_wallet_rpc_manager.py | 2 +- tests/utils/from_multiple_tx_sender.py | 13 +- tests/utils/integration_test_utils.py | 4 +- tests/utils/mining_utils.py | 4 +- tests/utils/output_utils.py | 146 ++ tests/utils/rpc_connection_utils.py | 102 +- tests/utils/send_and_update_txs_tester.py | 74 +- tests/utils/single_tx_sender.py | 22 +- tests/utils/string_utils.py | 9 - tests/utils/sync_progress_tester.py | 1 - tests/utils/test_utils.py | 11 +- tests/utils/to_multiple_tx_sender.py | 14 +- tests/utils/transfer_utils.py | 102 ++ tests/utils/tx_spammer.py | 6 +- tests/utils/tx_utils.py | 1074 +------------- tests/utils/tx_wallet_utils.py | 625 +++++++++ .../view_only_and_offline_wallet_tester.py | 8 +- tests/utils/wallet_equality_utils.py | 29 +- tests/utils/wallet_error_utils.py | 82 ++ tests/utils/wallet_send_utils.py | 79 ++ tests/utils/wallet_sweeper.py | 14 +- tests/utils/wallet_test_utils.py | 194 +++ tests/utils/wallet_transfers_utils.py | 50 + tests/utils/wallet_txs_utils.py | 133 ++ tests/utils/wallet_utils.py | 267 +--- 66 files changed, 5899 insertions(+), 4680 deletions(-) delete mode 100644 src/python/monero_connection_priority_comparator.pyi create mode 100644 src/python/monero_rpc_payment_info.pyi rename src/python/{monero_ssl_options.pyi => ssl_options.pyi} (84%) create mode 100644 tests/utils/context/__init__.py rename tests/utils/{ => context}/binary_block_context.py (100%) rename tests/utils/{ => context}/test_context.py (99%) rename tests/utils/{ => context}/tx_context.py (100%) create mode 100644 tests/utils/output_utils.py create mode 100644 tests/utils/transfer_utils.py create mode 100644 tests/utils/tx_wallet_utils.py create mode 100644 tests/utils/wallet_error_utils.py create mode 100644 tests/utils/wallet_send_utils.py create mode 100644 tests/utils/wallet_test_utils.py create mode 100644 tests/utils/wallet_transfers_utils.py create mode 100644 tests/utils/wallet_txs_utils.py diff --git a/src/cpp/common/py_monero_common.cpp b/src/cpp/common/py_monero_common.cpp index 2118455..7ef432f 100644 --- a/src/cpp/common/py_monero_common.cpp +++ b/src/cpp/common/py_monero_common.cpp @@ -1,3 +1,56 @@ +/** + * Copyright (c) everoddandeven + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2025-2026 woodser + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ #include #include #include @@ -6,26 +59,20 @@ #include "py_monero_common.h" #include "utils/monero_utils.h" -boost::property_tree::ptree json_to_property_node(const std::string& json) { - // deserialize json to property node - std::istringstream iss = json.empty() ? std::istringstream() : std::istringstream(json); - boost::property_tree::ptree node; - boost::property_tree::read_json(iss, node); - return node; -} +// --------------------------- THREAD POLLER --------------------------- -PyThreadPoller::~PyThreadPoller() { +thread_poller::~thread_poller() { set_is_polling(false); } -void PyThreadPoller::init_common(const std::string& name) { +void thread_poller::init_common(const std::string& name) { m_name = name; m_is_polling = false; m_poll_period_ms = 20000; m_poll_loop_running = false; } -void PyThreadPoller::set_is_polling(bool is_polling) { +void thread_poller::set_is_polling(bool is_polling) { if (is_polling == m_is_polling) return; m_is_polling = is_polling; @@ -41,11 +88,7 @@ void PyThreadPoller::set_is_polling(bool is_polling) { } } -void PyThreadPoller::set_period_in_ms(uint64_t period_ms) { - m_poll_period_ms = period_ms; -} - -void PyThreadPoller::run_poll_loop() { +void thread_poller::run_poll_loop() { if (m_poll_loop_running.exchange(true)) return; // only run one loop at a time // start pool loop thread @@ -55,8 +98,8 @@ void PyThreadPoller::run_poll_loop() { // poll while enabled while (m_is_polling) { try { poll(); } - catch (const std::exception& e) { std::cout << m_name << " failed to background poll: " << e.what() << std::endl; } - catch (...) { std::cout << m_name << " failed to background poll" << std::endl; } + catch (const std::exception& e) { MERROR(m_name << " failed to background poll: " << e.what()); } + catch (...) { MERROR(m_name << " failed to background poll"); } // only wait if polling still enabled if (m_is_polling) { @@ -70,6 +113,8 @@ void PyThreadPoller::run_poll_loop() { }); } +// --------------------------- GEN UTILS --------------------------- + py::object PyGenUtils::convert_value(const std::string& val) { if (val == "true") return py::bool_(true); if (val == "false") return py::bool_(false); @@ -122,106 +167,111 @@ py::object PyGenUtils::ptree_to_pyobject(const boost::property_tree::ptree& tree } } -boost::property_tree::ptree PyGenUtils::pyobject_to_ptree(const py::object& obj) { - boost::property_tree::ptree tree; +boost::property_tree::ptree PyGenUtils::parse_json_string(const std::string &json) { + boost::property_tree::ptree pt; + std::istringstream iss(json); + boost::property_tree::read_json(iss, pt); + return pt; +} - if (py::isinstance(obj)) { - py::dict d = obj.cast(); - for (auto item : d) { - std::string key = py::str(item.first); - py::object val = py::reinterpret_borrow(item.second); +// --------------------------- SSL OPTIONS --------------------------- - if (key == "__value__") { - tree.put_value(py::str(val)); - continue; - } +rapidjson::Value ssl_options::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); - boost::property_tree::ptree child = pyobject_to_ptree(val); - tree.add_child(key, child); - } - } - else if (py::isinstance(obj) || py::isinstance(obj)) { - py::sequence seq = obj.cast(); - for (py::handle item : seq) { - py::object val = py::reinterpret_borrow(item); - tree.push_back(std::make_pair("", pyobject_to_ptree(val))); - } - } - else if (py::isinstance(obj)) { - tree.put_value(obj.cast() ? "true" : "false"); - } - else if (py::isinstance(obj)) { - tree.put_value(std::to_string(obj.cast())); - } - else if (py::isinstance(obj)) { - tree.put_value(std::to_string(obj.cast())); - } - else { - tree.put_value(obj.cast()); - } + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_ssl_private_key_path != boost::none) monero_utils::add_json_member("sslPrivateKeyPath", m_ssl_private_key_path.get(), allocator, root, value_str); + if (m_ssl_certificate_path != boost::none) monero_utils::add_json_member("sslCertificatePath", m_ssl_certificate_path.get(), allocator, root, value_str); + if (m_ssl_ca_file != boost::none) monero_utils::add_json_member("sslCaFile", m_ssl_ca_file.get(), allocator, root, value_str); + if (m_ssl_private_key_path != boost::none) monero_utils::add_json_member("sslPrivateKeyPath", m_ssl_private_key_path.get(), allocator, root, value_str); + if (!m_ssl_allowed_fingerprints.empty()) root.AddMember("sslAllowedFingerprints", monero_utils::to_rapidjson_val(allocator, m_ssl_allowed_fingerprints), allocator); + + // set bool values + if (m_ssl_allow_any_cert != boost::none) monero_utils::add_json_member("sslAllowAnyCert", m_ssl_allow_any_cert.get(), allocator, root); - return tree; + return root; } -boost::property_tree::ptree PyGenUtils::parse_json_string(const std::string &json) { - boost::property_tree::ptree pt; - std::istringstream iss(json); - boost::property_tree::read_json(iss, pt); - return pt; +// --------------------------- MONERO REQUEST PARAMS --------------------------- + +rapidjson::Value monero_request_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + rapidjson::Value root(rapidjson::kObjectType); + + if (m_py_params != boost::none) { + // convert python dict params to rapidjson::Value + py::module json = py::module::import("json"); + std::string json_string = json.attr("dumps")(m_py_params.get()).cast(); + + rapidjson::Document doc; + doc.Parse(json_string.c_str()); + root.Swap(doc); + } + + return root; } -PyMoneroPathRequest::PyMoneroPathRequest(const std::string& method, const boost::optional& params) { +// --------------------------- MONERO PATH REQUEST --------------------------- + +monero_path_request::monero_path_request(const std::string& method, const boost::optional& params): m_params(std::make_shared(params)) { m_method = method; - if (params != boost::none) m_params = std::make_shared(params); - else m_params = std::make_shared(); } -PyMoneroPathRequest::PyMoneroPathRequest(const std::string& method, const std::shared_ptr& params): +monero_path_request::monero_path_request(const std::string& method, const std::shared_ptr& params): m_params(params) { m_method = method; - if (params == nullptr) m_params = std::make_shared(); + if (params == nullptr) m_params = std::make_shared(); } -PyMoneroBinaryRequest::PyMoneroBinaryRequest(const std::string& method, const boost::optional& params) { - m_method = method; - if (params != boost::none) m_params = std::make_shared(params); - m_params = std::make_shared(); +rapidjson::Value monero_path_request::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + if (m_params != boost::none) return m_params.get()->to_rapidjson_val(allocator); + throw std::runtime_error("No params provided"); } -PyMoneroBinaryRequest::PyMoneroBinaryRequest(const std::string& method, const std::shared_ptr& params) { +// --------------------------- MONERO BINARY REQUEST --------------------------- + +monero_binary_request::monero_binary_request(const std::string& method, const boost::optional& params) { m_method = method; - m_params = params; + m_params = std::make_shared(params); } -PyMoneroJsonRequestParams::PyMoneroJsonRequestParams(const boost::optional& py_params) { - m_py_params = py_params; +std::string monero_binary_request::to_binary_val() const { + auto json_val = serialize(); + std::string binary_val; + monero_utils::json_to_binary(json_val, binary_val); + return binary_val; } -PyMoneroJsonRequest::PyMoneroJsonRequest(): - m_version("2.0"), - m_id("0") { - m_params = std::make_shared(); +// --------------------------- MONERO JSON REQUEST --------------------------- + +rapidjson::Value monero_json_request::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + + if (m_version != boost::none) monero_utils::add_json_member("version", m_version.get(), allocator, root, value_str); + if (m_id != boost::none) monero_utils::add_json_member("id", m_id.get(), allocator, root, value_str); + if (m_method != boost::none) monero_utils::add_json_member("method", m_method.get(), allocator, root, value_str); + if (m_params != boost::none) root.AddMember("params", m_params.get()->to_rapidjson_val(allocator), allocator); + + return root; } -PyMoneroJsonRequest::PyMoneroJsonRequest(const PyMoneroJsonRequest& request): - m_version(request.m_version), - m_id(request.m_id), - m_params(request.m_params) -{ - m_method = request.m_method; +monero_json_request_params::monero_json_request_params(const boost::optional& py_params) { + m_py_params = py_params; } -PyMoneroJsonRequest::PyMoneroJsonRequest(const std::string& method, const boost::optional& params): +monero_json_request::monero_json_request(const std::string& method, const boost::optional& params): m_version("2.0"), - m_id("0") { + m_id("0"), + m_params(std::make_shared(params)) { m_method = method; - if (params != boost::none) { - m_params = std::make_shared(params); - } - else m_params = std::make_shared(); } -PyMoneroJsonRequest::PyMoneroJsonRequest(const std::string& method, const std::shared_ptr& params): +monero_json_request::monero_json_request(const std::string& method, const std::shared_ptr& params): m_version("2.0"), m_id("0"), m_params(params) { @@ -229,58 +279,54 @@ PyMoneroJsonRequest::PyMoneroJsonRequest(const std::string& method, const std::s if (params == nullptr) m_params = boost::none; } -PyMoneroGetBlocksByHeightRequest::PyMoneroGetBlocksByHeightRequest(uint64_t num_blocks) { +// --------------------------- MONERO GET BLOCKS BY HEIGHT REQUEST --------------------------- + +monero_get_blocks_by_height_request::monero_get_blocks_by_height_request(uint64_t num_blocks) { m_method = "get_blocks_by_height.bin"; m_heights.reserve(num_blocks); for (uint64_t i = 0; i < num_blocks; i++) m_heights.push_back(i); } -rapidjson::Value PyMoneroGetBlocksByHeightRequest::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +rapidjson::Value monero_get_blocks_by_height_request::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); if (!m_heights.empty()) root.AddMember("heights", monero_utils::to_rapidjson_val(allocator, m_heights), allocator); return root; } -std::string PyMoneroBinaryRequest::to_binary_val() const { - auto json_val = serialize(); - std::string binary_val; - monero_utils::json_to_binary(json_val, binary_val); - return binary_val; -} - -std::string PyMoneroRequestParams::serialize() const { - if (m_py_params == boost::none) return PySerializableStruct::serialize(); - auto node = PyGenUtils::pyobject_to_ptree(m_py_params.get()); - return monero_utils::serialize(node); -} +// --------------------------- MONERO JSON RESPONSE --------------------------- -boost::optional PyMoneroJsonResponse::get_result() const { +boost::optional monero_json_response::get_result() const { boost::optional res; if (m_result != boost::none) res = PyGenUtils::ptree_to_pyobject(m_result.get()); return res; } -std::shared_ptr PyMoneroJsonResponse::deserialize(const std::string& response_json) { +void monero_json_response::raise_rpc_error(const boost::property_tree::ptree& error_node) { + std::string err_message = "Unknown error"; + int err_code = -1; + + for (auto it = error_node.begin(); it != error_node.end(); ++it) { + std::string key_err = it->first; + if (key_err == std::string("message")) { + err_message = it->second.data(); + } else if (key_err == std::string("code")) { + err_code = it->second.get_value(); + } + } + + throw monero_rpc_error(err_code, err_message); +} + +std::shared_ptr monero_json_response::deserialize(const std::string& response_json) { // parse json to property node - boost::property_tree::ptree node = json_to_property_node(response_json); - auto response = std::make_shared(); + boost::property_tree::ptree node; + monero_utils::deserialize(response_json, node); + auto response = std::make_shared(); for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; if (key == std::string("error")) { - std::string err_message = "Unknown error"; - int err_code = -1; - for (auto it_err = it->second.begin(); it_err != it->second.end(); ++it_err) { - std::string key_err = it_err->first; - if (key_err == std::string("message")) { - err_message = it_err->second.data(); - } - else if (key_err == std::string("code")) { - err_code = it_err->second.get_value(); - } - } - - throw PyMoneroRpcError(err_code, err_message); + raise_rpc_error(it->second); } else if (key == std::string("jsonrpc")) { response->m_jsonrpc = it->second.data(); @@ -291,55 +337,59 @@ std::shared_ptr PyMoneroJsonResponse::deserialize(const st else if (key == std::string("result")) { response->m_result = it->second; } - else std::cout << std::string("WARNING MoneroJsonResponse::deserialize() unrecognized key: ") << key << std::endl; + else MWARNING("monero_json_response::deserialize() unrecognized key: "); } return response; } -boost::optional PyMoneroPathResponse::get_response() const { +// --------------------------- MONERO PATH RESPONSE --------------------------- + +boost::optional monero_path_response::get_response() const { boost::optional res; if (m_response != boost::none) res = PyGenUtils::ptree_to_pyobject(m_response.get()); return res; } -std::shared_ptr PyMoneroPathResponse::deserialize(const std::string& response_json) { +std::shared_ptr monero_path_response::deserialize(const std::string& response_json) { // parse json to property node - auto response = std::make_shared(); - response->m_response = json_to_property_node(response_json); + auto response = std::make_shared(); + boost::property_tree::ptree node; + monero_utils::deserialize(response_json, node); + response->m_response = node; return response; } -std::shared_ptr PyMoneroBinaryResponse::deserialize(const std::string& response_binary) { - auto response = std::make_shared(); - response->m_binary = response_binary; - return response; -} +// --------------------------- MONERO RPC CONNECTION --------------------------- -boost::optional PyMoneroBinaryResponse::get_response() const { - boost::optional res; - if (m_response != boost::none) res = PyGenUtils::ptree_to_pyobject(m_response.get()); - return res; -} +bool PyMoneroRpcConnection::before(const std::shared_ptr& c1, const std::shared_ptr& c2, const std::shared_ptr& current_connection) { + // current connection is first + if (c1 == current_connection) return true; + if (c2 == current_connection) return false; -rapidjson::Value PyMoneroPathRequest::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { - if (m_params != boost::none) return m_params.get()->to_rapidjson_val(allocator); - throw std::runtime_error("No params provided"); + // order by availability then priority then by name + if (c1->m_is_online == c2->m_is_online) { + if (c1->m_priority == c2->m_priority) { + // order by priority in descending order + return c1->m_uri.value_or("") < c2->m_uri.value_or(""); + } + // order by priority in descending order + return !compare(c1->m_priority, c2->m_priority); + } else { + if (c1->m_is_online != boost::none && c1->m_is_online.get()) return true; + else if (c2->m_is_online != boost::none && c2->m_is_online.get()) return false; + else if (c1->m_is_online == boost::none) return true; + // c1 is offline + return false; + } } -rapidjson::Value PyMoneroJsonRequest::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { - // create root - rapidjson::Value root(rapidjson::kObjectType); - - // set string values - rapidjson::Value value_str(rapidjson::kStringType); - - if (m_version != boost::none) monero_utils::add_json_member("version", m_version.get(), allocator, root, value_str); - if (m_id != boost::none) monero_utils::add_json_member("id", m_id.get(), allocator, root, value_str); - if (m_method != boost::none) monero_utils::add_json_member("method", m_method.get(), allocator, root, value_str); - if (m_params != boost::none) root.AddMember("params", m_params.get()->to_rapidjson_val(allocator), allocator); - - return root; +bool PyMoneroRpcConnection::compare(int p1, int p2) { + if (p1 == p2) return false; + // 0 alway first + if (p1 == 0) return true; + if (p2 == 0) return false; + return p1 > p2; } PyMoneroRpcConnection::PyMoneroRpcConnection(const std::string& uri, const std::string& username, const std::string& password, const std::string& proxy_uri, const std::string& zmq_uri, int priority, uint64_t timeout) { @@ -390,36 +440,6 @@ rapidjson::Value PyMoneroRpcConnection::to_rapidjson_val(rapidjson::Document::Al return root; } -bool PyMoneroConnectionPriorityComparator::compare(int p1, int p2) { - if (p1 == p2) return false; - // 0 alway first - if (p1 == 0) return true; - if (p2 == 0) return false; - return p1 > p2; -} - -bool PyMoneroRpcConnection::before(const std::shared_ptr& c1, const std::shared_ptr& c2, const std::shared_ptr& current_connection) { - // current connection is first - if (c1 == current_connection) return true; - if (c2 == current_connection) return false; - - // order by availability then priority then by name - if (c1->m_is_online == c2->m_is_online) { - if (c1->m_priority == c2->m_priority) { - // order by priority in descending order - return c1->m_uri.value_or("") < c2->m_uri.value_or(""); - } - // order by priority in descending order - return !PyMoneroConnectionPriorityComparator::compare(c1->m_priority, c2->m_priority); - } else { - if (c1->m_is_online != boost::none && c1->m_is_online.get()) return true; - else if (c2->m_is_online != boost::none && c2->m_is_online.get()) return false; - else if (c1->m_is_online == boost::none) return true; - // c1 is offline - return false; - } -} - bool PyMoneroRpcConnection::is_onion() const { // check onion uri return m_uri != boost::none && m_uri->size() >= 6 && m_uri->compare(m_uri->size() - 6, 6, ".onion") == 0; @@ -447,11 +467,11 @@ void PyMoneroRpcConnection::set_credentials(const std::string& username, const s // check username and password consistency if (!username_empty || !password_empty) { if (password_empty) { - throw PyMoneroError("password cannot be empty because username is not empty"); + throw monero_error("password cannot be empty because username is not empty"); } if (username_empty) { - throw PyMoneroError("username cannot be empty because password is not empty"); + throw monero_error("username cannot be empty because password is not empty"); } } @@ -537,12 +557,12 @@ bool PyMoneroRpcConnection::check_connection(const boost::optional& timeout m_http_client->connect(std::chrono::milliseconds(timeout_ms == boost::none ? m_timeout : *timeout_ms)); // assume daemon connection - PyMoneroGetBlocksByHeightRequest request(100); + monero_get_blocks_by_height_request request(100); send_binary_request(request); m_is_online = true; m_is_authenticated = true; } - catch (const PyMoneroRpcError& ex) { + catch (const monero_rpc_error& ex) { m_is_online = false; m_is_authenticated = boost::none; m_response_time = boost::none; @@ -579,3 +599,104 @@ bool PyMoneroRpcConnection::check_connection(const boost::optional& timeout return is_online_before != m_is_online || is_authenticated_before != m_is_authenticated; } + +const boost::property_tree::ptree PyMoneroRpcConnection::send_json_request(const std::string& path, const std::shared_ptr& params) { + monero_json_request request(path, params); + // send JSON-RPC request + auto response = send_json_request(request); + // assert JSON-RPC response is defined + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + return response->m_result.get(); +} + +const std::shared_ptr PyMoneroRpcConnection::send_json_request(const monero_json_request &request, std::chrono::milliseconds timeout) { + monero_json_response response; + // invoke JSON-RPC method + int result = invoke_post("/json_rpc", request, response, timeout); + // check status code + if (result != 200) throw monero_rpc_error(result, "HTTP error: code " + std::to_string(result)); + // return JSON-RPC response + return std::make_shared(response); +} + +const boost::property_tree::ptree PyMoneroRpcConnection::send_path_request(const std::string& path, const std::shared_ptr& params) { + monero_path_request request(path, params); + // send RPC request + auto response = send_path_request(request); + // assert RPC response is defined + if (response->m_response == boost::none) throw std::runtime_error("Invalid Monero path response"); + return response->m_response.get(); +} + +const std::shared_ptr PyMoneroRpcConnection::send_path_request(const monero_path_request &request, std::chrono::milliseconds timeout) { + // validate parameters + if (request.m_method == boost::none || request.m_method->empty()) throw std::runtime_error("No RPC method set in path request"); + monero_path_response response; + + // invoke RPC method + int result = invoke_post(std::string("/") + request.m_method.get(), request, response, timeout); + + // check status code + if (result != 200) throw monero_rpc_error(result, "HTTP error: code " + std::to_string(result)); + + // return RPC response + return std::make_shared(response); +} + +const std::shared_ptr PyMoneroRpcConnection::send_binary_request(const monero_binary_request &request, std::chrono::milliseconds timeout) { + // validate parameters + if (request.m_method == boost::none || request.m_method->empty()) throw std::runtime_error("No RPC method set in binary request"); + + // invoke Binary RPC method + std::string uri = std::string("/") + request.m_method.get(); + std::string body = request.to_binary_val(); + const epee::net_utils::http::http_response_info* response = invoke_post(uri, body, timeout); + + // check status code + int result = response->m_response_code; + if (result != 200) throw monero_rpc_error(result, "HTTP error: code " + std::to_string(result)); + + // return binary response + return std::make_shared(response->m_body); +} + +boost::optional PyMoneroRpcConnection::send_json_request(const std::string& method, const boost::optional& parameters) { + // send JSON-RPC request with py::object parameters + monero_json_request request(method, parameters); + auto response = send_json_request(request); + return response->get_result(); +} + +boost::optional PyMoneroRpcConnection::send_path_request(const std::string& method, const boost::optional& parameters) { + // send RPC request with py::object parameters + monero_path_request request(method, parameters); + auto response = send_path_request(request); + return response->get_response(); +} + +boost::optional PyMoneroRpcConnection::send_binary_request(const std::string& method, const boost::optional& parameters) { + // send Binary RPC request with py::object parameters + monero_binary_request request(method, parameters); + auto response = send_binary_request(request); + if (response->m_binary == boost::none || response->m_binary->empty()) { + // return empty response + return boost::none; + } + + // convert binary string to py::bytes + return py::bytes(response->m_binary.get()); +} + +const epee::net_utils::http::http_response_info* PyMoneroRpcConnection::invoke_post(const boost::string_ref uri, const std::string& body, std::chrono::milliseconds timeout) const { + // assert internal http client is initialized + if (!m_http_client) throw std::runtime_error("http client not initialized."); + + boost::lock_guard lock(m_mutex); + const epee::net_utils::http::http_response_info* pri = NULL; + + // invoke http json + if (!m_http_client->invoke_post(uri, body, timeout, std::addressof(pri))) throw std::runtime_error("Network error"); + if (!pri) throw std::runtime_error("Could not get response info"); + // return response info + return pri; +} diff --git a/src/cpp/common/py_monero_common.h b/src/cpp/common/py_monero_common.h index 05e6a34..e2511fe 100644 --- a/src/cpp/common/py_monero_common.h +++ b/src/cpp/common/py_monero_common.h @@ -1,3 +1,56 @@ +/** + * Copyright (c) everoddandeven + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2025-2026 woodser + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ #pragma once #include @@ -5,11 +58,12 @@ #include #include "net/http.h" -#include "utils/gen_utils.h" #include "daemon/monero_daemon_model.h" namespace py = pybind11; +// ------------------------------ Utilities --------------------------------- + namespace pybind11 { namespace detail { template @@ -43,14 +97,23 @@ namespace pybind11 { namespace detail { }} -class PyThreadPoller { +/** + * Collection of generic utilities. + */ +class PyGenUtils { public: + static py::object convert_value(const std::string& val); + static py::object ptree_to_pyobject(const boost::property_tree::ptree& tree); + static boost::property_tree::ptree parse_json_string(const std::string &json); +}; - ~PyThreadPoller(); +class thread_poller { +public: + ~thread_poller(); bool is_polling() const { return m_is_polling; } void set_is_polling(bool is_polling); - void set_period_in_ms(uint64_t period_ms); + void set_period_in_ms(uint64_t period_ms) { m_poll_period_ms = period_ms; } virtual void poll() = 0; protected: @@ -67,40 +130,31 @@ class PyThreadPoller { void run_poll_loop(); }; -class PySerializableStruct : public monero::serializable_struct { -public: - using serializable_struct::serializable_struct; - - virtual std::string serialize() const { return serializable_struct::serialize(); } - rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override { throw std::runtime_error("PySerializableStruct::to_rapid_json_value(): not implemented"); }; -}; +// ------------------------------ Errors --------------------------------- -class PyMoneroError : public std::exception { +class monero_error : public std::exception { public: std::string message; - PyMoneroError() {} - PyMoneroError(const std::string& msg) : message(msg) {} + monero_error() {} + monero_error(const std::string& msg) : message(msg) {} const char* what() const noexcept override { return message.c_str(); } }; -class PyMoneroRpcError : public PyMoneroError { +class monero_rpc_error : public monero_error { public: int code; - PyMoneroRpcError(int error_code, const std::string& msg) : code(error_code) { - message = msg; - } - - PyMoneroRpcError(const std::string& msg) : code(-1) { - message = msg; - } + monero_rpc_error(int error_code, const std::string& msg) : code(error_code) { message = msg; } + monero_rpc_error(const std::string& msg) : code(-1) { message = msg; } }; -class PyMoneroSslOptions { +// ------------------------------ Extended Data Model --------------------------------- + +struct ssl_options : public monero::serializable_struct { public: boost::optional m_ssl_private_key_path; boost::optional m_ssl_certificate_path; @@ -108,10 +162,10 @@ class PyMoneroSslOptions { std::vector m_ssl_allowed_fingerprints; boost::optional m_ssl_allow_any_cert; - PyMoneroSslOptions() {} + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -enum PyMoneroConnectionType : uint8_t { +enum monero_connection_type : uint8_t { INVALID = 0, IPV4, IPV6, @@ -119,147 +173,112 @@ enum PyMoneroConnectionType : uint8_t { I2P }; -class PyMoneroConnectionPriorityComparator { -public: +// ------------------------------ RPC Request --------------------------------- - static bool compare(int p1, int p2); -}; - -class PyGenUtils { -public: - PyGenUtils() {} - - static py::object convert_value(const std::string& val); - static py::object ptree_to_pyobject(const boost::property_tree::ptree& tree); - static boost::property_tree::ptree pyobject_to_ptree(const py::object& obj); - static boost::property_tree::ptree parse_json_string(const std::string &json); -}; - -class PyMoneroRequest : public PySerializableStruct { +struct monero_request : public monero::serializable_struct { public: boost::optional m_method; - PyMoneroRequest() { } - - rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override { throw std::runtime_error("PyMoneroRequest::to_rapid_json_value(): not implemented"); }; + monero_request() { } }; -class PyMoneroRequestParams : public PySerializableStruct { +struct monero_request_params : public monero::serializable_struct { public: boost::optional m_py_params; - PyMoneroRequestParams() { } - PyMoneroRequestParams(const boost::optional& py_params): m_py_params(py_params) {} - - std::string serialize() const override; - rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override { throw std::runtime_error("PyMoneroRequestParams::to_rapid_json_value(): not implemented"); }; -}; - -class PyMoneroRequestEmptyParams : public PyMoneroRequestParams { -public: - PyMoneroRequestEmptyParams() {} + monero_request_params() { } + monero_request_params(const boost::optional& py_params): m_py_params(py_params) { } - rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override { rapidjson::Value root(rapidjson::kObjectType); return root; }; + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroPathRequest : public PyMoneroRequest { +struct monero_path_request : public monero_request { public: - boost::optional> m_params; + boost::optional> m_params; - PyMoneroPathRequest() { } - PyMoneroPathRequest(const std::string& method, const boost::optional& params = boost::none); - PyMoneroPathRequest(const std::string& method, const std::shared_ptr& params); + monero_path_request() { } + monero_path_request(const std::string& method, const boost::optional& params = boost::none); + monero_path_request(const std::string& method, const std::shared_ptr& params); rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroBinaryRequest : public PyMoneroPathRequest { +struct monero_binary_request : public monero_path_request { public: - PyMoneroBinaryRequest() { } - PyMoneroBinaryRequest(const std::string& method, const boost::optional& params = boost::none); - PyMoneroBinaryRequest(const std::string& method, const std::shared_ptr& params); + monero_binary_request() { } + monero_binary_request(const std::string& method, const boost::optional& params = boost::none); std::string to_binary_val() const; }; -class PyMoneroJsonRequestParams : public PyMoneroRequestParams { +struct monero_json_request_params : public monero_request_params { public: - PyMoneroJsonRequestParams() { } - PyMoneroJsonRequestParams(const boost::optional& py_params); - - rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override { throw std::runtime_error("PyMoneroJsonRequestParams::to_rapid_json_value(): not implemented"); }; + monero_json_request_params() { } + monero_json_request_params(const boost::optional& py_params); }; -class PyMoneroJsonRequestEmptyParams : public PyMoneroJsonRequestParams { +struct monero_json_request : public monero_request { public: - PyMoneroJsonRequestEmptyParams() { } + boost::optional m_version; + boost::optional m_id; + boost::optional> m_params; + + monero_json_request(const std::string& method, const boost::optional& params = boost::none); + monero_json_request(const std::string& method, const std::shared_ptr& params); - rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override { rapidjson::Value root(rapidjson::kObjectType); return root; }; + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroJsonRequest : public PyMoneroRequest { +struct monero_get_blocks_by_height_request : public monero_binary_request { public: - boost::optional m_version; - boost::optional m_id; - boost::optional> m_params; + std::vector m_heights; - PyMoneroJsonRequest(); - PyMoneroJsonRequest(const PyMoneroJsonRequest& request); - PyMoneroJsonRequest(const std::string& method, const boost::optional& params = boost::none); - PyMoneroJsonRequest(const std::string& method, const std::shared_ptr& params); + monero_get_blocks_by_height_request(uint64_t num_blocks); + monero_get_blocks_by_height_request(const std::vector& heights): m_heights(heights) { m_method = "get_blocks_by_height.bin"; } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroJsonResponse { +// ------------------------------ RPC Response --------------------------------- + +struct monero_json_response { public: boost::optional m_jsonrpc; boost::optional m_id; boost::optional m_result; - static std::shared_ptr deserialize(const std::string& response_json); + static void raise_rpc_error(const boost::property_tree::ptree& error_node); + static std::shared_ptr deserialize(const std::string& response_json); - PyMoneroJsonResponse(const PyMoneroJsonResponse& response): m_jsonrpc("2.0"), m_id("0"), m_result(response.m_result) {} - PyMoneroJsonResponse(const boost::optional &result = boost::none): m_jsonrpc("2.0"), m_id("0"), m_result(result) {} + monero_json_response(const monero_json_response& response): m_jsonrpc("2.0"), m_id("0"), m_result(response.m_result) {} + monero_json_response(const boost::optional &result = boost::none): m_jsonrpc("2.0"), m_id("0"), m_result(result) {} boost::optional get_result() const; }; -class PyMoneroPathResponse { +struct monero_path_response { public: boost::optional m_response; - PyMoneroPathResponse() { } - PyMoneroPathResponse(const PyMoneroPathResponse& response): m_response(response.m_response) {} - PyMoneroPathResponse(const boost::optional &response): m_response(response) {} + monero_path_response() { } + monero_path_response(const monero_path_response& response): m_response(response.m_response) {} + monero_path_response(const boost::optional &response): m_response(response) {} boost::optional get_response() const; - static std::shared_ptr deserialize(const std::string& response_json); + static std::shared_ptr deserialize(const std::string& response_json); }; -class PyMoneroBinaryResponse { +struct monero_binary_response { public: boost::optional m_binary; boost::optional m_response; - PyMoneroBinaryResponse() {} - PyMoneroBinaryResponse(const std::string &binary): m_binary(binary) {} - PyMoneroBinaryResponse(const PyMoneroBinaryResponse& response): m_binary(response.m_binary), m_response(response.m_response) {} - - static std::shared_ptr deserialize(const std::string& response_binary); - boost::optional get_response() const; + monero_binary_response() { } + monero_binary_response(const std::string &binary): m_binary(binary) { } + monero_binary_response(const monero_binary_response& response): m_binary(response.m_binary), m_response(response.m_response) { } }; -// TODO refactory -class PyMoneroGetBlocksByHeightRequest : public PyMoneroBinaryRequest { -public: - std::vector m_heights; - - PyMoneroGetBlocksByHeightRequest(uint64_t num_blocks); - PyMoneroGetBlocksByHeightRequest(const std::vector& heights): m_heights(heights) { m_method = "get_blocks_by_height.bin"; } - - rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; -}; +// ------------------------------ Custom RPC Connection --------------------------------- /** * Maintains a connection and sends requests to a Monero RPC API. @@ -282,6 +301,14 @@ class PyMoneroRpcConnection : public monero::monero_rpc_connection { */ static bool before(const std::shared_ptr& c1, const std::shared_ptr& c2, const std::shared_ptr& current_connection); + /** + * Checks connection priority order. + * + * @param c1 first priority to compare. + * @param c2 second priority to compare. + */ + static bool compare(int p1, int p2); + /** * Initialize a new RPC connection. * @@ -381,13 +408,7 @@ class PyMoneroRpcConnection : public monero::monero_rpc_connection { * @param params are the request's input parameters * @return the RPC API response as a map */ - inline const boost::property_tree::ptree send_json_request(const std::string& path, const std::shared_ptr& params = nullptr) { - PyMoneroJsonRequest request(path, params); - auto response = send_json_request(request); - - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - return response->m_result.get(); - } + const boost::property_tree::ptree send_json_request(const std::string& path, const std::shared_ptr& params = nullptr); /** * Send a request to the RPC API. @@ -396,14 +417,7 @@ class PyMoneroRpcConnection : public monero::monero_rpc_connection { * @param timeout request timeout in milliseconds * @return the RPC API response as a map */ - inline const std::shared_ptr send_json_request(const PyMoneroJsonRequest &request, std::chrono::milliseconds timeout = std::chrono::seconds(15)) { - PyMoneroJsonResponse response; - - int result = invoke_post("/json_rpc", request, response, timeout); - if (result != 200) throw PyMoneroRpcError(result, "HTTP error: code " + std::to_string(result)); - - return std::make_shared(response); - } + const std::shared_ptr send_json_request(const monero_json_request &request, std::chrono::milliseconds timeout = std::chrono::seconds(15)); /** * Send a RPC request to the given path and with the given paramters. @@ -414,13 +428,7 @@ class PyMoneroRpcConnection : public monero::monero_rpc_connection { * @param params are request parameters sent in the body * @return the RPC API response as a map */ - inline const boost::property_tree::ptree send_path_request(const std::string& path, const std::shared_ptr& params = nullptr) { - PyMoneroPathRequest request(path, params); - auto response = send_path_request(request); - - if (response->m_response == boost::none) throw std::runtime_error("Invalid Monero path response"); - return response->m_response.get(); - } + const boost::property_tree::ptree send_path_request(const std::string& path, const std::shared_ptr& params = nullptr); /** * Send a RPC request to the given path and with the given paramters. @@ -429,15 +437,7 @@ class PyMoneroRpcConnection : public monero::monero_rpc_connection { * @param timeout request timeout in milliseconds * @return the request's deserialized response */ - inline const std::shared_ptr send_path_request(const PyMoneroPathRequest &request, std::chrono::milliseconds timeout = std::chrono::seconds(15)) { - PyMoneroPathResponse response; - - if (request.m_method == boost::none || request.m_method->empty()) throw std::runtime_error("No RPC method set in path request"); - int result = invoke_post(std::string("/") + request.m_method.get(), request, response, timeout); - if (result != 200) throw PyMoneroRpcError(result, "HTTP error: code " + std::to_string(result)); - - return std::make_shared(response); - } + const std::shared_ptr send_path_request(const monero_path_request &request, std::chrono::milliseconds timeout = std::chrono::seconds(15)); /** * Send a binary RPC request. @@ -446,22 +446,7 @@ class PyMoneroRpcConnection : public monero::monero_rpc_connection { * @param timeout request timeout in milliseconds * @return the request's deserialized response */ - inline const std::shared_ptr send_binary_request(const PyMoneroBinaryRequest &request, std::chrono::milliseconds timeout = std::chrono::seconds(15)) { - if (request.m_method == boost::none || request.m_method->empty()) throw std::runtime_error("No RPC method set in binary request"); - if (!m_http_client) throw std::runtime_error("http client not set"); - - std::string uri = std::string("/") + request.m_method.get(); - std::string body = request.to_binary_val(); - - const epee::net_utils::http::http_response_info* response = invoke_post(uri, body, timeout); - int result = response->m_response_code; - if (result != 200) throw PyMoneroRpcError(result, "HTTP error: code " + std::to_string(result)); - - auto res = std::make_shared(); - res->m_binary = response->m_body; - - return res; - } + const std::shared_ptr send_binary_request(const monero_binary_request &request, std::chrono::milliseconds timeout = std::chrono::seconds(15)); // exposed python methods @@ -472,11 +457,7 @@ class PyMoneroRpcConnection : public monero::monero_rpc_connection { * @param parameters are the request's input parameters * @return the RPC API response as a map */ - inline boost::optional send_json_request(const std::string& method, const boost::optional& parameters) { - PyMoneroJsonRequest request(method, parameters); - auto response = send_json_request(request); - return response->get_result(); - } + boost::optional send_json_request(const std::string& method, const boost::optional& parameters); /** * Send a RPC request to the given path and with the given paramters. @@ -487,11 +468,7 @@ class PyMoneroRpcConnection : public monero::monero_rpc_connection { * @param parameters are request parameters sent in the body * @return the RPC API response as a map */ - inline boost::optional send_path_request(const std::string& method, const boost::optional& parameters) { - PyMoneroPathRequest request(method, parameters); - auto response = send_path_request(request); - return response->get_response(); - } + boost::optional send_path_request(const std::string& method, const boost::optional& parameters); /** * Send a binary RPC request. @@ -500,16 +477,12 @@ class PyMoneroRpcConnection : public monero::monero_rpc_connection { * @param parameters are request parameters sent in the body * @return the request's deserialized response */ - inline boost::optional send_binary_request(const std::string& method, const boost::optional& parameters) { - PyMoneroBinaryRequest request(method, parameters); - auto response = send_binary_request(request); - return response->m_binary; - } + boost::optional send_binary_request(const std::string& method, const boost::optional& parameters); rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; protected: - // istance variables + // instance variables mutable boost::recursive_mutex m_mutex; std::string m_server; boost::optional m_credentials; @@ -518,38 +491,16 @@ class PyMoneroRpcConnection : public monero::monero_rpc_connection { boost::optional m_is_online; boost::optional m_is_authenticated; + const epee::net_utils::http::http_response_info* invoke_post(const boost::string_ref uri, const std::string& body, std::chrono::milliseconds timeout = std::chrono::seconds(15)) const; + template inline int invoke_post(const boost::string_ref uri, const t_request& request, t_response& res, std::chrono::milliseconds timeout = std::chrono::seconds(15)) const { - if (!m_http_client) throw std::runtime_error("http client not set"); - - rapidjson::Document document(rapidjson::Type::kObjectType); - rapidjson::Value req = request.to_rapidjson_val(document.GetAllocator()); - rapidjson::StringBuffer sb; - rapidjson::Writer writer(sb); - req.Accept(writer); - std::string body = sb.GetString(); - + std::string body = request.serialize(); const epee::net_utils::http::http_response_info* response = invoke_post(uri, body, timeout); - - int status_code = response->m_response_code; - - if (status_code == 200) { + if (response->m_response_code == 200) { res = *t_response::deserialize(response->m_body); } - - return status_code; - } - - inline const epee::net_utils::http::http_response_info* invoke_post(const boost::string_ref uri, const std::string& body, std::chrono::milliseconds timeout = std::chrono::seconds(15)) const { - if (!m_http_client) throw std::runtime_error("http client not set"); - - std::shared_ptr _res = std::make_shared(); - const epee::net_utils::http::http_response_info* response = _res.get(); - boost::lock_guard lock(m_mutex); - - if (!m_http_client->invoke_post(uri, body, timeout, &response)) throw std::runtime_error("Network error"); - - return response; + return response->m_response_code; } }; diff --git a/src/cpp/daemon/py_monero_daemon.h b/src/cpp/daemon/py_monero_daemon.h index 7325b45..870756e 100644 --- a/src/cpp/daemon/py_monero_daemon.h +++ b/src/cpp/daemon/py_monero_daemon.h @@ -1,3 +1,56 @@ +/** + * Copyright (c) everoddandeven + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2025-2026 woodser + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ #pragma once #include "py_monero_daemon_model.h" @@ -19,45 +72,33 @@ class PyMoneroDaemonListener : public monero_daemon_listener { } }; -class PyMoneroBlockNotifier : public PyMoneroDaemonListener { +class monero_daemon { public: - PyMoneroBlockNotifier(boost::mutex* temp, boost::condition_variable* cv, bool* ready) { this->temp = temp; this->cv = cv; this->ready = ready; } - void on_block_header(const std::shared_ptr& header) override { - boost::mutex::scoped_lock lock(*temp); - m_last_header = header; - *ready = true; - cv->notify_one(); - } -private: - boost::mutex* temp; - boost::condition_variable* cv; - bool* ready; -}; - -class PyMoneroDaemon { -public: - PyMoneroDaemon() {} - virtual void add_listener(const std::shared_ptr &listener) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual void remove_listener(const std::shared_ptr &listener) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::vector> get_listeners() { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual void remove_listeners() { throw std::runtime_error("PyMoneroDaemon::remove_listeners(): not supported"); }; - virtual monero::monero_version get_version() { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual bool is_trusted() { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual uint64_t get_height() { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::string get_block_hash(uint64_t height) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::shared_ptr get_block_template(const std::string& wallet_address) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::shared_ptr get_block_template(const std::string& wallet_address, int reserve_size) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::shared_ptr get_last_block_header() { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::shared_ptr get_block_header_by_hash(const std::string& hash) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::shared_ptr get_block_header_by_height(uint64_t height) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::vector> get_block_headers_by_range(uint64_t start_height, uint64_t end_height) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::shared_ptr get_block_by_hash(const std::string& hash) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::vector> get_blocks_by_hash(const std::vector& block_hashes, uint64_t start_height, bool prune) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::shared_ptr get_block_by_height(uint64_t height) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::vector> get_blocks_by_height(const std::vector& heights) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::vector> get_blocks_by_range(boost::optional start_height, boost::optional end_height) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::vector> get_blocks_by_range_chunked(boost::optional start_height, boost::optional end_height, boost::optional max_chunk_size) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::vector get_block_hashes(const std::vector& block_hashes, uint64_t start_height) { throw std::runtime_error("PyMoneroDaemon: not supported"); } + /** + * Virtual destructor. + */ + virtual ~monero_daemon() {} + monero_daemon() { } + virtual void add_listener(const std::shared_ptr &listener) { throw std::runtime_error("monero_daemon: not supported"); } + virtual void remove_listener(const std::shared_ptr &listener) { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::vector> get_listeners() { throw std::runtime_error("monero_daemon: not supported"); } + virtual void remove_listeners() { throw std::runtime_error("monero_daemon::remove_listeners(): not supported"); }; + virtual monero::monero_version get_version() { throw std::runtime_error("monero_daemon: not supported"); } + virtual bool is_trusted() { throw std::runtime_error("monero_daemon: not supported"); } + virtual uint64_t get_height() { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::string get_block_hash(uint64_t height) { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::shared_ptr get_block_template(const std::string& wallet_address, const boost::optional& reserve_size = boost::none) { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::shared_ptr get_last_block_header() { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::shared_ptr get_block_header_by_hash(const std::string& hash) { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::shared_ptr get_block_header_by_height(uint64_t height) { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::vector> get_block_headers_by_range(uint64_t start_height, uint64_t end_height) { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::shared_ptr get_block_by_hash(const std::string& hash) { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::vector> get_blocks_by_hash(const std::vector& block_hashes, uint64_t start_height, bool prune) { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::shared_ptr get_block_by_height(uint64_t height) { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::vector> get_blocks_by_height(const std::vector& heights) { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::vector> get_blocks_by_range(boost::optional start_height, boost::optional end_height) { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::vector> get_blocks_by_range_chunked(boost::optional start_height, boost::optional end_height, boost::optional max_chunk_size) { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::vector get_block_hashes(const std::vector& block_hashes, uint64_t start_height) { throw std::runtime_error("monero_daemon: not supported"); } virtual boost::optional> get_tx(const std::string& tx_hash, bool prune = false) { std::vector hashes; hashes.push_back(tx_hash); @@ -70,7 +111,7 @@ class PyMoneroDaemon { return tx; } - virtual std::vector> get_txs(const std::vector& tx_hashes, bool prune = false) { throw std::runtime_error("PyMoneroDaemon: not supported"); } + virtual std::vector> get_txs(const std::vector& tx_hashes, bool prune = false) { throw std::runtime_error("monero_daemon: not supported"); } virtual boost::optional get_tx_hex(const std::string& tx_hash, bool prune = false) { std::vector hashes; hashes.push_back(tx_hash); @@ -82,70 +123,70 @@ class PyMoneroDaemon { return hex; } - virtual std::vector get_tx_hexes(const std::vector& tx_hashes, bool prune = false) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::shared_ptr get_miner_tx_sum(uint64_t height, uint64_t num_blocks) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::shared_ptr get_fee_estimate(uint64_t grace_blocks = 0) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::shared_ptr submit_tx_hex(const std::string& tx_hex, bool do_not_relay = false) { throw std::runtime_error("PyMoneroDaemon: not supported"); } + virtual std::vector get_tx_hexes(const std::vector& tx_hashes, bool prune = false) { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::shared_ptr get_miner_tx_sum(uint64_t height, uint64_t num_blocks) { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::shared_ptr get_fee_estimate(uint64_t grace_blocks = 0) { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::shared_ptr submit_tx_hex(const std::string& tx_hex, bool do_not_relay = false) { throw std::runtime_error("monero_daemon: not supported"); } virtual void relay_tx_by_hash(const std::string& tx_hash) { std::vector tx_hashes; tx_hashes.push_back(tx_hash); relay_txs_by_hash(tx_hashes); } - virtual void relay_txs_by_hash(const std::vector& tx_hashes) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::vector> get_tx_pool() { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::vector get_tx_pool_hashes() { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::vector get_tx_pool_backlog() { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::shared_ptr get_tx_pool_stats() { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual void flush_tx_pool() { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual void flush_tx_pool(const std::vector &hashes) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual void flush_tx_pool(const std::string &hash) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual PyMoneroKeyImageSpentStatus get_key_image_spent_status(const std::string& key_image) { + virtual void relay_txs_by_hash(const std::vector& tx_hashes) { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::vector> get_tx_pool() { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::vector get_tx_pool_hashes() { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::vector get_tx_pool_backlog() { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::shared_ptr get_tx_pool_stats() { throw std::runtime_error("monero_daemon: not supported"); } + virtual void flush_tx_pool() { throw std::runtime_error("monero_daemon: not supported"); } + virtual void flush_tx_pool(const std::vector &hashes) { throw std::runtime_error("monero_daemon: not supported"); } + virtual void flush_tx_pool(const std::string &hash) { throw std::runtime_error("monero_daemon: not supported"); } + virtual monero_key_image_spent_status get_key_image_spent_status(const std::string& key_image) { std::vector key_images; key_images.push_back(key_image); auto statuses = get_key_image_spent_statuses(key_images); if (statuses.empty()) throw std::runtime_error("Could not get key image spent status"); return statuses[0]; } - virtual std::vector get_key_image_spent_statuses(const std::vector& key_images) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::vector> get_outputs(const std::vector& outputs) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::vector> get_output_histogram(const std::vector& amounts, const boost::optional& min_count, const boost::optional& max_count, const boost::optional& is_unlocked, const boost::optional& recent_cutoff) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::vector> get_output_distribution(const std::vector& amounts) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::vector> get_output_distribution(const std::vector& amounts, bool is_cumulative, uint64_t start_height, uint64_t end_height) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::shared_ptr get_info() { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::shared_ptr get_sync_info() { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::shared_ptr get_hard_fork_info() { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::vector> get_alt_chains() { throw std::runtime_error("PyMoneroDaemon::get_alt_chains(): not supported"); } - virtual std::vector get_alt_block_hashes() { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual int get_download_limit() { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual int set_download_limit(int limit) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual int reset_download_limit() { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual int get_upload_limit() { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual int set_upload_limit(int limit) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual int reset_upload_limit() { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::vector> get_peers() { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::vector> get_known_peers() { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual void set_outgoing_peer_limit(int limit) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual void set_incoming_peer_limit(int limit) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::vector> get_peer_bans() { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual void set_peer_bans(const std::vector>& bans) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual void set_peer_ban(const std::shared_ptr& ban) { + virtual std::vector get_key_image_spent_statuses(const std::vector& key_images) { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::vector> get_outputs(const std::vector& outputs) { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::vector> get_output_histogram(const std::vector& amounts, const boost::optional& min_count, const boost::optional& max_count, const boost::optional& is_unlocked, const boost::optional& recent_cutoff) { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::vector> get_output_distribution(const std::vector& amounts) { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::vector> get_output_distribution(const std::vector& amounts, bool is_cumulative, uint64_t start_height, uint64_t end_height) { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::shared_ptr get_info() { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::shared_ptr get_sync_info() { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::shared_ptr get_hard_fork_info() { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::vector> get_alt_chains() { throw std::runtime_error("monero_daemon::get_alt_chains(): not supported"); } + virtual std::vector get_alt_block_hashes() { throw std::runtime_error("monero_daemon: not supported"); } + virtual int get_download_limit() { throw std::runtime_error("monero_daemon: not supported"); } + virtual int set_download_limit(int limit) { throw std::runtime_error("monero_daemon: not supported"); } + virtual int reset_download_limit() { throw std::runtime_error("monero_daemon: not supported"); } + virtual int get_upload_limit() { throw std::runtime_error("monero_daemon: not supported"); } + virtual int set_upload_limit(int limit) { throw std::runtime_error("monero_daemon: not supported"); } + virtual int reset_upload_limit() { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::vector> get_peers() { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::vector> get_known_peers() { throw std::runtime_error("monero_daemon: not supported"); } + virtual void set_outgoing_peer_limit(int limit) { throw std::runtime_error("monero_daemon: not supported"); } + virtual void set_incoming_peer_limit(int limit) { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::vector> get_peer_bans() { throw std::runtime_error("monero_daemon: not supported"); } + virtual void set_peer_bans(const std::vector>& bans) { throw std::runtime_error("monero_daemon: not supported"); } + virtual void set_peer_ban(const std::shared_ptr& ban) { if (ban == nullptr) throw std::runtime_error("Ban is none"); - std::vector> bans; + std::vector> bans; bans.push_back(ban); set_peer_bans(bans); } - virtual void start_mining(const std::string &address, int num_threads, bool is_background, bool ignore_battery) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual void stop_mining() { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::shared_ptr get_mining_status() { throw std::runtime_error("PyMoneroDaemon: not supported"); } + virtual void start_mining(const std::string &address, int num_threads, bool is_background, bool ignore_battery) { throw std::runtime_error("monero_daemon: not supported"); } + virtual void stop_mining() { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::shared_ptr get_mining_status() { throw std::runtime_error("monero_daemon: not supported"); } virtual void submit_block(const std::string& block_blob) { std::vector block_blobs; block_blobs.push_back(block_blob); return submit_blocks(block_blobs); } - virtual void submit_blocks(const std::vector& block_blobs) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::shared_ptr prune_blockchain(bool check) { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::shared_ptr check_for_update() { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::shared_ptr download_update(const std::string& path = "") { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual void stop() { throw std::runtime_error("PyMoneroDaemon: not supported"); } - virtual std::shared_ptr wait_for_next_block_header() { throw std::runtime_error("PyMoneroDaemon: not supported"); } + virtual void submit_blocks(const std::vector& block_blobs) { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::shared_ptr prune_blockchain(bool check) { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::shared_ptr check_for_update() { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::shared_ptr download_update(const std::string& path = "") { throw std::runtime_error("monero_daemon: not supported"); } + virtual void stop() { throw std::runtime_error("monero_daemon: not supported"); } + virtual std::shared_ptr wait_for_next_block_header() { throw std::runtime_error("monero_daemon: not supported"); } }; diff --git a/src/cpp/daemon/py_monero_daemon_model.cpp b/src/cpp/daemon/py_monero_daemon_model.cpp index c2e21c8..8a462fc 100644 --- a/src/cpp/daemon/py_monero_daemon_model.cpp +++ b/src/cpp/daemon/py_monero_daemon_model.cpp @@ -1,30 +1,60 @@ +/** + * Copyright (c) everoddandeven + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2025-2026 woodser + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ #include "py_monero_daemon_model.h" #include "utils/monero_utils.h" -PyMoneroDownloadUpdateParams::PyMoneroDownloadUpdateParams(const std::string& command, const std::string& path): m_command(command) { - if (!path.empty()) m_path = path; -} - -PyMoneroStartMiningParams::PyMoneroStartMiningParams(const std::string& address, int num_threads, bool is_background, bool ignore_battery): - m_miner_address(address), - m_num_threads(num_threads), - m_is_background(is_background), - m_ignore_battery(ignore_battery) { -} - -PyMoneroGetTxsParams::PyMoneroGetTxsParams(const std::vector &tx_hashes, bool prune, bool decode_as_json): - m_tx_hashes(tx_hashes), - m_prune(prune), - m_decode_as_json(decode_as_json) { -} - -PyMoneroGetOutputHistrogramParams::PyMoneroGetOutputHistrogramParams(const std::vector& amounts, const boost::optional& min_count, const boost::optional& max_count, const boost::optional& is_unlocked, const boost::optional& recent_cutoff): - m_amounts(amounts), - m_min_count(min_count), - m_max_count(max_count), - m_is_unlocked(is_unlocked), - m_recent_cutoff(recent_cutoff) { -} +// --------------------------- Custom Data Model --------------------------- void PyMoneroBlockHeader::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& header) { for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { @@ -410,6 +440,20 @@ void PyMoneroTx::from_property_tree(const boost::property_tree::ptree& node, std } } +void PyMoneroTx::from_property_tree(const boost::property_tree::ptree& node, std::vector& tx_hashes) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + + if (key == std::string("tx_hashes")) { + auto node2 = it->second; + + for(boost::property_tree::ptree::const_iterator it2 = node2.begin(); it2 != node2.end(); ++it2) { + tx_hashes.push_back(it2->second.data()); + } + } + } +} + void PyMoneroVersion::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& version) { for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; @@ -418,7 +462,35 @@ void PyMoneroVersion::from_property_tree(const boost::property_tree::ptree& node } } -void PyMoneroAltChain::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& alt_chain) { +// --------------------------- MONERO RPC PAYMENT INFO --------------------------- + +void monero_rpc_payment_info::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& rpc_payment_info) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if ((key == std::string("top_hash") || key == std::string("top_block_hash")) && !it->second.data().empty()) rpc_payment_info->m_top_block_hash = it->second.data(); + else if (key == std::string("credits")) rpc_payment_info->m_credits = it->second.get_value(); + } +} + +rapidjson::Value monero_rpc_payment_info::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_top_block_hash != boost::none) monero_utils::add_json_member("topBlockHash", m_top_block_hash.get(), allocator, root, value_str); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_credits != boost::none) monero_utils::add_json_member("credits", m_credits.get(), allocator, root, value_num); + + // return root + return root; +} + +// --------------------------- MONERO ALT CHAIN --------------------------- + +void monero_alt_chain::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& alt_chain) { for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; if (key == std::string("difficulty")) alt_chain->m_difficulty = it->second.get_value(); @@ -431,14 +503,9 @@ void PyMoneroAltChain::from_property_tree(const boost::property_tree::ptree& nod } } -void PyMoneroGetBlockCountResult::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& result) { - for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { - std::string key = it->first; - if (key == std::string("count")) result->m_count = it->second.get_value(); - } -} +// --------------------------- MONERO BAN --------------------------- -void PyMoneroBan::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& ban) { +void monero_ban::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& ban) { for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; if (key == std::string("host")) ban->m_host = it->second.data(); @@ -448,21 +515,43 @@ void PyMoneroBan::from_property_tree(const boost::property_tree::ptree& node, co } } -void PyMoneroBan::from_property_tree(const boost::property_tree::ptree& node, std::vector>& bans) { +void monero_ban::from_property_tree(const boost::property_tree::ptree& node, std::vector>& bans) { for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; if (key == std::string("bans")) { auto node2 = it->second; for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) { - auto ban = std::make_shared(); - PyMoneroBan::from_property_tree(it2->second, ban); + auto ban = std::make_shared(); + monero_ban::from_property_tree(it2->second, ban); bans.push_back(ban); } } } } -void PyMoneroPruneResult::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& result) { +rapidjson::Value monero_ban::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_host != boost::none) monero_utils::add_json_member("host", m_host.get(), allocator, root, value_str); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_ip != boost::none) monero_utils::add_json_member("ip", m_ip.get(), allocator, root, value_num); + if (m_seconds != boost::none) monero_utils::add_json_member("seconds", m_seconds.get(), allocator, root, value_num); + + // set bool values + if (m_is_banned != boost::none) monero_utils::add_json_member("ban", m_is_banned.get(), allocator, root); + + // return root + return root; +} + +// --------------------------- MONERO PRUNE RESULT --------------------------- + +void monero_prune_result::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& result) { for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; if (key == std::string("pruned")) result->m_is_pruned = it->second.get_value(); @@ -470,7 +559,9 @@ void PyMoneroPruneResult::from_property_tree(const boost::property_tree::ptree& } } -void PyMoneroMiningStatus::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& status) { +// --------------------------- MONERO MINING STATUS --------------------------- + +void monero_mining_status::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& status) { for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; if (key == std::string("active")) status->m_is_active = it->second.get_value(); @@ -486,23 +577,9 @@ void PyMoneroMiningStatus::from_property_tree(const boost::property_tree::ptree& } } -std::vector PyMoneroTxHashes::from_property_tree(const boost::property_tree::ptree& node) { - std::vector result; - for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { - std::string key = it->first; - - if (key == std::string("tx_hashes")) { - auto node2 = it->second; +// --------------------------- MINER TX SUM --------------------------- - for(boost::property_tree::ptree::const_iterator it2 = node2.begin(); it2 != node2.end(); ++it2) { - result.push_back(it2->second.data()); - } - } - } - return result; -} - -void PyMoneroMinerTxSum::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& sum) { +void monero_miner_tx_sum::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& sum) { for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; if (key == std::string("emission_amount")) sum->m_emission_sum = it->second.get_value(); @@ -510,7 +587,9 @@ void PyMoneroMinerTxSum::from_property_tree(const boost::property_tree::ptree& n } } -void PyMoneroBlockTemplate::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& tmplt) { +// --------------------------- MONERO BLOCK TEMPLATE --------------------------- + +void monero_block_template::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& tmplt) { for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; if (key == std::string("blocktemplate_blob")) tmplt->m_block_template_blob = it->second.data(); @@ -526,7 +605,9 @@ void PyMoneroBlockTemplate::from_property_tree(const boost::property_tree::ptree } } -void PyMoneroConnectionSpan::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& span) { +// --------------------------- MONERO CONNECTION SPAN --------------------------- + +void monero_connection_span::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& span) { for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; if (key == std::string("connection_id")) span->m_connection_id = it->second.data(); @@ -539,7 +620,9 @@ void PyMoneroConnectionSpan::from_property_tree(const boost::property_tree::ptre } } -void PyMoneroPeer::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& peer) { +// --------------------------- MONERO PEER --------------------------- + +void monero_peer::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& peer) { for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; if (key == std::string("host")) peer->m_host = it->second.data(); @@ -569,34 +652,34 @@ void PyMoneroPeer::from_property_tree(const boost::property_tree::ptree& node, c else if (key == std::string("address_type")) { int rpc_type = it->second.get_value(); if (rpc_type == 0) { - peer->m_connection_type = PyMoneroConnectionType::INVALID; + peer->m_connection_type = monero_connection_type::INVALID; } else if (rpc_type == 1) { - peer->m_connection_type = PyMoneroConnectionType::IPV4; + peer->m_connection_type = monero_connection_type::IPV4; } else if (rpc_type == 2) { - peer->m_connection_type = PyMoneroConnectionType::IPV6; + peer->m_connection_type = monero_connection_type::IPV6; } else if (rpc_type == 3) { - peer->m_connection_type = PyMoneroConnectionType::TOR; + peer->m_connection_type = monero_connection_type::TOR; } else if (rpc_type == 4) { - peer->m_connection_type = PyMoneroConnectionType::I2P; + peer->m_connection_type = monero_connection_type::I2P; } else throw std::runtime_error("Invalid RPC peer type, expected 0-4: " + std::to_string(rpc_type)); } } } -void PyMoneroPeer::from_property_tree(const boost::property_tree::ptree& node, std::vector>& peers) { +void monero_peer::from_property_tree(const boost::property_tree::ptree& node, std::vector>& peers) { for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; bool is_online = key == std::string("white_list"); if (key == std::string("connections") || is_online || key == std::string("gray_list") ) { auto node2 = it->second; for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) { - auto peer = std::make_shared(); - PyMoneroPeer::from_property_tree(it2->second, peer); + auto peer = std::make_shared(); + monero_peer::from_property_tree(it2->second, peer); peer->m_is_online = is_online; peers.push_back(peer); } @@ -604,7 +687,11 @@ void PyMoneroPeer::from_property_tree(const boost::property_tree::ptree& node, s } } -void PyMoneroSubmitTxResult::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& result) { +// --------------------------- MONERO SUBMIT TX RESULT --------------------------- + +void monero_submit_tx_result::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& result) { + monero_rpc_payment_info::from_property_tree(node, result); + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; if (key == std::string("double_spend")) result->m_is_double_spend = it->second.get_value(); @@ -618,14 +705,14 @@ void PyMoneroSubmitTxResult::from_property_tree(const boost::property_tree::ptre else if (key == std::string("reason") && !it->second.data().empty()) result->m_reason = it->second.data(); else if (key == std::string("too_big")) result->m_is_too_big = it->second.get_value(); else if (key == std::string("sanity_check_failed")) result->m_sanity_check_failed = it->second.get_value(); - else if (key == std::string("credits")) result->m_credits = it->second.get_value(); - else if (key == std::string("top_hash") && !it->second.data().empty()) result->m_top_block_hash = it->second.data(); else if (key == std::string("tx_extra_too_big")) result->m_is_tx_extra_too_big = it->second.get_value(); else if (key == std::string("nonzero_unlock_time")) result->m_is_nonzero_unlock_time = it->second.get_value(); } } -void PyMoneroOutputDistributionEntry::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& entry) { +// --------------------------- MONERO OUTPUT DISTRIBUTION ENTRY --------------------------- + +void monero_output_distribution_entry::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& entry) { for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; if (key == std::string("amount")) entry->m_amount = it->second.get_value(); @@ -640,7 +727,9 @@ void PyMoneroOutputDistributionEntry::from_property_tree(const boost::property_t } } -void PyMoneroOutputHistogramEntry::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& entry) { +// --------------------------- MONERO OUTPUT HISTOGRAM ENTRY --------------------------- + +void monero_output_histogram_entry::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& entry) { for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; if (key == std::string("amount")) entry->m_amount = it->second.get_value(); @@ -650,7 +739,7 @@ void PyMoneroOutputHistogramEntry::from_property_tree(const boost::property_tree } } -void PyMoneroOutputHistogramEntry::from_property_tree(const boost::property_tree::ptree& node, std::vector>& entries) { +void monero_output_histogram_entry::from_property_tree(const boost::property_tree::ptree& node, std::vector>& entries) { for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; @@ -658,7 +747,7 @@ void PyMoneroOutputHistogramEntry::from_property_tree(const boost::property_tree auto node2 = it->second; for(boost::property_tree::ptree::const_iterator it2 = node2.begin(); it2 != node2.end(); ++it2) { - auto entry = std::make_shared(); + auto entry = std::make_shared(); from_property_tree(it2->second, entry); entries.push_back(entry); } @@ -666,11 +755,13 @@ void PyMoneroOutputHistogramEntry::from_property_tree(const boost::property_tree } } -void PyMoneroTxPoolStats::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& stats) { +// --------------------------- MONERO TX POOL STATS --------------------------- + +void monero_tx_pool_stats::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& stats) { for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; if (key == std::string("pool_stats")) { - PyMoneroTxPoolStats::from_property_tree(it->second, stats); + monero_tx_pool_stats::from_property_tree(it->second, stats); break; } else if (key == std::string("txs_total")) stats->m_num_txs = it->second.get_value(); @@ -689,7 +780,9 @@ void PyMoneroTxPoolStats::from_property_tree(const boost::property_tree::ptree& } } -void PyMoneroDaemonUpdateCheckResult::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& check) { +// --------------------------- MONERO DAEMON UPDATE CHECK RESULT --------------------------- + +void monero_daemon_update_check_result::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& check) { for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; if (key == std::string("update")) check->m_is_update_available = it->second.get_value(); @@ -700,8 +793,10 @@ void PyMoneroDaemonUpdateCheckResult::from_property_tree(const boost::property_t } } -void PyMoneroDaemonUpdateDownloadResult::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& check) { - PyMoneroDaemonUpdateCheckResult::from_property_tree(node, check); +// --------------------------- MONERO DAEMON UPDATE DOWNLOAD RESULT --------------------------- + +void monero_daemon_update_download_result::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& check) { + monero_daemon_update_check_result::from_property_tree(node, check); for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; @@ -709,7 +804,9 @@ void PyMoneroDaemonUpdateDownloadResult::from_property_tree(const boost::propert } } -void PyMoneroFeeEstimate::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& estimate) { +// --------------------------- MONERO FEE ESTIMATE --------------------------- + +void monero_fee_estimate::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& estimate) { for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; if (key == std::string("fee")) estimate->m_fee = it->second.get_value(); @@ -724,7 +821,11 @@ void PyMoneroFeeEstimate::from_property_tree(const boost::property_tree::ptree& } } -void PyMoneroDaemonInfo::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& info) { +// --------------------------- MONERO DAEMON INFO --------------------------- + +void monero_daemon_info::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& info) { + monero_rpc_payment_info::from_property_tree(node, info); + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; if (key == std::string("version")) info->m_version = it->second.data(); @@ -755,20 +856,22 @@ void PyMoneroDaemonInfo::from_property_tree(const boost::property_tree::ptree& n else if (key == std::string("adjusted_time")) info->m_adjusted_timestamp = it->second.get_value(); else if (key == std::string("target")) info->m_target = it->second.get_value(); else if (key == std::string("target_height")) info->m_target_height = it->second.get_value(); - else if ((key == std::string("top_block_hash") || key == std::string("top_hash")) && !it->second.data().empty()) info->m_top_block_hash = it->second.data(); else if (key == std::string("tx_count")) info->m_num_txs = it->second.get_value(); else if (key == std::string("tx_pool_size")) info->m_num_txs_pool = it->second.get_value(); else if (key == std::string("was_bootstrap_ever_used")) info->m_was_bootstrap_ever_used = it->second.get_value(); else if (key == std::string("database_size")) info->m_database_size = it->second.get_value(); else if (key == std::string("update_available")) info->m_update_available = it->second.get_value(); - else if (key == std::string("credits")) info->m_credits = it->second.get_value(); else if (key == std::string("busy_syncing")) info->m_is_busy_syncing = it->second.get_value(); else if (key == std::string("synchronized")) info->m_is_synchronized = it->second.get_value(); else if (key == std::string("restricted")) info->m_is_restricted = it->second.get_value(); } } -void PyMoneroDaemonSyncInfo::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& info) { +// --------------------------- MONERO DAEMON SYNC INFO --------------------------- + +void monero_daemon_sync_info::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& info) { + monero_rpc_payment_info::from_property_tree(node, info); + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; if (key == std::string("height")) info->m_height = it->second.get_value(); @@ -776,12 +879,14 @@ void PyMoneroDaemonSyncInfo::from_property_tree(const boost::property_tree::ptre else if (key == std::string("next_needed_pruning_seed")) info->m_next_needed_pruning_seed = it->second.get_value(); // TODO implement overview field //else if (key == std::string("overview") && !it->second.data().empty()) info->m_overview = it->second.data(); - else if (key == std::string("credits")) info->m_credits = it->second.get_value(); - else if (key == std::string("top_hash") && !it->second.data().empty()) info->m_top_block_hash = it->second.data(); } } -void PyMoneroHardForkInfo::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& info) { +// --------------------------- MONERO HARD FORK INFO --------------------------- + +void monero_hard_fork_info::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& info) { + monero_rpc_payment_info::from_property_tree(node, info); + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; if (key == std::string("earliest_height")) info->m_earliest_height = it->second.get_value(); @@ -792,24 +897,31 @@ void PyMoneroHardForkInfo::from_property_tree(const boost::property_tree::ptree& else if (key == std::string("votes")) info->m_num_votes = it->second.get_value(); else if (key == std::string("window")) info->m_window = it->second.get_value(); else if (key == std::string("voting")) info->m_voting = it->second.get_value(); - else if (key == std::string("credits")) info->m_credits = it->second.get_value(); - else if (key == std::string("top_hash") && !it->second.data().empty()) info->m_top_block_hash = it->second.data(); } } -void PyMoneroGetAltBlocksHashesResponse::from_property_tree(const boost::property_tree::ptree& node, std::vector& block_hashes) { - for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { - std::string key = it->first; - if (key == std::string("blks_hashes")) { - auto node2 = it->second; - for(auto it2 = node2.begin(); it2 != node2.end(); ++it2) { - block_hashes.push_back(it2->second.data()); - } - } - } +// --------------------------- MONERO DOWNLOAD UPDATE PARAMS --------------------------- + +monero_download_update_params::monero_download_update_params(const std::string& command, const std::string& path): m_command(command) { + if (!path.empty()) m_path = path; } -void PyMoneroBandwithLimits::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& limits) { +rapidjson::Value monero_download_update_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_command != boost::none) monero_utils::add_json_member("command", m_command.get(), allocator, root, value_str); + if (m_path != boost::none) monero_utils::add_json_member("path", m_path.get(), allocator, root, value_str); + + // return root + return root; +} + +// --------------------------- MONERO BANDWITH LIMITS PARAMS --------------------------- + +void monero_bandwith_limits_params::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& limits) { for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; if (key == std::string("limit_up")) limits->m_up = it->second.get_value(); @@ -817,167 +929,277 @@ void PyMoneroBandwithLimits::from_property_tree(const boost::property_tree::ptre } } -void PyMoneroGetHeightResponse::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& response) { - for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { - std::string key = it->first; - if (key == std::string("height")) response->m_height = it->second.get_value(); - else if (key == std::string("untrusted")) response->m_untrusted = it->second.get_value(); - } +rapidjson::Value monero_bandwith_limits_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_up != boost::none) monero_utils::add_json_member("limit_up", m_up.get(), allocator, root, value_num); + if (m_down != boost::none) monero_utils::add_json_member("limit_down", m_down.get(), allocator, root, value_num); + + // return root + return root; } -rapidjson::Value PyMoneroDownloadUpdateParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO SUBMIT TX PARAMS --------------------------- + +rapidjson::Value monero_submit_tx_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set string values rapidjson::Value value_str(rapidjson::kStringType); + if (m_tx_hex != boost::none) monero_utils::add_json_member("tx_as_hex", m_tx_hex.get(), allocator, root, value_str); - if (m_command != boost::none) monero_utils::add_json_member("command", m_command.get(), allocator, root, value_str); - if (m_path != boost::none) monero_utils::add_json_member("path", m_path.get(), allocator, root, value_str); + // set bool values + if (m_do_not_relay != boost::none) monero_utils::add_json_member("do_not_relay", m_do_not_relay.get(), allocator, root); + // return root return root; } -rapidjson::Value PyMoneroStartMiningParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO PEER LIMITS PARAMS --------------------------- + +rapidjson::Value monero_peer_limits_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); - rapidjson::Value value_str(rapidjson::kStringType); + + // set number values rapidjson::Value value_num(rapidjson::kNumberType); - if (m_miner_address != boost::none) monero_utils::add_json_member("miner_address", m_miner_address.get(), allocator, root, value_str); - if (m_num_threads != boost::none) monero_utils::add_json_member("threads_count", m_num_threads.get(), allocator, root, value_num); - if (m_is_background != boost::none) monero_utils::add_json_member("do_background_mining", m_is_background.get(), allocator, root); - if (m_ignore_battery != boost::none) monero_utils::add_json_member("ignore_battery", m_ignore_battery.get(), allocator, root); + if (m_in_peers != boost::none) monero_utils::add_json_member("in_peers", m_in_peers.get(), allocator, root, value_num); + if (m_out_peers != boost::none) monero_utils::add_json_member("out_peers", m_out_peers.get(), allocator, root, value_num); + + // return root return root; } -rapidjson::Value PyMoneroPruneBlockchainParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO GET TXS PARAMS --------------------------- + +rapidjson::Value monero_get_txs_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); - if (m_check != boost::none) monero_utils::add_json_member("check", m_check.get(), allocator, root); + + // set bool values + if (m_prune != boost::none) monero_utils::add_json_member("prune", m_prune.get(), allocator, root); + if (m_decode_as_json != boost::none) monero_utils::add_json_member("decode_as_json", m_decode_as_json.get(), allocator, root); + + // set sub-arrays + if (!m_tx_hashes.empty()) root.AddMember("txs_hashes", monero_utils::to_rapidjson_val(allocator, m_tx_hashes), allocator); + + // return root return root; } -rapidjson::Value PyMoneroSubmitBlocksParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { - return monero_utils::to_rapidjson_val(allocator, m_block_blobs); +// --------------------------- MONERO IS KEY IMAGE SPENT PARAMS PARAMS --------------------------- + +rapidjson::Value monero_is_key_image_spent_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set sub-arrays + if (!m_key_images.empty()) root.AddMember("key_images", monero_utils::to_rapidjson_val(allocator, m_key_images), allocator); + + // return root + return root; } -rapidjson::Value PyMoneroGetBlockParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO START MINING PARAMS --------------------------- + +rapidjson::Value monero_start_mining_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set string values rapidjson::Value value_str(rapidjson::kStringType); + if (m_miner_address != boost::none) monero_utils::add_json_member("miner_address", m_miner_address.get(), allocator, root, value_str); + + // set number values rapidjson::Value value_num(rapidjson::kNumberType); + if (m_num_threads != boost::none) monero_utils::add_json_member("threads_count", m_num_threads.get(), allocator, root, value_num); - if (m_hash != boost::none) monero_utils::add_json_member("hash", m_hash.get(), allocator, root, value_str); - if (m_height != boost::none) monero_utils::add_json_member("height", m_height.get(), allocator, root, value_num); - if (m_fill_pow_hash != boost::none) monero_utils::add_json_member("fill_pow_hash", m_fill_pow_hash.get(), allocator, root); + // set bool values + if (m_is_background != boost::none) monero_utils::add_json_member("do_background_mining", m_is_background.get(), allocator, root); + if (m_ignore_battery != boost::none) monero_utils::add_json_member("ignore_battery", m_ignore_battery.get(), allocator, root); + + // return root + return root; +} + +// --------------------------- MONERO PRUNE BLOCKCHAIN PARAMS --------------------------- + +rapidjson::Value monero_prune_blockchain_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set bool values + if (m_check != boost::none) monero_utils::add_json_member("check", m_check.get(), allocator, root); + // return root return root; } -rapidjson::Value PyMoneroGetBlockRangeParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO SUBMIT BLOCKS PARAMS --------------------------- + +rapidjson::Value monero_submit_blocks_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + return monero_utils::to_rapidjson_val(allocator, m_block_blobs); +} + +// --------------------------- MONERO GET BLOCK PARAMS --------------------------- + +rapidjson::Value monero_get_block_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); rapidjson::Value value_num(rapidjson::kNumberType); + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_hash != boost::none) monero_utils::add_json_member("hash", m_hash.get(), allocator, root, value_str); + + // set num values + if (m_height != boost::none) monero_utils::add_json_member("height", m_height.get(), allocator, root, value_num); if (m_start_height != boost::none) monero_utils::add_json_member("start_height", m_start_height.get(), allocator, root, value_num); if (m_end_height != boost::none) monero_utils::add_json_member("end_height", m_end_height.get(), allocator, root, value_num); + // set bool values + if (m_fill_pow_hash != boost::none) monero_utils::add_json_member("fill_pow_hash", m_fill_pow_hash.get(), allocator, root); + + // return root return root; } -rapidjson::Value PyMoneroGetBlockHashParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO GET BLOCK HASH PARAMS --------------------------- + +rapidjson::Value monero_get_block_hash_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { std::vector params; if (m_height != boost::none) params.push_back(m_height.get()); return monero_utils::to_rapidjson_val(allocator, params); } -rapidjson::Value PyMoneroGetBlockTemplateParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO GET BLOCK TEMPLATE PARAMS --------------------------- + +rapidjson::Value monero_get_block_template_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); - rapidjson::Value value_str(rapidjson::kStringType); - rapidjson::Value value_num(rapidjson::kNumberType); + // set string values + rapidjson::Value value_str(rapidjson::kStringType); if (m_wallet_address != boost::none) monero_utils::add_json_member("wallet_address", m_wallet_address.get(), allocator, root, value_str); + + // set num values + rapidjson::Value value_num(rapidjson::kNumberType); if (m_reserve_size != boost::none) monero_utils::add_json_member("reserve_size", m_reserve_size.get(), allocator, root, value_num); + // return root return root; } -rapidjson::Value PyMoneroBan::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { - rapidjson::Value root(rapidjson::kObjectType); - rapidjson::Value value_str(rapidjson::kStringType); - rapidjson::Value value_num(rapidjson::kNumberType); - if (m_host != boost::none) monero_utils::add_json_member("host", m_host.get(), allocator, root, value_str); - if (m_ip != boost::none) monero_utils::add_json_member("ip", m_ip.get(), allocator, root, value_num); - if (m_is_banned != boost::none) monero_utils::add_json_member("ban", m_is_banned.get(), allocator, root); - if (m_seconds != boost::none) monero_utils::add_json_member("seconds", m_seconds.get(), allocator, root, value_num); - return root; -} +// --------------------------- MONERO RELAY TX PARAMS --------------------------- -rapidjson::Value PyMoneroSubmitTxParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +rapidjson::Value monero_relay_tx_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); - rapidjson::Value value_str(rapidjson::kStringType); - if (m_tx_hex != boost::none) monero_utils::add_json_member("tx_as_hex", m_tx_hex.get(), allocator, root, value_str); - if (m_do_not_relay != boost::none) monero_utils::add_json_member("do_not_relay", m_do_not_relay.get(), allocator, root); - return root; -} -rapidjson::Value PyMoneroRelayTxParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { - rapidjson::Value root(rapidjson::kObjectType); + // set sub-arrays if (!m_tx_hashes.empty()) root.AddMember("txids", monero_utils::to_rapidjson_val(allocator, m_tx_hashes), allocator); - return root; -} -rapidjson::Value PyMoneroBandwithLimits::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { - rapidjson::Value root(rapidjson::kObjectType); - rapidjson::Value value_num(rapidjson::kNumberType); - if (m_up != boost::none) monero_utils::add_json_member("limit_up", m_up.get(), allocator, root, value_num); - if (m_down != boost::none) monero_utils::add_json_member("limit_down", m_down.get(), allocator, root, value_num); + // return root return root; } -rapidjson::Value PyMoneroPeerLimits::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { - rapidjson::Value root(rapidjson::kObjectType); - rapidjson::Value value_num(rapidjson::kNumberType); - if (m_in_peers != boost::none) monero_utils::add_json_member("in_peers", m_in_peers.get(), allocator, root, value_num); - if (m_out_peers != boost::none) monero_utils::add_json_member("out_peers", m_out_peers.get(), allocator, root, value_num); - return root; -} +// --------------------------- MONERO GET MINER TX SUM PARAMS --------------------------- -rapidjson::Value PyMoneroGetMinerTxSumParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +rapidjson::Value monero_get_miner_tx_sum_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set num values rapidjson::Value value_num(rapidjson::kNumberType); if (m_height != boost::none) monero_utils::add_json_member("height", m_height.get(), allocator, root, value_num); if (m_count != boost::none) monero_utils::add_json_member("count", m_count.get(), allocator, root, value_num); + + // return root return root; } -rapidjson::Value PyMoneroGetFeeEstimateParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO GET FEE ESTIMATE PARAMS --------------------------- + +rapidjson::Value monero_get_fee_estimate_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set num values rapidjson::Value value_num(rapidjson::kNumberType); if (m_grace_blocks != boost::none) monero_utils::add_json_member("grace_blocks", m_grace_blocks.get(), allocator, root, value_num); + + // return root return root; } -rapidjson::Value PyMoneroSetBansParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO SET BANS PARAMS --------------------------- + +rapidjson::Value monero_set_bans_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set sub-arrays if (!m_bans.empty()) root.AddMember("bans", monero_utils::to_rapidjson_val(allocator, m_bans), allocator); - return root; -} -rapidjson::Value PyMoneroGetTxsParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { - rapidjson::Value root(rapidjson::kObjectType); - if (!m_tx_hashes.empty()) root.AddMember("txs_hashes", monero_utils::to_rapidjson_val(allocator, m_tx_hashes), allocator); - if (m_prune != boost::none) monero_utils::add_json_member("prune", m_prune.get(), allocator, root); - if (m_decode_as_json != boost::none) monero_utils::add_json_member("decode_as_json", m_decode_as_json.get(), allocator, root); + // return root return root; } -rapidjson::Value PyMoneroGetOutputHistrogramParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO GET OUTPUT HISTOGRAM PARAMS --------------------------- + +rapidjson::Value monero_get_output_histogram_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set num values rapidjson::Value value_num(rapidjson::kNumberType); - if (!m_amounts.empty()) root.AddMember("amounts", monero_utils::to_rapidjson_val(allocator, m_amounts), allocator); if (m_min_count != boost::none) monero_utils::add_json_member("min_count", m_min_count.get(), allocator, root, value_num); if (m_max_count != boost::none) monero_utils::add_json_member("max_count", m_max_count.get(), allocator, root, value_num); - if (m_is_unlocked != boost::none) monero_utils::add_json_member("is_unlocked", m_is_unlocked.get(), allocator, root); if (m_recent_cutoff != boost::none) monero_utils::add_json_member("recent_cutoff", m_recent_cutoff.get(), allocator, root, value_num); + + // set bool values + if (m_is_unlocked != boost::none) monero_utils::add_json_member("is_unlocked", m_is_unlocked.get(), allocator, root); + + // set sub-array values + if (!m_amounts.empty()) root.AddMember("amounts", monero_utils::to_rapidjson_val(allocator, m_amounts), allocator); + + // return root return root; } -rapidjson::Value PyMoneroIsKeyImageSpentParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { - rapidjson::Value root(rapidjson::kObjectType); - if (!m_key_images.empty()) root.AddMember("key_images", monero_utils::to_rapidjson_val(allocator, m_key_images), allocator); - return root; +// --------------------------- MONERO GET BLOCK COUNT RESULT --------------------------- + +void monero_get_block_count_result::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& result) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("count")) result->m_count = it->second.get_value(); + } +} + +// --------------------------- MONERO GET ALT BLOCK HASHES RESPONSE --------------------------- + +void monero_get_alt_block_hashes_response::from_property_tree(const boost::property_tree::ptree& node, std::vector& block_hashes) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("blks_hashes")) { + auto node2 = it->second; + for(auto it2 = node2.begin(); it2 != node2.end(); ++it2) { + block_hashes.push_back(it2->second.data()); + } + } + } +} + +// --------------------------- MONERO GET HEIGHT RESPONSE --------------------------- + +void monero_get_height_response::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& response) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("height")) response->m_height = it->second.get_value(); + else if (key == std::string("untrusted")) response->m_untrusted = it->second.get_value(); + } } diff --git a/src/cpp/daemon/py_monero_daemon_model.h b/src/cpp/daemon/py_monero_daemon_model.h index 5c13b12..3198849 100644 --- a/src/cpp/daemon/py_monero_daemon_model.h +++ b/src/cpp/daemon/py_monero_daemon_model.h @@ -1,134 +1,114 @@ +/** + * Copyright (c) everoddandeven + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2025-2026 woodser + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ #pragma once #include "common/py_monero_common.h" -enum PyMoneroKeyImageSpentStatus : uint8_t { - NOT_SPENT = 0, - CONFIRMED, - TX_POOL -}; +// ------------------------------ Custom Data Model --------------------------------- -class PyMoneroBlockHeader : public monero::monero_block_header { +struct PyMoneroBlockHeader : public monero::monero_block_header { public: static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& header); static void from_property_tree(const boost::property_tree::ptree& node, std::vector>& headers); }; -class PyMoneroBlock : public PyMoneroBlockHeader { +struct PyMoneroBlock : public PyMoneroBlockHeader { public: static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& block); static void from_property_tree(const boost::property_tree::ptree& node, const std::vector& heights, std::vector>& blocks); }; -class PyMoneroOutput : public monero::monero_output { +struct PyMoneroOutput : public monero::monero_output { public: static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& output); }; -class PyMoneroTx : public monero::monero_tx { +struct PyMoneroTx : public monero::monero_tx { public: inline static const std::string DEFAULT_ID = "0000000000000000000000000000000000000000000000000000000000000000"; static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& tx); static void from_property_tree(const boost::property_tree::ptree& node, std::vector>& txs); + static void from_property_tree(const boost::property_tree::ptree& node, std::vector& tx_hashes); }; -class PyMoneroTxHashes { -public: - static std::vector from_property_tree(const boost::property_tree::ptree& node); -}; - -// #region JSON-RPC - -class PyMoneroDownloadUpdateParams : public PyMoneroRequestParams { -public: - boost::optional m_command; - boost::optional m_path; - - PyMoneroDownloadUpdateParams(const std::string& command = "download", const std::string& path = ""); - - rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; -}; - -class PyMoneroStartMiningParams : public PyMoneroJsonRequestParams { -public: - boost::optional m_miner_address; - boost::optional m_num_threads; - boost::optional m_is_background; - boost::optional m_ignore_battery; - - PyMoneroStartMiningParams(const std::string& address, int num_threads, bool is_background, bool ignore_battery); - PyMoneroStartMiningParams(int num_threads, bool is_background, bool ignore_battery): m_num_threads(num_threads), m_is_background(is_background), m_ignore_battery(ignore_battery) { } - - rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; -}; - -class PyMoneroPruneBlockchainParams : public PyMoneroJsonRequestParams { -public: - boost::optional m_check; - - PyMoneroPruneBlockchainParams(bool check = true): m_check(check) { } - - rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; -}; - -class PyMoneroSubmitBlocksParams : public PyMoneroJsonRequestParams { -public: - std::vector m_block_blobs; - - PyMoneroSubmitBlocksParams(const std::vector& block_blobs): m_block_blobs(block_blobs) { } - - rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; -}; - -class PyMoneroGetBlockParams : public PyMoneroJsonRequestParams { -public: - boost::optional m_height; - boost::optional m_hash; - boost::optional m_fill_pow_hash; - - PyMoneroGetBlockParams(uint64_t height, bool fill_pow_hash = false): m_height(height), m_fill_pow_hash(fill_pow_hash) { } - PyMoneroGetBlockParams(const std::string& hash, bool fill_pow_hash = false): m_hash(hash), m_fill_pow_hash(fill_pow_hash) { } - - rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; -}; - -class PyMoneroGetBlockRangeParams : public PyMoneroJsonRequestParams { +struct PyMoneroVersion : public monero::monero_version { public: - boost::optional m_start_height; - boost::optional m_end_height; - - PyMoneroGetBlockRangeParams(uint64_t start_height, uint64_t end_height): m_start_height(start_height), m_end_height(end_height) { } - - rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& version); }; -class PyMoneroGetBlockHashParams : public PyMoneroJsonRequestParams { -public: - boost::optional m_height; - - PyMoneroGetBlockHashParams(uint64_t height): m_height(height) { } +// ------------------------------ Extended Data Model --------------------------------- - rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; +enum monero_key_image_spent_status : uint8_t { + NOT_SPENT = 0, + CONFIRMED, + TX_POOL }; -class PyMoneroGetBlockTemplateParams : public PyMoneroJsonRequestParams { +/** + * Models a Monero RPC payment information. + */ +struct monero_rpc_payment_info : public monero::serializable_struct { public: - boost::optional m_wallet_address; - boost::optional m_reserve_size; - - PyMoneroGetBlockTemplateParams(const std::string& wallet_address, const boost::optional& reserve_size = boost::none): m_wallet_address(wallet_address), m_reserve_size(reserve_size) { } + boost::optional m_credits; + boost::optional m_top_block_hash; rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& rpc_payment_info); }; -class PyMoneroVersion : public monero::monero_version { -public: - - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& version); -}; - -class PyMoneroAltChain { +struct monero_alt_chain { public: std::vector m_block_hashes; boost::optional m_difficulty; @@ -136,17 +116,10 @@ class PyMoneroAltChain { boost::optional m_length; boost::optional m_main_chain_parent_block_hash; - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& alt_chain); + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& alt_chain); }; -class PyMoneroGetBlockCountResult { -public: - boost::optional m_count; - - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& result); -}; - -class PyMoneroBan : public PySerializableStruct { +struct monero_ban : public monero::serializable_struct { public: boost::optional m_host; boost::optional m_ip; @@ -154,19 +127,19 @@ class PyMoneroBan : public PySerializableStruct { boost::optional m_seconds; rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& ban); - static void from_property_tree(const boost::property_tree::ptree& node, std::vector>& bans); + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& ban); + static void from_property_tree(const boost::property_tree::ptree& node, std::vector>& bans); }; -class PyMoneroPruneResult { +struct monero_prune_result { public: boost::optional m_is_pruned; boost::optional m_pruning_seed; - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& result); + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& result); }; -class PyMoneroMiningStatus { +struct monero_mining_status { public: boost::optional m_is_active; boost::optional m_is_background; @@ -174,18 +147,18 @@ class PyMoneroMiningStatus { boost::optional m_speed; boost::optional m_num_threads; - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& status); + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& status); }; -class PyMoneroMinerTxSum { +struct monero_miner_tx_sum { public: boost::optional m_emission_sum; boost::optional m_fee_sum; - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& sum); + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& sum); }; -class PyMoneroBlockTemplate { +struct monero_block_template { public: boost::optional m_block_template_blob; boost::optional m_block_hashing_blob; @@ -198,10 +171,10 @@ class PyMoneroBlockTemplate { boost::optional m_seed_hash; boost::optional m_next_seed_hash; - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& tmplt); + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& tmplt); }; -class PyMoneroConnectionSpan { +struct monero_connection_span { public: boost::optional m_connection_id; boost::optional m_num_blocks; @@ -211,10 +184,10 @@ class PyMoneroConnectionSpan { boost::optional m_size; boost::optional m_start_height; - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& span); + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& span); }; -class PyMoneroPeer { +struct monero_peer { public: boost::optional m_id; boost::optional m_address; @@ -241,13 +214,13 @@ class PyMoneroPeer { boost::optional m_send_idle_time; boost::optional m_state; boost::optional m_num_support_flags; - boost::optional m_connection_type; + boost::optional m_connection_type; - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& peer); - static void from_property_tree(const boost::property_tree::ptree& node, std::vector>& peers); + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& peer); + static void from_property_tree(const boost::property_tree::ptree& node, std::vector>& peers); }; -class PyMoneroSubmitTxResult { +struct monero_submit_tx_result : public monero_rpc_payment_info { public: boost::optional m_is_good; boost::optional m_is_relayed; @@ -266,54 +239,35 @@ class PyMoneroSubmitTxResult { boost::optional m_is_tx_extra_too_big; boost::optional m_is_nonzero_unlock_time; - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& result); -}; - -class PyMoneroSubmitTxParams : public PyMoneroRequestParams { -public: - boost::optional m_tx_hex; - boost::optional m_do_not_relay; - - PyMoneroSubmitTxParams(const std::string& tx_hex, bool do_not_relay): m_tx_hex(tx_hex), m_do_not_relay(do_not_relay) { } - - rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; -}; - -class PyMoneroRelayTxParams : public PyMoneroJsonRequestParams { -public: - std::vector m_tx_hashes; - - PyMoneroRelayTxParams(const std::vector& tx_hashes): m_tx_hashes(tx_hashes) { } - - rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& result); }; -class PyMoneroTxBacklogEntry { +struct monero_tx_backlog_entry { // TODO }; -class PyMoneroOutputDistributionEntry { +struct monero_output_distribution_entry { public: boost::optional m_amount; boost::optional m_base; std::vector m_distribution; boost::optional m_start_height; - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& entry); + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& entry); }; -class PyMoneroOutputHistogramEntry { +struct monero_output_histogram_entry { public: boost::optional m_amount; boost::optional m_num_instances; boost::optional m_unlocked_instances; boost::optional m_recent_instances; - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& entry); - static void from_property_tree(const boost::property_tree::ptree& node, std::vector>& entries); + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& entry); + static void from_property_tree(const boost::property_tree::ptree& node, std::vector>& entries); }; -class PyMoneroTxPoolStats { +struct monero_tx_pool_stats { public: boost::optional m_num_txs; boost::optional m_num_not_relayed; @@ -329,10 +283,10 @@ class PyMoneroTxPoolStats { boost::optional m_histo98pc; boost::optional m_oldest_timestamp; - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& stats); + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& stats); }; -class PyMoneroDaemonUpdateCheckResult { +struct monero_daemon_update_check_result { public: boost::optional m_is_update_available; boost::optional m_version; @@ -340,26 +294,26 @@ class PyMoneroDaemonUpdateCheckResult { boost::optional m_auto_uri; boost::optional m_user_uri; - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& check); + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& check); }; -class PyMoneroDaemonUpdateDownloadResult : public PyMoneroDaemonUpdateCheckResult { +struct monero_daemon_update_download_result : public monero_daemon_update_check_result { public: boost::optional m_download_path; - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& check); + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& check); }; -class PyMoneroFeeEstimate { +struct monero_fee_estimate { public: boost::optional m_fee; std::vector m_fees; boost::optional m_quantization_mask; - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& estimate); + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& estimate); }; -class PyMoneroDaemonInfo { +struct monero_daemon_info : public monero_rpc_payment_info { public: boost::optional m_version; boost::optional m_num_alt_blocks; @@ -395,24 +349,24 @@ class PyMoneroDaemonInfo { boost::optional m_is_synchronized; boost::optional m_is_restricted; - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& info); + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& info); }; -class PyMoneroDaemonSyncInfo { +struct monero_daemon_sync_info : public monero_rpc_payment_info { public: boost::optional m_height; - std::vector m_peers; - std::vector m_spans; + std::vector m_peers; + std::vector m_spans; boost::optional m_target_height; boost::optional m_next_needed_pruning_seed; boost::optional m_overview; boost::optional m_credits; boost::optional m_top_block_hash; - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& info); + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& info); }; -class PyMoneroHardForkInfo { +struct monero_hard_fork_info : public monero_rpc_payment_info { public: boost::optional m_earliest_height; boost::optional m_is_enabled; @@ -425,27 +379,44 @@ class PyMoneroHardForkInfo { boost::optional m_credits; boost::optional m_top_block_hash; - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& info); + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& info); }; -class PyMoneroGetAltBlocksHashesResponse { +// ------------------------------ RPC Params --------------------------------- + +struct monero_download_update_params : public monero_request_params { public: - static void from_property_tree(const boost::property_tree::ptree& node, std::vector& block_hashes); + boost::optional m_command; + boost::optional m_path; + + monero_download_update_params(const std::string& command = "download", const std::string& path = ""); + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; +}; + +struct monero_submit_tx_params : public monero_request_params { +public: + boost::optional m_tx_hex; + boost::optional m_do_not_relay; + + monero_submit_tx_params(const std::string& tx_hex, bool do_not_relay): m_tx_hex(tx_hex), m_do_not_relay(do_not_relay) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroBandwithLimits : public PyMoneroRequestParams { +struct monero_bandwith_limits_params : public monero_request_params { public: boost::optional m_up; boost::optional m_down; - PyMoneroBandwithLimits() { } - PyMoneroBandwithLimits(int up, int down): m_up(up), m_down(down) { } + monero_bandwith_limits_params() { } + monero_bandwith_limits_params(int up, int down): m_up(up), m_down(down) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& limits); + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& limits); }; -class PyMoneroPeerLimits : public PyMoneroRequestParams { +struct monero_peer_limits_params : public monero_request_params { public: boost::optional m_in_peers; boost::optional m_out_peers; @@ -453,54 +424,131 @@ class PyMoneroPeerLimits : public PyMoneroRequestParams { rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroGetHeightResponse { +struct monero_get_txs_params : public monero_request_params { public: - boost::optional m_height; - boost::optional m_untrusted; + std::vector m_tx_hashes; + boost::optional m_decode_as_json; + boost::optional m_prune; + + monero_get_txs_params(const std::vector &tx_hashes, bool prune, bool decode_as_json = true): m_tx_hashes(tx_hashes), m_prune(prune), m_decode_as_json(decode_as_json) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; +}; + +struct monero_is_key_image_spent_params : public monero_request_params { +public: + std::vector m_key_images; + + monero_is_key_image_spent_params(const std::vector& key_images): m_key_images(key_images) { } - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& response); + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; +}; + +// ------------------------------ JSON-RPC Params --------------------------------- + +struct monero_start_mining_params : public monero_json_request_params { +public: + boost::optional m_miner_address; + boost::optional m_num_threads; + boost::optional m_is_background; + boost::optional m_ignore_battery; + + monero_start_mining_params(const std::string& address, int num_threads, bool is_background, bool ignore_battery): m_miner_address(address), m_num_threads(num_threads), m_is_background(is_background), m_ignore_battery(ignore_battery) { } + monero_start_mining_params(int num_threads, bool is_background, bool ignore_battery): m_num_threads(num_threads), m_is_background(is_background), m_ignore_battery(ignore_battery) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroGetMinerTxSumParams : public PyMoneroJsonRequestParams { +struct monero_prune_blockchain_params : public monero_json_request_params { +public: + boost::optional m_check; + + monero_prune_blockchain_params(bool check = true): m_check(check) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; +}; + +struct monero_submit_blocks_params : public monero_json_request_params { +public: + std::vector m_block_blobs; + + monero_submit_blocks_params(const std::vector& block_blobs): m_block_blobs(block_blobs) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; +}; + +struct monero_get_block_params : public monero_json_request_params { public: boost::optional m_height; - boost::optional m_count; + boost::optional m_hash; + boost::optional m_fill_pow_hash; + boost::optional m_start_height; + boost::optional m_end_height; - PyMoneroGetMinerTxSumParams(uint64_t height, uint64_t count): m_height(height), m_count(count) { } + monero_get_block_params(uint64_t height, bool fill_pow_hash = false): m_height(height), m_fill_pow_hash(fill_pow_hash) { } + monero_get_block_params(const std::string& hash, bool fill_pow_hash = false): m_hash(hash), m_fill_pow_hash(fill_pow_hash) { } + monero_get_block_params(uint64_t start_height, uint64_t end_height): m_start_height(start_height), m_end_height(end_height) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroGetFeeEstimateParams : public PyMoneroJsonRequestParams { +struct monero_get_block_hash_params : public monero_json_request_params { public: - boost::optional m_grace_blocks; + boost::optional m_height; - PyMoneroGetFeeEstimateParams(uint64_t grace_blocks = 0): m_grace_blocks(grace_blocks) { } + monero_get_block_hash_params(uint64_t height): m_height(height) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroSetBansParams : public PyMoneroJsonRequestParams { +struct monero_get_block_template_params : public monero_json_request_params { public: - std::vector> m_bans; + boost::optional m_wallet_address; + boost::optional m_reserve_size; - PyMoneroSetBansParams(const std::vector>& bans): m_bans(bans) { } + monero_get_block_template_params(const std::string& wallet_address, const boost::optional& reserve_size = boost::none): m_wallet_address(wallet_address), m_reserve_size(reserve_size) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroGetTxsParams : public PyMoneroRequestParams { +struct monero_relay_tx_params : public monero_json_request_params { public: std::vector m_tx_hashes; - boost::optional m_decode_as_json; - boost::optional m_prune; - PyMoneroGetTxsParams(const std::vector &tx_hashes, bool prune, bool decode_as_json = true); + monero_relay_tx_params(const std::vector& tx_hashes): m_tx_hashes(tx_hashes) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; +}; + +struct monero_get_miner_tx_sum_params : public monero_json_request_params { +public: + boost::optional m_height; + boost::optional m_count; + + monero_get_miner_tx_sum_params(uint64_t height, uint64_t count): m_height(height), m_count(count) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroGetOutputHistrogramParams : public PyMoneroJsonRequestParams { +struct monero_get_fee_estimate_params : public monero_json_request_params { +public: + boost::optional m_grace_blocks; + + monero_get_fee_estimate_params(uint64_t grace_blocks = 0): m_grace_blocks(grace_blocks) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; +}; + +struct monero_set_bans_params : public monero_json_request_params { +public: + std::vector> m_bans; + + monero_set_bans_params(const std::vector>& bans): m_bans(bans) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; +}; + +struct monero_get_output_histogram_params : public monero_json_request_params { public: std::vector m_amounts; boost::optional m_min_count; @@ -508,16 +556,29 @@ class PyMoneroGetOutputHistrogramParams : public PyMoneroJsonRequestParams { boost::optional m_is_unlocked; boost::optional m_recent_cutoff; - PyMoneroGetOutputHistrogramParams(const std::vector& amounts, const boost::optional& min_count, const boost::optional& max_count, const boost::optional& is_unlocked, const boost::optional& recent_cutoff); + monero_get_output_histogram_params(const std::vector& amounts, const boost::optional& min_count, const boost::optional& max_count, const boost::optional& is_unlocked, const boost::optional& recent_cutoff) : m_amounts(amounts), m_min_count(min_count), m_max_count(max_count), m_is_unlocked(is_unlocked), m_recent_cutoff(recent_cutoff) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroIsKeyImageSpentParams : public PyMoneroRequestParams { +// ------------------------------ JSON-RPC Response --------------------------------- + +struct monero_get_block_count_result { public: - std::vector m_key_images; + boost::optional m_count; - PyMoneroIsKeyImageSpentParams(const std::vector& key_images): m_key_images(key_images) { } + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& result); +}; - rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; +struct monero_get_alt_block_hashes_response { +public: + static void from_property_tree(const boost::property_tree::ptree& node, std::vector& block_hashes); +}; + +struct monero_get_height_response { +public: + boost::optional m_height; + boost::optional m_untrusted; + + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& response); }; diff --git a/src/cpp/daemon/py_monero_daemon_rpc.cpp b/src/cpp/daemon/py_monero_daemon_rpc.cpp index 35569f0..b66dcc6 100644 --- a/src/cpp/daemon/py_monero_daemon_rpc.cpp +++ b/src/cpp/daemon/py_monero_daemon_rpc.cpp @@ -1,78 +1,161 @@ +/** + * Copyright (c) everoddandeven + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2025-2026 woodser + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ #include "py_monero_daemon_rpc.h" #include "utils/py_monero_utils.h" static const uint64_t MAX_REQ_SIZE = 3000000; static const uint64_t NUM_HEADERS_PER_REQ = 750; -PyMoneroDaemonPoller::PyMoneroDaemonPoller(PyMoneroDaemon* daemon, uint64_t poll_period_ms) { - m_daemon = daemon; - init_common("monero_daemon_rpc"); - m_poll_period_ms = poll_period_ms; -} +/** + * Polls daemon and sends notifications in order to notify external daemon listeners. + */ +class monero_daemon_poller: public thread_poller { +public: -void PyMoneroDaemonPoller::poll() { - if (!m_last_header) { - m_last_header = m_daemon->get_last_block_header(); - return; + explicit monero_daemon_poller(monero_daemon* daemon, uint64_t poll_period_ms = 5000): m_daemon(daemon) { + init_common("monero_daemon_rpc"); + m_poll_period_ms = poll_period_ms; } - auto header = m_daemon->get_last_block_header(); - if (header->m_hash != m_last_header->m_hash) { - m_last_header = header; - announce_block_header(header); + void poll() override { + if (!m_last_header) { + m_last_header = m_daemon->get_last_block_header(); + return; + } + + auto header = m_daemon->get_last_block_header(); + if (header->m_hash != m_last_header->m_hash) { + m_last_header = header; + announce_block_header(header); + } } -} -void PyMoneroDaemonPoller::announce_block_header(const std::shared_ptr& header) { - const auto& listeners = m_daemon->get_listeners(); - for (const auto& listener : listeners) { - try { - listener->on_block_header(header); +private: + monero_daemon* m_daemon; + std::shared_ptr m_last_header; + + void announce_block_header(const std::shared_ptr& header) { + const auto& listeners = m_daemon->get_listeners(); + for (const auto& listener : listeners) { + try { + listener->on_block_header(header); - } catch (const std::exception& e) { - std::cout << "Error calling listener on new block header: " << e.what() << std::endl; + } catch (const std::exception& e) { + MERROR("Error calling listener on new block header: " << e.what()); + } } } -} +}; + +/** + * Sends a notification on a condition variable when a block is added to blockchain. + */ +class block_notifier : public monero_daemon_listener { +public: + block_notifier(boost::mutex* temp, boost::condition_variable* cv, bool* ready) { this->temp = temp; this->cv = cv; this->ready = ready; } + + void on_block_header(const std::shared_ptr& header) override { + boost::mutex::scoped_lock lock(*temp); + m_last_header = header; + *ready = true; + cv->notify_one(); + } -PyMoneroDaemonRpc::PyMoneroDaemonRpc(const std::shared_ptr& rpc) { - m_rpc = rpc; +private: + boost::mutex* temp; + boost::condition_variable* cv; + bool* ready; +}; + +monero_daemon_rpc::monero_daemon_rpc(const std::shared_ptr& rpc): m_rpc(rpc) { if (!rpc->is_online() && rpc->m_uri != boost::none) rpc->check_connection(); } -PyMoneroDaemonRpc::PyMoneroDaemonRpc(const std::string& uri, const std::string& username, const std::string& password, const std::string& proxy_uri, const std::string& zmq_uri, uint64_t timeout) { - m_rpc = std::make_shared(uri, username, password, proxy_uri, zmq_uri, 0, timeout); +monero_daemon_rpc::monero_daemon_rpc(const std::string& uri, const std::string& username, const std::string& password, const std::string& proxy_uri, const std::string& zmq_uri, uint64_t timeout): + m_rpc(std::make_shared(uri, username, password, proxy_uri, zmq_uri, 0, timeout)) { if (!uri.empty()) m_rpc->check_connection(); } -std::vector> PyMoneroDaemonRpc::get_listeners() { +std::vector> monero_daemon_rpc::get_listeners() { boost::lock_guard lock(m_listeners_mutex); return m_listeners; } -void PyMoneroDaemonRpc::add_listener(const std::shared_ptr &listener) { +void monero_daemon_rpc::add_listener(const std::shared_ptr &listener) { boost::lock_guard lock(m_listeners_mutex); m_listeners.push_back(listener); refresh_listening(); } -void PyMoneroDaemonRpc::remove_listener(const std::shared_ptr &listener) { +void monero_daemon_rpc::remove_listener(const std::shared_ptr &listener) { boost::lock_guard lock(m_listeners_mutex); - m_listeners.erase(std::remove_if(m_listeners.begin(), m_listeners.end(), [&listener](std::shared_ptr iter){ return iter == listener; }), m_listeners.end()); + m_listeners.erase(std::remove_if(m_listeners.begin(), m_listeners.end(), [&listener](std::shared_ptr iter){ return iter == listener; }), m_listeners.end()); refresh_listening(); } -void PyMoneroDaemonRpc::remove_listeners() { +void monero_daemon_rpc::remove_listeners() { boost::lock_guard lock(m_listeners_mutex); m_listeners.clear(); refresh_listening(); } -std::shared_ptr PyMoneroDaemonRpc::get_rpc_connection() const { +std::shared_ptr monero_daemon_rpc::get_rpc_connection() const { + MTRACE("monero_daemon_rpc::get_rpc_connection()"); return m_rpc; } -bool PyMoneroDaemonRpc::is_connected() { +bool monero_daemon_rpc::is_connected() { try { get_version(); return true; @@ -82,90 +165,92 @@ bool PyMoneroDaemonRpc::is_connected() { } } -monero::monero_version PyMoneroDaemonRpc::get_version() { +monero::monero_version monero_daemon_rpc::get_version() { auto res = m_rpc->send_json_request("get_version"); + check_response_status(res); std::shared_ptr info = std::make_shared(); PyMoneroVersion::from_property_tree(res, info); return *info; } -bool PyMoneroDaemonRpc::is_trusted() { +bool monero_daemon_rpc::is_trusted() { auto res = m_rpc->send_path_request("get_height"); - auto get_height_response = std::make_shared(); - PyMoneroGetHeightResponse::from_property_tree(res, get_height_response); + check_response_status(res); + auto get_height_response = std::make_shared(); + monero_get_height_response::from_property_tree(res, get_height_response); return !get_height_response->m_untrusted.get(); } -uint64_t PyMoneroDaemonRpc::get_height() { +uint64_t monero_daemon_rpc::get_height() { auto res = m_rpc->send_json_request("get_block_count"); - std::shared_ptr result = std::make_shared(); - PyMoneroGetBlockCountResult::from_property_tree(res, result); + check_response_status(res); + std::shared_ptr result = std::make_shared(); + monero_get_block_count_result::from_property_tree(res, result); if (result->m_count == boost::none) throw std::runtime_error("Could not get height"); return result->m_count.get(); } -std::string PyMoneroDaemonRpc::get_block_hash(uint64_t height) { - std::shared_ptr params = std::make_shared(height); +std::string monero_daemon_rpc::get_block_hash(uint64_t height) { + std::shared_ptr params = std::make_shared(height); auto res = m_rpc->send_json_request("on_get_block_hash", params); return res.data(); } -std::shared_ptr PyMoneroDaemonRpc::get_block_template(const std::string& wallet_address, int reserve_size) { - auto params = std::make_shared(wallet_address, reserve_size); +std::shared_ptr monero_daemon_rpc::get_block_template(const std::string& wallet_address, const boost::optional& reserve_size) { + MTRACE("monero_daemon_rpc::get_block_template()"); + auto params = std::make_shared(wallet_address, reserve_size); auto res = m_rpc->send_json_request("get_block_template", params); - std::shared_ptr tmplt = std::make_shared(); - PyMoneroBlockTemplate::from_property_tree(res, tmplt); - return tmplt; -} - -std::shared_ptr PyMoneroDaemonRpc::get_block_template(const std::string& wallet_address) { - auto params = std::make_shared(wallet_address); - auto res = m_rpc->send_json_request("get_block_template", params); - std::shared_ptr tmplt = std::make_shared(); - PyMoneroBlockTemplate::from_property_tree(res, tmplt); + check_response_status(res); + std::shared_ptr tmplt = std::make_shared(); + monero_block_template::from_property_tree(res, tmplt); return tmplt; } -std::shared_ptr PyMoneroDaemonRpc::get_last_block_header() { +std::shared_ptr monero_daemon_rpc::get_last_block_header() { auto res = m_rpc->send_json_request("get_last_block_header"); + check_response_status(res); std::shared_ptr header = std::make_shared(); PyMoneroBlockHeader::from_property_tree(res, header); return header; } -std::shared_ptr PyMoneroDaemonRpc::get_block_header_by_hash(const std::string& hash) { - std::shared_ptr params = std::make_shared(hash); +std::shared_ptr monero_daemon_rpc::get_block_header_by_hash(const std::string& hash) { + std::shared_ptr params = std::make_shared(hash); auto res = m_rpc->send_json_request("get_block_header_by_hash", params); + check_response_status(res); std::shared_ptr header = std::make_shared(); PyMoneroBlockHeader::from_property_tree(res, header); return header; } -std::shared_ptr PyMoneroDaemonRpc::get_block_header_by_height(uint64_t height) { - std::shared_ptr params = std::make_shared(height); +std::shared_ptr monero_daemon_rpc::get_block_header_by_height(uint64_t height) { + std::shared_ptr params = std::make_shared(height); auto res = m_rpc->send_json_request("get_block_header_by_height", params); + check_response_status(res); std::shared_ptr header = std::make_shared(); PyMoneroBlockHeader::from_property_tree(res, header); return header; } -std::vector> PyMoneroDaemonRpc::get_block_headers_by_range(uint64_t start_height, uint64_t end_height) { - auto params = std::make_shared(start_height, end_height); +std::vector> monero_daemon_rpc::get_block_headers_by_range(uint64_t start_height, uint64_t end_height) { + auto params = std::make_shared(start_height, end_height); auto res = m_rpc->send_json_request("get_block_headers_range", params); + check_response_status(res); std::vector> headers; PyMoneroBlockHeader::from_property_tree(res, headers); return headers; } -std::shared_ptr PyMoneroDaemonRpc::get_block_by_hash(const std::string& hash) { - std::shared_ptr params = std::make_shared(hash); +std::shared_ptr monero_daemon_rpc::get_block_by_hash(const std::string& hash) { + std::shared_ptr params = std::make_shared(hash); auto res = m_rpc->send_json_request("get_block", params); + check_response_status(res); auto block = std::make_shared(); PyMoneroBlock::from_property_tree(res, block); return block; } -std::shared_ptr PyMoneroDaemonRpc::get_block_header_by_height_cached(uint64_t height, uint64_t max_height) { +std::shared_ptr monero_daemon_rpc::get_block_header_by_height_cached(uint64_t height, uint64_t max_height) { // get header from cache auto found = m_cached_headers.find(height); if (found != m_cached_headers.end()) return found->second; @@ -181,21 +266,22 @@ std::shared_ptr PyMoneroDaemonRpc::get_block_header return m_cached_headers[height]; } -std::vector> PyMoneroDaemonRpc::get_blocks_by_hash(const std::vector& block_hashes, uint64_t start_height, bool prune) { - throw std::runtime_error("PyMoneroDaemonRpc::get_blocks_by_hash(): not implemented"); +std::vector> monero_daemon_rpc::get_blocks_by_hash(const std::vector& block_hashes, uint64_t start_height, bool prune) { + throw std::runtime_error("monero_daemon_rpc::get_blocks_by_hash(): not implemented"); } -std::shared_ptr PyMoneroDaemonRpc::get_block_by_height(uint64_t height) { - std::shared_ptr params = std::make_shared(height); +std::shared_ptr monero_daemon_rpc::get_block_by_height(uint64_t height) { + std::shared_ptr params = std::make_shared(height); auto res = m_rpc->send_json_request("get_block", params); + check_response_status(res); auto block = std::make_shared(); PyMoneroBlock::from_property_tree(res, block); return block; } -std::vector> PyMoneroDaemonRpc::get_blocks_by_height(const std::vector& heights) { +std::vector> monero_daemon_rpc::get_blocks_by_height(const std::vector& heights) { // fetch blocks in binary - PyMoneroGetBlocksByHeightRequest request(heights); + monero_get_blocks_by_height_request request(heights); auto response = m_rpc->send_binary_request(request); if (response->m_binary == boost::none) throw std::runtime_error("Invalid Monero Binary response"); boost::property_tree::ptree node; @@ -206,7 +292,7 @@ std::vector> PyMoneroDaemonRpc::get_blocks return blocks; } -std::vector> PyMoneroDaemonRpc::get_blocks_by_range(boost::optional start_height, boost::optional end_height) { +std::vector> monero_daemon_rpc::get_blocks_by_range(boost::optional start_height, boost::optional end_height) { if (start_height == boost::none) { start_height = 0; } @@ -220,7 +306,7 @@ std::vector> PyMoneroDaemonRpc::get_blocks return get_blocks_by_height(heights); } -std::vector> PyMoneroDaemonRpc::get_blocks_by_range_chunked(boost::optional start_height, boost::optional end_height, boost::optional max_chunk_size) { +std::vector> monero_daemon_rpc::get_blocks_by_range_chunked(boost::optional start_height, boost::optional end_height, boost::optional max_chunk_size) { if (start_height == boost::none) start_height = 0; if (end_height == boost::none) end_height = get_height() - 1; uint64_t from_height = start_height.get(); @@ -240,7 +326,7 @@ std::vector> PyMoneroDaemonRpc::get_blocks return blocks; } -std::vector> PyMoneroDaemonRpc::get_max_blocks(boost::optional start_height, boost::optional max_height, boost::optional chunk_size) { +std::vector> monero_daemon_rpc::get_max_blocks(boost::optional start_height, boost::optional max_height, boost::optional chunk_size) { if (start_height == boost::none) start_height = 0; if (max_height == boost::none) max_height = get_height() - 1; if (chunk_size == boost::none) chunk_size = MAX_REQ_SIZE; @@ -278,13 +364,14 @@ std::vector> PyMoneroDaemonRpc::get_max_bl return std::vector>(); } -std::vector PyMoneroDaemonRpc::get_block_hashes(const std::vector& block_hashes, uint64_t start_height) { - throw std::runtime_error("PyMoneroDaemonRpc::get_block_hashes(): not implemented"); +std::vector monero_daemon_rpc::get_block_hashes(const std::vector& block_hashes, uint64_t start_height) { + throw std::runtime_error("monero_daemon_rpc::get_block_hashes(): not implemented"); } -std::vector> PyMoneroDaemonRpc::get_txs(const std::vector& tx_hashes, bool prune) { +std::vector> monero_daemon_rpc::get_txs(const std::vector& tx_hashes, bool prune) { + MTRACE("monero_daemon_rpc::get_txs()"); if (tx_hashes.empty()) throw std::runtime_error("Must provide an array of transaction hashes"); - auto params = std::make_shared(tx_hashes, prune); + auto params = std::make_shared(tx_hashes, prune); auto res = m_rpc->send_path_request("get_transactions", params); try { check_response_status(res); } catch (const std::exception& ex) { @@ -298,7 +385,8 @@ std::vector> PyMoneroDaemonRpc::get_txs(const return txs; } -std::vector PyMoneroDaemonRpc::get_tx_hexes(const std::vector& tx_hashes, bool prune) { +std::vector monero_daemon_rpc::get_tx_hexes(const std::vector& tx_hashes, bool prune) { + MTRACE("monero_daemon_rpc::get_tx_hexes()"); std::vector hexes; for(const auto& tx : get_txs(tx_hashes, prune)) { // tx may be pruned regardless of configuration @@ -312,27 +400,30 @@ std::vector PyMoneroDaemonRpc::get_tx_hexes(const std::vector PyMoneroDaemonRpc::get_miner_tx_sum(uint64_t height, uint64_t num_blocks) { - auto params = std::make_shared(height, num_blocks); +std::shared_ptr monero_daemon_rpc::get_miner_tx_sum(uint64_t height, uint64_t num_blocks) { + auto params = std::make_shared(height, num_blocks); auto res = m_rpc->send_json_request("get_coinbase_tx_sum", params); - auto sum = std::make_shared(); - PyMoneroMinerTxSum::from_property_tree(res, sum); + check_response_status(res); + auto sum = std::make_shared(); + monero_miner_tx_sum::from_property_tree(res, sum); return sum; } -std::shared_ptr PyMoneroDaemonRpc::get_fee_estimate(uint64_t grace_blocks) { - auto params = std::make_shared(grace_blocks); +std::shared_ptr monero_daemon_rpc::get_fee_estimate(uint64_t grace_blocks) { + auto params = std::make_shared(grace_blocks); auto res = m_rpc->send_json_request("get_fee_estimate", params); - auto estimate = std::make_shared(); - PyMoneroFeeEstimate::from_property_tree(res, estimate); + check_response_status(res); + auto estimate = std::make_shared(); + monero_fee_estimate::from_property_tree(res, estimate); return estimate; } -std::shared_ptr PyMoneroDaemonRpc::submit_tx_hex(const std::string& tx_hex, bool do_not_relay) { - auto params = std::make_shared(tx_hex, do_not_relay); +std::shared_ptr monero_daemon_rpc::submit_tx_hex(const std::string& tx_hex, bool do_not_relay) { + MTRACE("monero_daemon_rpc::submit_tx_hex()"); + auto params = std::make_shared(tx_hex, do_not_relay); auto res = m_rpc->send_path_request("send_raw_transaction", params); - auto sum = std::make_shared(); - PyMoneroSubmitTxResult::from_property_tree(res, sum); + auto sum = std::make_shared(); + monero_submit_tx_result::from_property_tree(res, sum); // set m_is_good based on status try { @@ -344,54 +435,61 @@ std::shared_ptr PyMoneroDaemonRpc::submit_tx_hex(const s return sum; } -void PyMoneroDaemonRpc::relay_txs_by_hash(const std::vector& tx_hashes) { - auto params = std::make_shared(tx_hashes); +void monero_daemon_rpc::relay_txs_by_hash(const std::vector& tx_hashes) { + MTRACE("monero_daemon_rpc::relay_txs_by_hash()"); + auto params = std::make_shared(tx_hashes); auto res = m_rpc->send_json_request("relay_tx", params); check_response_status(res); } -std::shared_ptr PyMoneroDaemonRpc::get_tx_pool_stats() { +std::shared_ptr monero_daemon_rpc::get_tx_pool_stats() { auto res = m_rpc->send_path_request("get_transaction_pool_stats"); - auto stats = std::make_shared(); - PyMoneroTxPoolStats::from_property_tree(res, stats); + check_response_status(res); + auto stats = std::make_shared(); + monero_tx_pool_stats::from_property_tree(res, stats); return stats; } -std::vector> PyMoneroDaemonRpc::get_tx_pool() { +std::vector> monero_daemon_rpc::get_tx_pool() { auto res = m_rpc->send_path_request("get_transaction_pool"); + check_response_status(res); std::vector> pool; PyMoneroTx::from_property_tree(res, pool); return pool; } -std::vector PyMoneroDaemonRpc::get_tx_pool_hashes() { +std::vector monero_daemon_rpc::get_tx_pool_hashes() { auto res = m_rpc->send_path_request("get_transaction_pool_hashes"); - return PyMoneroTxHashes::from_property_tree(res); + check_response_status(res); + std::vector tx_hashes; + PyMoneroTx::from_property_tree(res, tx_hashes); + return tx_hashes; } -void PyMoneroDaemonRpc::flush_tx_pool(const std::vector &hashes) { - auto params = std::make_shared(hashes); +void monero_daemon_rpc::flush_tx_pool(const std::vector &hashes) { + MTRACE("monero_daemon_rpc::flush_tx_pool()"); + auto params = std::make_shared(hashes); auto res = m_rpc->send_json_request("flush_txpool", params); check_response_status(res); } -void PyMoneroDaemonRpc::flush_tx_pool() { +void monero_daemon_rpc::flush_tx_pool() { std::vector hashes; flush_tx_pool(hashes); } -void PyMoneroDaemonRpc::flush_tx_pool(const std::string &hash) { +void monero_daemon_rpc::flush_tx_pool(const std::string &hash) { std::vector hashes; hashes.push_back(hash); flush_tx_pool(hashes); } -std::vector PyMoneroDaemonRpc::get_key_image_spent_statuses(const std::vector& key_images) { +std::vector monero_daemon_rpc::get_key_image_spent_statuses(const std::vector& key_images) { if (key_images.empty()) throw std::runtime_error("Must provide key images to check the status of"); - auto params = std::make_shared(key_images); + auto params = std::make_shared(key_images); auto res = m_rpc->send_path_request("is_key_image_spent", params); check_response_status(res); - std::vector statuses; + std::vector statuses; for (auto it = res.begin(); it != res.end(); ++it) { std::string key = it->first; if (key == std::string("spent_status")) { @@ -399,13 +497,13 @@ std::vector PyMoneroDaemonRpc::get_key_image_spent_ for (auto it2 = spent_status_node.begin(); it2 != spent_status_node.end(); ++it2) { auto value = it2->second.get_value(); if (value == 0) { - statuses.push_back(PyMoneroKeyImageSpentStatus::NOT_SPENT); + statuses.push_back(monero_key_image_spent_status::NOT_SPENT); } else if (value == 1) { - statuses.push_back(PyMoneroKeyImageSpentStatus::CONFIRMED); + statuses.push_back(monero_key_image_spent_status::CONFIRMED); } else if (value == 2) { - statuses.push_back(PyMoneroKeyImageSpentStatus::TX_POOL); + statuses.push_back(monero_key_image_spent_status::TX_POOL); } } } @@ -413,51 +511,57 @@ std::vector PyMoneroDaemonRpc::get_key_image_spent_ return statuses; } -std::vector> PyMoneroDaemonRpc::get_outputs(const std::vector& outputs) { - throw std::runtime_error("PyMoneroDaemonRpc::get_outputs(): not implemented"); +std::vector> monero_daemon_rpc::get_outputs(const std::vector& outputs) { + throw std::runtime_error("monero_daemon_rpc::get_outputs(): not implemented"); } -std::vector> PyMoneroDaemonRpc::get_output_histogram(const std::vector& amounts, const boost::optional& min_count, const boost::optional& max_count, const boost::optional& is_unlocked, const boost::optional& recent_cutoff) { - auto params = std::make_shared(amounts, min_count, max_count, is_unlocked, recent_cutoff); +std::vector> monero_daemon_rpc::get_output_histogram(const std::vector& amounts, const boost::optional& min_count, const boost::optional& max_count, const boost::optional& is_unlocked, const boost::optional& recent_cutoff) { + MTRACE("monero_daemon_rpc::get_output_histogram()"); + + auto params = std::make_shared(amounts, min_count, max_count, is_unlocked, recent_cutoff); auto res = m_rpc->send_json_request("get_output_histogram", params); - std::vector> entries; - PyMoneroOutputHistogramEntry::from_property_tree(res, entries); + check_response_status(res); + std::vector> entries; + monero_output_histogram_entry::from_property_tree(res, entries); return entries; } -std::shared_ptr PyMoneroDaemonRpc::get_info() { +std::shared_ptr monero_daemon_rpc::get_info() { auto res = m_rpc->send_json_request("get_info"); - std::shared_ptr info = std::make_shared(); - PyMoneroDaemonInfo::from_property_tree(res, info); + check_response_status(res); + std::shared_ptr info = std::make_shared(); + monero_daemon_info::from_property_tree(res, info); return info; } -std::shared_ptr PyMoneroDaemonRpc::get_sync_info() { +std::shared_ptr monero_daemon_rpc::get_sync_info() { auto res = m_rpc->send_json_request("sync_info"); - std::shared_ptr info = std::make_shared(); - PyMoneroDaemonSyncInfo::from_property_tree(res, info); + check_response_status(res); + std::shared_ptr info = std::make_shared(); + monero_daemon_sync_info::from_property_tree(res, info); return info; } -std::shared_ptr PyMoneroDaemonRpc::get_hard_fork_info() { +std::shared_ptr monero_daemon_rpc::get_hard_fork_info() { auto res = m_rpc->send_json_request("hard_fork_info"); - std::shared_ptr info = std::make_shared(); - PyMoneroHardForkInfo::from_property_tree(res, info); + check_response_status(res); + std::shared_ptr info = std::make_shared(); + monero_hard_fork_info::from_property_tree(res, info); return info; } -std::vector> PyMoneroDaemonRpc::get_alt_chains() { - std::vector> result; +std::vector> monero_daemon_rpc::get_alt_chains() { + std::vector> result; auto res = m_rpc->send_json_request("get_alternate_chains"); - + check_response_status(res); for (boost::property_tree::ptree::const_iterator it = res.begin(); it != res.end(); ++it) { std::string key = it->first; if (key == std::string("chains")) { boost::property_tree::ptree chains = it->second; for (boost::property_tree::ptree::const_iterator it2 = chains.begin(); it2 != chains.end(); ++it2) { - std::shared_ptr alt_chain = std::make_shared(); - PyMoneroAltChain::from_property_tree(it2->second, alt_chain); + std::shared_ptr alt_chain = std::make_shared(); + monero_alt_chain::from_property_tree(it2->second, alt_chain); result.push_back(alt_chain); } } @@ -466,20 +570,23 @@ std::vector> PyMoneroDaemonRpc::get_alt_chains return result; } -std::vector PyMoneroDaemonRpc::get_alt_block_hashes() { +std::vector monero_daemon_rpc::get_alt_block_hashes() { auto res = m_rpc->send_path_request("get_alt_blocks_hashes"); + check_response_status(res); std::vector hashes; - PyMoneroGetAltBlocksHashesResponse::from_property_tree(res, hashes); + monero_get_alt_block_hashes_response::from_property_tree(res, hashes); return hashes; } -int PyMoneroDaemonRpc::get_download_limit() { +int monero_daemon_rpc::get_download_limit() { + MTRACE("monero_daemon_rpc::get_download_limit()"); auto limits = get_bandwidth_limits(); if (limits->m_down != boost::none) return limits->m_down.get(); throw std::runtime_error("Could not get download limit"); } -int PyMoneroDaemonRpc::set_download_limit(int limit) { +int monero_daemon_rpc::set_download_limit(int limit) { + MTRACE("monero_daemon_rpc::set_download_limit()"); if (limit == -1) return reset_download_limit(); if (limit <= 0) throw std::runtime_error("Download limit must be an integer greater than 0"); auto res = set_bandwidth_limits(0, limit); @@ -487,19 +594,22 @@ int PyMoneroDaemonRpc::set_download_limit(int limit) { throw std::runtime_error("Could not set download limit"); } -int PyMoneroDaemonRpc::reset_download_limit() { +int monero_daemon_rpc::reset_download_limit() { + MTRACE("monero_daemon_rpc::reset_download_limit()"); auto res = set_bandwidth_limits(0, -1); if (res->m_down != boost::none) return res->m_down.get(); throw std::runtime_error("Could not set download limit"); } -int PyMoneroDaemonRpc::get_upload_limit() { +int monero_daemon_rpc::get_upload_limit() { + MTRACE("monero_daemon_rpc::get_upload_limit()"); auto limits = get_bandwidth_limits(); if (limits->m_up != boost::none) return limits->m_up.get(); throw std::runtime_error("Could not get upload limit"); } -int PyMoneroDaemonRpc::set_upload_limit(int limit) { +int monero_daemon_rpc::set_upload_limit(int limit) { + MTRACE("monero_daemon_rpc::set_upload_limit()"); if (limit == -1) return reset_upload_limit(); if (limit <= 0) throw std::runtime_error("Upload limit must be an integer greater than 0"); auto res = set_bandwidth_limits(limit, 0); @@ -507,122 +617,141 @@ int PyMoneroDaemonRpc::set_upload_limit(int limit) { throw std::runtime_error("Could not set download limit"); } -int PyMoneroDaemonRpc::reset_upload_limit() { +int monero_daemon_rpc::reset_upload_limit() { + MTRACE("monero_daemon_rpc::reset_upload_limit()"); auto res = set_bandwidth_limits(-1, 0); if (res->m_up != boost::none) return res->m_up.get(); throw std::runtime_error("Could not set download limit"); } -std::vector> PyMoneroDaemonRpc::get_peers() { +std::vector> monero_daemon_rpc::get_peers() { auto res = m_rpc->send_json_request("get_connections"); - std::vector> peers; - PyMoneroPeer::from_property_tree(res, peers); + check_response_status(res); + std::vector> peers; + monero_peer::from_property_tree(res, peers); return peers; } -std::vector> PyMoneroDaemonRpc::get_known_peers() { +std::vector> monero_daemon_rpc::get_known_peers() { auto res = m_rpc->send_path_request("get_peer_list"); - std::vector> peers; - PyMoneroPeer::from_property_tree(res, peers); + check_response_status(res); + std::vector> peers; + monero_peer::from_property_tree(res, peers); return peers; } -void PyMoneroDaemonRpc::set_outgoing_peer_limit(int limit) { +void monero_daemon_rpc::set_outgoing_peer_limit(int limit) { + MTRACE("monero_daemon_rpc::set_outgoing_peer_limit()"); if (limit < 0) throw std::runtime_error("Outgoing peer limit must be >= 0"); - auto params = std::make_shared(); + auto params = std::make_shared(); params->m_out_peers = limit; auto res = m_rpc->send_path_request("out_peers", params); check_response_status(res); } -void PyMoneroDaemonRpc::set_incoming_peer_limit(int limit) { +void monero_daemon_rpc::set_incoming_peer_limit(int limit) { + MTRACE("monero_daemon_rpc::set_incoming_peer_limit()"); if (limit < 0) throw std::runtime_error("Incoming peer limit must be >= 0"); - auto params = std::make_shared(); + auto params = std::make_shared(); params->m_in_peers = limit; auto res = m_rpc->send_path_request("in_peers", params); check_response_status(res); } -std::vector> PyMoneroDaemonRpc::get_peer_bans() { +std::vector> monero_daemon_rpc::get_peer_bans() { + MTRACE("monero_daemon_rpc::get_peer_bans()"); auto res = m_rpc->send_json_request("get_bans"); - std::vector> bans; - PyMoneroBan::from_property_tree(res, bans); + check_response_status(res); + std::vector> bans; + monero_ban::from_property_tree(res, bans); return bans; } -void PyMoneroDaemonRpc::set_peer_bans(const std::vector>& bans) { - auto params = std::make_shared(bans); +void monero_daemon_rpc::set_peer_bans(const std::vector>& bans) { + MTRACE("monero_daemon_rpc::set_peer_bans()"); + auto params = std::make_shared(bans); auto res = m_rpc->send_json_request("set_bans", params); check_response_status(res); } -void PyMoneroDaemonRpc::start_mining(const std::string &address, int num_threads, bool is_background, bool ignore_battery) { +void monero_daemon_rpc::start_mining(const std::string &address, int num_threads, bool is_background, bool ignore_battery) { + MTRACE("monero_daemon_rpc::start_mining()"); if (address.empty()) throw std::runtime_error("Must provide address to mine to"); if (num_threads <= 0) throw std::runtime_error("Number of threads must be an integer greater than 0"); - auto params = std::make_shared(address, num_threads, is_background, ignore_battery); + auto params = std::make_shared(address, num_threads, is_background, ignore_battery); auto res = m_rpc->send_path_request("start_mining", params); check_response_status(res); } -void PyMoneroDaemonRpc::stop_mining() { +void monero_daemon_rpc::stop_mining() { + MTRACE("monero_daemon_rpc::stop_mining()"); auto res = m_rpc->send_path_request("stop_mining"); check_response_status(res); } -std::shared_ptr PyMoneroDaemonRpc::get_mining_status() { +std::shared_ptr monero_daemon_rpc::get_mining_status() { + MTRACE("monero_daemon_rpc::get_mining_status()"); auto res = m_rpc->send_path_request("mining_status"); check_response_status(res); - auto result = std::make_shared(); - PyMoneroMiningStatus::from_property_tree(res, result); + auto result = std::make_shared(); + monero_mining_status::from_property_tree(res, result); return result; } -void PyMoneroDaemonRpc::submit_blocks(const std::vector& block_blobs) { +void monero_daemon_rpc::submit_blocks(const std::vector& block_blobs) { + MTRACE("monero_daemon_rpc::submit_blocks()"); if (block_blobs.empty()) throw std::runtime_error("Must provide an array of mined block blobs to submit"); - auto params = std::make_shared(block_blobs); + auto params = std::make_shared(block_blobs); auto res = m_rpc->send_json_request("submit_block", params); check_response_status(res); } -std::shared_ptr PyMoneroDaemonRpc::prune_blockchain(bool check) { - auto params = std::make_shared(check); +std::shared_ptr monero_daemon_rpc::prune_blockchain(bool check) { + MTRACE("monero_daemon_rpc::prune_blockchain()"); + auto params = std::make_shared(check); auto res = m_rpc->send_json_request("prune_blockchain", params); - std::shared_ptr result = std::make_shared(); - PyMoneroPruneResult::from_property_tree(res, result); + check_response_status(res); + std::shared_ptr result = std::make_shared(); + monero_prune_result::from_property_tree(res, result); return result; } -std::shared_ptr PyMoneroDaemonRpc::check_for_update() { - auto params = std::make_shared("check"); +std::shared_ptr monero_daemon_rpc::check_for_update() { + MTRACE("monero_daemon_rpc::check_for_update()"); + auto params = std::make_shared("check"); auto res = m_rpc->send_path_request("update", params); check_response_status(res); - auto result = std::make_shared(); - PyMoneroDaemonUpdateCheckResult::from_property_tree(res, result); + auto result = std::make_shared(); + monero_daemon_update_check_result::from_property_tree(res, result); return result; } -std::shared_ptr PyMoneroDaemonRpc::download_update(const std::string& path) { - auto params = std::make_shared("download", path); +std::shared_ptr monero_daemon_rpc::download_update(const std::string& path) { + MTRACE("monero_daemon_rpc::download_update()"); + auto params = std::make_shared("download", path); auto res = m_rpc->send_path_request("update", params); check_response_status(res); - auto result = std::make_shared(); - PyMoneroDaemonUpdateDownloadResult::from_property_tree(res, result); + auto result = std::make_shared(); + monero_daemon_update_download_result::from_property_tree(res, result); return result; } -void PyMoneroDaemonRpc::stop() { +void monero_daemon_rpc::stop() { + MTRACE("monero_daemon_rpc::stop()"); auto res = m_rpc->send_path_request("stop_daemon"); check_response_status(res); } -std::shared_ptr PyMoneroDaemonRpc::wait_for_next_block_header() { +std::shared_ptr monero_daemon_rpc::wait_for_next_block_header() { + MTRACE("monero_daemon_rpc::wait_for_next_block_header()"); + // use mutex and condition variable with predicate to wait for block boost::mutex temp; boost::condition_variable cv; bool ready = false; // create listener which notifies condition variable when block is added - auto block_listener = std::make_shared(&temp, &cv, &ready); + auto block_listener = std::make_shared(&temp, &cv, &ready); // register the listener add_listener(block_listener); @@ -638,31 +767,33 @@ std::shared_ptr PyMoneroDaemonRpc::wait_for_next_bl return block_listener->m_last_header; } -std::shared_ptr PyMoneroDaemonRpc::get_bandwidth_limits() { +std::shared_ptr monero_daemon_rpc::get_bandwidth_limits() { + MTRACE("monero_daemon_rpc::get_bandwidth_limits()"); auto res = m_rpc->send_path_request("get_limit"); check_response_status(res); - auto limits = std::make_shared(); - PyMoneroBandwithLimits::from_property_tree(res, limits); + auto limits = std::make_shared(); + monero_bandwith_limits_params::from_property_tree(res, limits); return limits; } -std::shared_ptr PyMoneroDaemonRpc::set_bandwidth_limits(int up, int down) { - auto limits = std::make_shared(up, down); +std::shared_ptr monero_daemon_rpc::set_bandwidth_limits(int up, int down) { + MTRACE("monero_daemon_rpc::set_bandwidth_limits()"); + auto limits = std::make_shared(up, down); auto res = m_rpc->send_path_request("set_limit", limits); check_response_status(res); - PyMoneroBandwithLimits::from_property_tree(res, limits); + monero_bandwith_limits_params::from_property_tree(res, limits); return limits; } -void PyMoneroDaemonRpc::refresh_listening() { +void monero_daemon_rpc::refresh_listening() { boost::lock_guard lock(m_listeners_mutex); if (!m_poller && m_listeners.size() > 0) { - m_poller = std::make_shared(this); + m_poller = std::make_shared(this); } if (m_poller) m_poller->set_is_polling(m_listeners.size() > 0); } -void PyMoneroDaemonRpc::check_response_status(const boost::property_tree::ptree& node) { +void monero_daemon_rpc::check_response_status(const boost::property_tree::ptree& node) { for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; if (key == std::string("status")) { @@ -672,25 +803,14 @@ void PyMoneroDaemonRpc::check_response_status(const boost::property_tree::ptree& if (status == std::string("OK") || status == std::string("")) { return; } - else throw PyMoneroRpcError(status); + else throw monero_rpc_error(status); } } throw std::runtime_error("Could not get JSON RPC response status"); } -void PyMoneroDaemonRpc::check_response_status(const std::shared_ptr& response) { - if (response->m_response == boost::none) throw std::runtime_error("Invalid Monero RPC response"); - auto node = response->m_response.get(); - check_response_status(node); -} - -void PyMoneroDaemonRpc::check_response_status(const std::shared_ptr& response) { - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSON RPC response"); - auto node = response->m_result.get(); - check_response_status(node); -} - -PyMoneroDaemonRpc::~PyMoneroDaemonRpc() { +monero_daemon_rpc::~monero_daemon_rpc() { + MTRACE("~monero_daemon_rpc()"); remove_listeners(); } diff --git a/src/cpp/daemon/py_monero_daemon_rpc.h b/src/cpp/daemon/py_monero_daemon_rpc.h index 7895c5b..dab9fed 100644 --- a/src/cpp/daemon/py_monero_daemon_rpc.h +++ b/src/cpp/daemon/py_monero_daemon_rpc.h @@ -1,31 +1,81 @@ +/** + * Copyright (c) everoddandeven + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2025-2026 woodser + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ #pragma once #include "py_monero_daemon.h" -class PyMoneroDaemonPoller: public PyThreadPoller { -public: - - PyMoneroDaemonPoller(PyMoneroDaemon* daemon, uint64_t poll_period_ms = 5000); - - void poll() override; - -private: - PyMoneroDaemon* m_daemon; - std::shared_ptr m_last_header; +class monero_daemon_poller; - void announce_block_header(const std::shared_ptr& header); -}; - -class PyMoneroDaemonRpc : public PyMoneroDaemon { +/** + * Implements a Monero daemon using monerod-rpc. + */ +class monero_daemon_rpc : public monero_daemon { public: - ~PyMoneroDaemonRpc(); - PyMoneroDaemonRpc(const std::shared_ptr& rpc); - PyMoneroDaemonRpc(const std::string& uri, const std::string& username = "", const std::string& password = "", const std::string& proxy_uri = "", const std::string& zmq_uri = "", uint64_t timeout = 20000); + /** + * Destruct the daemon. + */ + ~monero_daemon_rpc() override; + monero_daemon_rpc(const std::shared_ptr& rpc); + monero_daemon_rpc(const std::string& uri, const std::string& username = "", const std::string& password = "", const std::string& proxy_uri = "", const std::string& zmq_uri = "", uint64_t timeout = 20000); - std::vector> get_listeners() override; - void add_listener(const std::shared_ptr &listener) override; - void remove_listener(const std::shared_ptr &listener) override; + /** + * Supported daemon methods. + */ + std::vector> get_listeners() override; + void add_listener(const std::shared_ptr &listener) override; + void remove_listener(const std::shared_ptr &listener) override; void remove_listeners() override; std::shared_ptr get_rpc_connection() const; bool is_connected(); @@ -33,8 +83,7 @@ class PyMoneroDaemonRpc : public PyMoneroDaemon { bool is_trusted() override; uint64_t get_height() override; std::string get_block_hash(uint64_t height) override; - std::shared_ptr get_block_template(const std::string& wallet_address, int reserve_size) override; - std::shared_ptr get_block_template(const std::string& wallet_address) override; + std::shared_ptr get_block_template(const std::string& wallet_address, const boost::optional& reserve_size = boost::none) override; std::shared_ptr get_last_block_header() override; std::shared_ptr get_block_header_by_hash(const std::string& hash) override; std::shared_ptr get_block_header_by_height(uint64_t height) override; @@ -48,23 +97,23 @@ class PyMoneroDaemonRpc : public PyMoneroDaemon { std::vector get_block_hashes(const std::vector& block_hashes, uint64_t start_height) override; std::vector> get_txs(const std::vector& tx_hashes, bool prune = false) override; std::vector get_tx_hexes(const std::vector& tx_hashes, bool prune = false) override; - std::shared_ptr get_miner_tx_sum(uint64_t height, uint64_t num_blocks) override; - std::shared_ptr get_fee_estimate(uint64_t grace_blocks = 0) override; - std::shared_ptr submit_tx_hex(const std::string& tx_hex, bool do_not_relay = false) override; + std::shared_ptr get_miner_tx_sum(uint64_t height, uint64_t num_blocks) override; + std::shared_ptr get_fee_estimate(uint64_t grace_blocks = 0) override; + std::shared_ptr submit_tx_hex(const std::string& tx_hex, bool do_not_relay = false) override; void relay_txs_by_hash(const std::vector& tx_hashes) override; - std::shared_ptr get_tx_pool_stats() override; + std::shared_ptr get_tx_pool_stats() override; std::vector> get_tx_pool() override; std::vector get_tx_pool_hashes() override; void flush_tx_pool(const std::vector &hashes) override; void flush_tx_pool() override; void flush_tx_pool(const std::string &hash) override; - std::vector get_key_image_spent_statuses(const std::vector& key_images) override; + std::vector get_key_image_spent_statuses(const std::vector& key_images) override; std::vector> get_outputs(const std::vector& outputs) override; - std::vector> get_output_histogram(const std::vector& amounts, const boost::optional& min_count, const boost::optional& max_count, const boost::optional& is_unlocked, const boost::optional& recent_cutoff) override; - std::shared_ptr get_info() override; - std::shared_ptr get_sync_info() override; - std::shared_ptr get_hard_fork_info() override; - std::vector> get_alt_chains() override; + std::vector> get_output_histogram(const std::vector& amounts, const boost::optional& min_count, const boost::optional& max_count, const boost::optional& is_unlocked, const boost::optional& recent_cutoff) override; + std::shared_ptr get_info() override; + std::shared_ptr get_sync_info() override; + std::shared_ptr get_hard_fork_info() override; + std::vector> get_alt_chains() override; std::vector get_alt_block_hashes() override; int get_download_limit() override; int set_download_limit(int limit) override; @@ -72,35 +121,36 @@ class PyMoneroDaemonRpc : public PyMoneroDaemon { int get_upload_limit() override; int set_upload_limit(int limit) override; int reset_upload_limit() override; - std::vector> get_peers() override; - std::vector> get_known_peers() override; + std::vector> get_peers() override; + std::vector> get_known_peers() override; void set_outgoing_peer_limit(int limit) override; void set_incoming_peer_limit(int limit) override; - std::vector> get_peer_bans() override; - void set_peer_bans(const std::vector>& bans) override; + std::vector> get_peer_bans() override; + void set_peer_bans(const std::vector>& bans) override; void start_mining(const std::string &address, int num_threads, bool is_background, bool ignore_battery) override; void stop_mining() override; - std::shared_ptr get_mining_status() override; + std::shared_ptr get_mining_status() override; void submit_blocks(const std::vector& block_blobs) override; - std::shared_ptr prune_blockchain(bool check) override; - std::shared_ptr check_for_update() override; - std::shared_ptr download_update(const std::string& path = "") override; + std::shared_ptr prune_blockchain(bool check) override; + std::shared_ptr check_for_update() override; + std::shared_ptr download_update(const std::string& path = "") override; void stop() override; std::shared_ptr wait_for_next_block_header() override; - static void check_response_status(const std::shared_ptr& response); - static void check_response_status(const std::shared_ptr& response); -protected: +// --------------------------------- PRIVATE -------------------------------- + +private: + friend class monero_daemon_poller; mutable boost::recursive_mutex m_listeners_mutex; - std::vector> m_listeners; + std::vector> m_listeners; std::shared_ptr m_rpc; - std::shared_ptr m_poller; + std::shared_ptr m_poller; std::unordered_map> m_cached_headers; std::vector> get_max_blocks(boost::optional start_height, boost::optional max_height, boost::optional chunk_size); std::shared_ptr get_block_header_by_height_cached(uint64_t height, uint64_t max_height); - std::shared_ptr get_bandwidth_limits(); - std::shared_ptr set_bandwidth_limits(int up, int down); + std::shared_ptr get_bandwidth_limits(); + std::shared_ptr set_bandwidth_limits(int up, int down); void refresh_listening(); static void check_response_status(const boost::property_tree::ptree& node); }; diff --git a/src/cpp/py_monero.cpp b/src/cpp/py_monero.cpp index 4398ba2..48657d2 100644 --- a/src/cpp/py_monero.cpp +++ b/src/cpp/py_monero.cpp @@ -1,3 +1,56 @@ +/** + * Copyright (c) everoddandeven + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2025-2026 woodser + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ #include "daemon/py_monero_daemon_rpc.h" #include "wallet/py_monero_wallet_rpc.h" #include "wallet/monero_wallet_keys.h" @@ -7,19 +60,20 @@ #define MONERO_CATCH_AND_RETHROW(expr) \ try { \ return expr; \ - } catch (const PyMoneroRpcError& e) { \ + } catch (const monero_rpc_error& e) { \ throw; \ - } catch (const PyMoneroError& e) { \ + } catch (const monero_error& e) { \ throw; \ } \ catch (const std::exception& e) { \ - throw PyMoneroError(e.what()); \ + throw monero_error(e.what()); \ } using VectorInt = std::vector; using VectorUint8 = std::vector; using VectorUint32 = std::vector; using VectorUint64 = std::vector; +using VectorString = std::vector; using VectorMoneroOutgoingTransfer = std::vector>; using VectorMoneroIncomingTransfer = std::vector>; @@ -38,21 +92,22 @@ PYBIND11_MAKE_OPAQUE(VectorUint64); PYBIND11_MODULE(monero, m) { m.doc() = ""; - auto py_serializable_struct = py::class_>(m, "SerializableStruct"); + auto py_serializable_struct = py::class_>(m, "SerializableStruct"); + auto py_monero_rpc_payment_info =py::class_>(m, "MoneroRpcPaymentInfo"); auto py_monero_rpc_connection = py::class_>(m, "MoneroRpcConnection"); - auto py_monero_ssl_options = py::class_(m, "MoneroSslOptions"); + auto py_monero_ssl_options = py::class_(m, "SslOptions"); auto py_monero_version = py::class_>(m, "MoneroVersion"); auto py_monero_block_header = py::class_>(m, "MoneroBlockHeader"); auto py_monero_block = py::class_>(m, "MoneroBlock"); auto py_monero_tx = py::class_>(m, "MoneroTx"); auto py_monero_key_image = py::class_>(m, "MoneroKeyImage"); auto py_monero_output = py::class_>(m, "MoneroOutput"); - auto py_monero_wallet_config = py::class_>(m, "MoneroWalletConfig"); + auto py_monero_wallet_config = py::class_>(m, "MoneroWalletConfig"); auto py_monero_subaddress = py::class_>(m, "MoneroSubaddress"); auto py_monero_sync_result = py::class_>(m, "MoneroSyncResult"); auto py_monero_account = py::class_>(m, "MoneroAccount"); - auto py_monero_account_tag = py::class_>(m, "MoneroAccountTag"); + auto py_monero_account_tag = py::class_>(m, "MoneroAccountTag"); auto py_monero_destination = py::class_>(m, "MoneroDestination"); auto py_monero_transfer = py::class_>(m, "MoneroTransfer"); auto py_monero_incoming_transfer = py::class_>(m, "MoneroIncomingTransfer"); @@ -64,7 +119,7 @@ PYBIND11_MODULE(monero, m) { auto py_monero_tx_query = py::class_>(m, "MoneroTxQuery"); auto py_monero_tx_set = py::class_>(m, "MoneroTxSet"); auto py_monero_integrated_address = py::class_>(m, "MoneroIntegratedAddress"); - auto py_monero_decoded_address = py::class_>(m, "MoneroDecodedAddress"); + auto py_monero_decoded_address = py::class_>(m, "MoneroDecodedAddress"); auto py_monero_tx_config = py::class_>(m, "MoneroTxConfig"); auto py_monero_key_image_import_result = py::class_>(m, "MoneroKeyImageImportResult"); auto py_monero_message_signature_result = py::class_>(m, "MoneroMessageSignatureResult"); @@ -77,41 +132,83 @@ PYBIND11_MODULE(monero, m) { auto py_monero_address_book_entry = py::class_>(m, "MoneroAddressBookEntry"); auto py_monero_wallet_listener = py::class_>(m, "MoneroWalletListener"); auto py_monero_daemon_listener = py::class_>(m, "MoneroDaemonListener"); - auto py_monero_daemon = py::class_>(m, "MoneroDaemon"); - auto py_monero_daemon_rpc = py::class_>(m, "MoneroDaemonRpc"); + auto py_monero_daemon = py::class_>(m, "MoneroDaemon"); + auto py_monero_daemon_rpc = py::class_>(m, "MoneroDaemonRpc"); auto py_monero_wallet = py::class_>(m, "MoneroWallet"); auto py_monero_wallet_keys = py::class_>(m, "MoneroWalletKeys"); auto py_monero_wallet_full = py::class_>(m, "MoneroWalletFull"); - auto py_monero_wallet_rpc = py::class_>(m, "MoneroWalletRpc"); + auto py_monero_wallet_rpc = py::class_>(m, "MoneroWalletRpc"); auto py_monero_utils = py::class_(m, "MoneroUtils"); - auto py_tx_height_comparator = py::class_>(m, "TxHeightComparator"); - auto py_incoming_transfer_comparator = py::class_>(m, "IncomingTransferComparator"); - auto py_output_comparator = py::class_>(m, "OutputComparator"); - - py::bind_vector(m, "VectorInt"); - py::bind_vector(m, "VectorUint8"); - py::bind_vector(m, "VectorUint32"); - py::bind_vector(m, "VectorUint64"); - py::bind_vector>(m, "VectorString"); - py::bind_vector>>(m, "VectorMoneroAccountTagPtr"); - py::bind_vector>>(m, "VectorKeyImagePtr"); - py::bind_vector>>(m, "VectorMoneroBlock"); - py::bind_vector>>(m, "VectorMoneroBlockHeader"); + auto py_tx_height_comparator = py::class_>(m, "TxHeightComparator"); + auto py_incoming_transfer_comparator = py::class_>(m, "IncomingTransferComparator"); + auto py_output_comparator = py::class_>(m, "OutputComparator"); + + py::bind_vector(m, "VectorInt") + .def("copy", [](const VectorInt& v) { + return VectorInt(v); + }); + py::bind_vector(m, "VectorUint8") + .def("copy", [](const VectorUint8& v) { + return VectorUint8(v); + }); + py::bind_vector(m, "VectorUint32") + .def("copy", [](const VectorUint32& v) { + return VectorUint32(v); + }); + py::bind_vector(m, "VectorUint64") + .def("copy", [](const VectorUint64& v) { + return VectorUint64(v); + }); + py::bind_vector(m, "VectorString") + .def("copy", [](const VectorString& v) { + return VectorString(v); + }); + py::bind_vector>>(m, "VectorMoneroAccountTagPtr") + .def("copy", [](const std::vector>& v) { + return std::vector>(v); + }); + py::bind_vector>>(m, "VectorKeyImagePtr") + .def("copy", [](const std::vector>& v) { + return std::vector>(v); + }); + py::bind_vector>>(m, "VectorMoneroBlock") + .def("copy", [](const std::vector>& v) { + return std::vector>(v); + }); + py::bind_vector>>(m, "VectorMoneroBlockHeader") + .def("copy", [](const std::vector>& v) { + return std::vector>(v); + }); py::bind_vector(m, "VectorMoneroTx") + .def("sort", [](VectorMoneroTx &v) { + std::sort(v.begin(), v.end(), monero_tx_height_comparator()); + }) .def("copy", [](const VectorMoneroTx& v) { return VectorMoneroTx(v); }); py::bind_vector(m, "VectorMoneroTxWallet") + .def("sort", [](VectorMoneroTxWallet &v) { + std::sort(v.begin(), v.end(), monero_tx_height_comparator()); + }) .def("copy", [](const VectorMoneroTxWallet& v) { return VectorMoneroTxWallet(v); }); - py::bind_vector>>(m, "VectorMoneroOutput"); - py::bind_vector>>(m, "VectorMoneroOutputWallet"); - py::bind_vector>>(m, "VectorMoneroTransfer"); + py::bind_vector>>(m, "VectorMoneroOutput") + .def("copy", [](const std::vector>& v) { + return std::vector>(v); + }); + py::bind_vector>>(m, "VectorMoneroOutputWallet") + .def("copy", [](const std::vector>& v) { + return std::vector>(v); + }); + py::bind_vector>>(m, "VectorMoneroTransfer") + .def("copy", [](const std::vector>& v) { + return std::vector>(v); + }); py::bind_vector(m, "VectorMoneroIncomingTransfer") .def("sort", [](VectorMoneroIncomingTransfer &v) { - std::sort(v.begin(), v.end(), PyIncomingTransferComparator()); + std::sort(v.begin(), v.end(), monero_incoming_transfer_comparator()); }) .def("copy", [](const VectorMoneroIncomingTransfer& v) { return VectorMoneroIncomingTransfer(v); @@ -134,7 +231,7 @@ PYBIND11_MODULE(monero, m) { py::implicitly_convertible(); py::implicitly_convertible(); py::implicitly_convertible>(); - py::implicitly_convertible>>(); + py::implicitly_convertible>>(); py::implicitly_convertible>>(); py::implicitly_convertible>>(); py::implicitly_convertible>>(); @@ -149,7 +246,7 @@ PYBIND11_MODULE(monero, m) { py::implicitly_convertible>>(); // monero_error - py::exception pyMoneroError(m, "MoneroError"); + py::exception pyMoneroError(m, "MoneroError"); // python subclass py::exec(R"( @@ -163,7 +260,7 @@ PYBIND11_MODULE(monero, m) { try { if (p) std::rethrow_exception(p); } - catch (const PyMoneroRpcError& e) { + catch (const monero_rpc_error& e) { py::object cls = py::module_::import("monero").attr("MoneroRpcError"); py::object exc = cls(e.what(), e.code); PyErr_SetObject(cls.ptr(), exc.ptr()); @@ -177,24 +274,24 @@ PYBIND11_MODULE(monero, m) { .value("STAGENET", monero::monero_network_type::STAGENET); // enum monero_connection_type - py::enum_(m, "MoneroConnectionType") - .value("INVALID", PyMoneroConnectionType::INVALID) - .value("IPV4", PyMoneroConnectionType::IPV4) - .value("IPV6", PyMoneroConnectionType::IPV6) - .value("TOR", PyMoneroConnectionType::TOR) - .value("I2P", PyMoneroConnectionType::I2P); + py::enum_(m, "MoneroConnectionType") + .value("INVALID", monero_connection_type::INVALID) + .value("IPV4", monero_connection_type::IPV4) + .value("IPV6", monero_connection_type::IPV6) + .value("TOR", monero_connection_type::TOR) + .value("I2P", monero_connection_type::I2P); // enum monero_key_image_spent_status - py::enum_(m, "MoneroKeyImageSpentStatus") - .value("NOT_SPENT", PyMoneroKeyImageSpentStatus::NOT_SPENT) - .value("CONFIRMED", PyMoneroKeyImageSpentStatus::CONFIRMED) - .value("TX_POOL", PyMoneroKeyImageSpentStatus::TX_POOL); + py::enum_(m, "MoneroKeyImageSpentStatus") + .value("NOT_SPENT", monero_key_image_spent_status::NOT_SPENT) + .value("CONFIRMED", monero_key_image_spent_status::CONFIRMED) + .value("TX_POOL", monero_key_image_spent_status::TX_POOL); // enum address_type - py::enum_(m, "MoneroAddressType") - .value("PRIMARY_ADDRESS", PyMoneroAddressType::PRIMARY_ADDRESS) - .value("INTEGRATED_ADDRESS", PyMoneroAddressType::INTEGRATED_ADDRESS) - .value("SUBADDRESS", PyMoneroAddressType::SUBADDRESS); + py::enum_(m, "MoneroAddressType") + .value("PRIMARY_ADDRESS", monero_address_type::PRIMARY_ADDRESS) + .value("INTEGRATED_ADDRESS", monero_address_type::INTEGRATED_ADDRESS) + .value("SUBADDRESS", monero_address_type::SUBADDRESS); // enum monero_tx_priority py::enum_(m, "MoneroTxPriority") @@ -208,72 +305,36 @@ PYBIND11_MODULE(monero, m) { .value("SIGN_WITH_SPEND_KEY", monero::monero_message_signature_type::SIGN_WITH_SPEND_KEY) .value("SIGN_WITH_VIEW_KEY", monero::monero_message_signature_type::SIGN_WITH_VIEW_KEY); - // serializable_struct py_serializable_struct - .def(py::init<>()) .def("serialize", [](monero::serializable_struct& self) { MONERO_CATCH_AND_RETHROW(self.serialize()); }); - // monero_ssl_options - py_monero_ssl_options - .def(py::init<>()) - .def_readwrite("ssl_private_key_path", &PyMoneroSslOptions::m_ssl_private_key_path) - .def_readwrite("ssl_certificate_path", &PyMoneroSslOptions::m_ssl_certificate_path) - .def_readwrite("ssl_ca_file", &PyMoneroSslOptions::m_ssl_ca_file) - .def_readwrite("ssl_allowed_fingerprints", &PyMoneroSslOptions::m_ssl_allowed_fingerprints) - .def_readwrite("ssl_allow_any_cert", &PyMoneroSslOptions::m_ssl_allow_any_cert); - - // monero_json_request_params - py::class_>(m, "MoneroJsonRequestParams") - .def(py::init()); - - // monero_json_request_empty_params - py::class_>(m, "MoneroJsonRequestEmptyParams") - .def(py::init<>()); - - // monero_request - py::class_>(m, "MoneroRequest") + // monero_rpc_payment_info + py_monero_rpc_payment_info .def(py::init<>()) - .def_readwrite("method", &PyMoneroRequest::m_method); - - // monero_path_request - py::class_>(m, "MoneroPathRequest") - .def(py::init<>()); + .def_readwrite("credits", &monero_rpc_payment_info::m_credits) + .def_readwrite("top_block_hash", &monero_rpc_payment_info::m_top_block_hash); - // monero_json_request - py::class_>(m, "MoneroJsonRequest") - .def(py::init<>()) - .def(py::init(), py::arg("request")) - .def(py::init(), py::arg("method")) - .def(py::init&>(), py::arg("method"), py::arg("params")) - .def_readwrite("version", &PyMoneroJsonRequest::m_version) - .def_readwrite("id", &PyMoneroJsonRequest::m_id) - .def_readwrite("params", &PyMoneroJsonRequest::m_params); - - // monero_json_response - py::class_>(m, "MoneroJsonResponse") + // monero_ssl_options + py_monero_ssl_options .def(py::init<>()) - .def(py::init(), py::arg("response")) - .def_static("deserialize", [](const std::string& response_json) { - MONERO_CATCH_AND_RETHROW(PyMoneroJsonResponse::deserialize(response_json)); - }, py::arg("response_json")) - .def_readwrite("jsonrpc", &PyMoneroJsonResponse::m_jsonrpc) - .def_readwrite("id", &PyMoneroJsonResponse::m_id) - .def("get_result", [](const PyMoneroJsonResponse& self) { - MONERO_CATCH_AND_RETHROW(self.get_result()); - }); + .def_readwrite("ssl_private_key_path", &ssl_options::m_ssl_private_key_path) + .def_readwrite("ssl_certificate_path", &ssl_options::m_ssl_certificate_path) + .def_readwrite("ssl_ca_file", &ssl_options::m_ssl_ca_file) + .def_readwrite("ssl_allowed_fingerprints", &ssl_options::m_ssl_allowed_fingerprints) + .def_readwrite("ssl_allow_any_cert", &ssl_options::m_ssl_allow_any_cert); // monero_fee_estimate - py::class_>(m, "MoneroFeeEstimate") + py::class_>(m, "MoneroFeeEstimate") .def(py::init<>()) - .def_readwrite("fee", &PyMoneroFeeEstimate::m_fee) - .def_readwrite("fees", &PyMoneroFeeEstimate::m_fees) - .def_readwrite("quantization_mask", &PyMoneroFeeEstimate::m_quantization_mask); + .def_readwrite("fee", &monero_fee_estimate::m_fee) + .def_readwrite("fees", &monero_fee_estimate::m_fees) + .def_readwrite("quantization_mask", &monero_fee_estimate::m_quantization_mask); // monero_tx_backlog_entry - py::class_>(m, "MoneroTxBacklogEntry") + py::class_>(m, "MoneroTxBacklogEntry") .def(py::init<>()); // monero_version @@ -282,12 +343,6 @@ PYBIND11_MODULE(monero, m) { .def_readwrite("number", &monero::monero_version::m_number) .def_readwrite("is_release", &monero::monero_version::m_is_release); - // monero_connection_priority_comparator - py::class_>(m, "MoneroConnectionPriorityComparator") - .def_static("compare", [](int p1, int p2) { - MONERO_CATCH_AND_RETHROW(PyMoneroConnectionPriorityComparator::compare(p1, p2)); - }, py::arg("p1"), py::arg("p2")); - // monero_rpc_connection py_monero_rpc_connection .def(py::init(), py::arg("uri") = "", py::arg("username") = "", py::arg("password") = "", py::arg("proxy_uri") = "", py::arg("zmq_uri") = "", py::arg("priority") = 0, py::arg("timeout") = 20000) @@ -295,6 +350,9 @@ PYBIND11_MODULE(monero, m) { .def_static("before", [](const std::shared_ptr& c1, const std::shared_ptr& c2, const std::shared_ptr& current_connection) { MONERO_CATCH_AND_RETHROW(PyMoneroRpcConnection::before(c1, c2, current_connection)); }, py::arg("c1"), py::arg("c2"), py::arg("current_connection")) + .def_static("compare", [](int p1, int p2) { + MONERO_CATCH_AND_RETHROW(PyMoneroRpcConnection::compare(p1, p2)); + }, py::arg("p1"), py::arg("p2")) .def_property("uri", [](const PyMoneroRpcConnection& self) { return self.m_uri; }, [](PyMoneroRpcConnection& self, const boost::optional& val) { @@ -414,226 +472,218 @@ PYBIND11_MODULE(monero, m) { }, py::arg("other")); // monero_block_template - py::class_>(m, "MoneroBlockTemplate") + py::class_>(m, "MoneroBlockTemplate") .def(py::init<>()) - .def_readwrite("block_template_blob", &PyMoneroBlockTemplate::m_block_template_blob) - .def_readwrite("block_hashing_blob", &PyMoneroBlockTemplate::m_block_hashing_blob) - .def_readwrite("difficulty", &PyMoneroBlockTemplate::m_difficulty) - .def_readwrite("expected_reward", &PyMoneroBlockTemplate::m_expected_reward) - .def_readwrite("height", &PyMoneroBlockTemplate::m_height) - .def_readwrite("prev_hash", &PyMoneroBlockTemplate::m_prev_hash) - .def_readwrite("reserved_offset", &PyMoneroBlockTemplate::m_reserved_offset) - .def_readwrite("seed_height", &PyMoneroBlockTemplate::m_seed_height) - .def_readwrite("seed_hash", &PyMoneroBlockTemplate::m_seed_hash) - .def_readwrite("next_seed_hash", &PyMoneroBlockTemplate::m_next_seed_hash); + .def_readwrite("block_template_blob", &monero_block_template::m_block_template_blob) + .def_readwrite("block_hashing_blob", &monero_block_template::m_block_hashing_blob) + .def_readwrite("difficulty", &monero_block_template::m_difficulty) + .def_readwrite("expected_reward", &monero_block_template::m_expected_reward) + .def_readwrite("height", &monero_block_template::m_height) + .def_readwrite("prev_hash", &monero_block_template::m_prev_hash) + .def_readwrite("reserved_offset", &monero_block_template::m_reserved_offset) + .def_readwrite("seed_height", &monero_block_template::m_seed_height) + .def_readwrite("seed_hash", &monero_block_template::m_seed_hash) + .def_readwrite("next_seed_hash", &monero_block_template::m_next_seed_hash); // monero_connection_span - py::class_>(m, "MoneroConnectionSpan") + py::class_>(m, "MoneroConnectionSpan") .def(py::init<>()) - .def_readwrite("connection_id", &PyMoneroConnectionSpan::m_connection_id) - .def_readwrite("num_blocks", &PyMoneroConnectionSpan::m_num_blocks) - .def_readwrite("remote_address", &PyMoneroConnectionSpan::m_remote_address) - .def_readwrite("rate", &PyMoneroConnectionSpan::m_rate) - .def_readwrite("speed", &PyMoneroConnectionSpan::m_speed) - .def_readwrite("size", &PyMoneroConnectionSpan::m_size) - .def_readwrite("start_height", &PyMoneroConnectionSpan::m_start_height); + .def_readwrite("connection_id", &monero_connection_span::m_connection_id) + .def_readwrite("num_blocks", &monero_connection_span::m_num_blocks) + .def_readwrite("remote_address", &monero_connection_span::m_remote_address) + .def_readwrite("rate", &monero_connection_span::m_rate) + .def_readwrite("speed", &monero_connection_span::m_speed) + .def_readwrite("size", &monero_connection_span::m_size) + .def_readwrite("start_height", &monero_connection_span::m_start_height); // monero_peer - py::class_>(m, "MoneroPeer") + py::class_>(m, "MoneroPeer") .def(py::init<>()) - .def_readwrite("id", &PyMoneroPeer::m_id) - .def_readwrite("address", &PyMoneroPeer::m_address) - .def_readwrite("host", &PyMoneroPeer::m_host) - .def_readwrite("port", &PyMoneroPeer::m_port) - .def_readwrite("is_online", &PyMoneroPeer::m_is_online) - .def_readwrite("last_seen_timestamp", &PyMoneroPeer::m_last_seen_timestamp) - .def_readwrite("pruning_seed", &PyMoneroPeer::m_pruning_seed) - .def_readwrite("rpc_port", &PyMoneroPeer::m_rpc_port) - .def_readwrite("rpc_credits_per_hash", &PyMoneroPeer::m_rpc_credits_per_hash) - .def_readwrite("hash", &PyMoneroPeer::m_hash) - .def_readwrite("avg_download", &PyMoneroPeer::m_avg_download) - .def_readwrite("avg_upload", &PyMoneroPeer::m_avg_upload) - .def_readwrite("current_download", &PyMoneroPeer::m_current_download) - .def_readwrite("current_upload", &PyMoneroPeer::m_current_upload) - .def_readwrite("height", &PyMoneroPeer::m_height) - .def_readwrite("is_incoming", &PyMoneroPeer::m_is_incoming) - .def_readwrite("live_time", &PyMoneroPeer::m_live_time) - .def_readwrite("is_local_ip", &PyMoneroPeer::m_is_local_ip) - .def_readwrite("is_local_host", &PyMoneroPeer::m_is_local_host) - .def_readwrite("num_receives", &PyMoneroPeer::m_num_receives) - .def_readwrite("num_sends", &PyMoneroPeer::m_num_sends) - .def_readwrite("receive_idle_time", &PyMoneroPeer::m_receive_idle_time) - .def_readwrite("send_idle_time", &PyMoneroPeer::m_send_idle_time) - .def_readwrite("state", &PyMoneroPeer::m_state) - .def_readwrite("num_support_flags", &PyMoneroPeer::m_num_support_flags) - .def_readwrite("connection_type", &PyMoneroPeer::m_connection_type); + .def_readwrite("id", &monero_peer::m_id) + .def_readwrite("address", &monero_peer::m_address) + .def_readwrite("host", &monero_peer::m_host) + .def_readwrite("port", &monero_peer::m_port) + .def_readwrite("is_online", &monero_peer::m_is_online) + .def_readwrite("last_seen_timestamp", &monero_peer::m_last_seen_timestamp) + .def_readwrite("pruning_seed", &monero_peer::m_pruning_seed) + .def_readwrite("rpc_port", &monero_peer::m_rpc_port) + .def_readwrite("rpc_credits_per_hash", &monero_peer::m_rpc_credits_per_hash) + .def_readwrite("hash", &monero_peer::m_hash) + .def_readwrite("avg_download", &monero_peer::m_avg_download) + .def_readwrite("avg_upload", &monero_peer::m_avg_upload) + .def_readwrite("current_download", &monero_peer::m_current_download) + .def_readwrite("current_upload", &monero_peer::m_current_upload) + .def_readwrite("height", &monero_peer::m_height) + .def_readwrite("is_incoming", &monero_peer::m_is_incoming) + .def_readwrite("live_time", &monero_peer::m_live_time) + .def_readwrite("is_local_ip", &monero_peer::m_is_local_ip) + .def_readwrite("is_local_host", &monero_peer::m_is_local_host) + .def_readwrite("num_receives", &monero_peer::m_num_receives) + .def_readwrite("num_sends", &monero_peer::m_num_sends) + .def_readwrite("receive_idle_time", &monero_peer::m_receive_idle_time) + .def_readwrite("send_idle_time", &monero_peer::m_send_idle_time) + .def_readwrite("state", &monero_peer::m_state) + .def_readwrite("num_support_flags", &monero_peer::m_num_support_flags) + .def_readwrite("connection_type", &monero_peer::m_connection_type); // monero_alt_chain - py::class_>(m, "MoneroAltChain") + py::class_>(m, "MoneroAltChain") .def(py::init<>()) - .def_readwrite("block_hashes", &PyMoneroAltChain::m_block_hashes) - .def_readwrite("difficulty", &PyMoneroAltChain::m_difficulty) - .def_readwrite("height", &PyMoneroAltChain::m_height) - .def_readwrite("length", &PyMoneroAltChain::m_length) - .def_readwrite("main_chain_parent_block_hash", &PyMoneroAltChain::m_main_chain_parent_block_hash); + .def_readwrite("block_hashes", &monero_alt_chain::m_block_hashes) + .def_readwrite("difficulty", &monero_alt_chain::m_difficulty) + .def_readwrite("height", &monero_alt_chain::m_height) + .def_readwrite("length", &monero_alt_chain::m_length) + .def_readwrite("main_chain_parent_block_hash", &monero_alt_chain::m_main_chain_parent_block_hash); // monero_ban - py::class_>(m, "MoneroBan") + py::class_>(m, "MoneroBan") .def(py::init<>()) - .def_readwrite("host", &PyMoneroBan::m_host) - .def_readwrite("ip", &PyMoneroBan::m_ip) - .def_readwrite("is_banned", &PyMoneroBan::m_is_banned) - .def_readwrite("seconds", &PyMoneroBan::m_seconds); + .def_readwrite("host", &monero_ban::m_host) + .def_readwrite("ip", &monero_ban::m_ip) + .def_readwrite("is_banned", &monero_ban::m_is_banned) + .def_readwrite("seconds", &monero_ban::m_seconds); // monero_output_distribution_entry - py::class_>(m, "MoneroOutputDistributionEntry") + py::class_>(m, "MoneroOutputDistributionEntry") .def(py::init<>()) - .def_readwrite("amount", &PyMoneroOutputDistributionEntry::m_amount) - .def_readwrite("base", &PyMoneroOutputDistributionEntry::m_base) - .def_readwrite("distribution", &PyMoneroOutputDistributionEntry::m_distribution) - .def_readwrite("start_height", &PyMoneroOutputDistributionEntry::m_start_height); + .def_readwrite("amount", &monero_output_distribution_entry::m_amount) + .def_readwrite("base", &monero_output_distribution_entry::m_base) + .def_readwrite("distribution", &monero_output_distribution_entry::m_distribution) + .def_readwrite("start_height", &monero_output_distribution_entry::m_start_height); // monero_output_histogram_entry - py::class_>(m, "MoneroOutputHistogramEntry") + py::class_>(m, "MoneroOutputHistogramEntry") .def(py::init<>()) - .def_readwrite("amount", &PyMoneroOutputHistogramEntry::m_amount) - .def_readwrite("num_instances", &PyMoneroOutputHistogramEntry::m_num_instances) - .def_readwrite("unlocked_instances", &PyMoneroOutputHistogramEntry::m_unlocked_instances) - .def_readwrite("recent_instances", &PyMoneroOutputHistogramEntry::m_recent_instances); + .def_readwrite("amount", &monero_output_histogram_entry::m_amount) + .def_readwrite("num_instances", &monero_output_histogram_entry::m_num_instances) + .def_readwrite("unlocked_instances", &monero_output_histogram_entry::m_unlocked_instances) + .def_readwrite("recent_instances", &monero_output_histogram_entry::m_recent_instances); // monero_hard_fork_info - py::class_>(m, "MoneroHardForkInfo") + py::class_>(m, "MoneroHardForkInfo") .def(py::init<>()) - .def_readwrite("earliest_height", &PyMoneroHardForkInfo::m_earliest_height) - .def_readwrite("is_enabled", &PyMoneroHardForkInfo::m_is_enabled) - .def_readwrite("state", &PyMoneroHardForkInfo::m_state) - .def_readwrite("threshold", &PyMoneroHardForkInfo::m_threshold) - .def_readwrite("version", &PyMoneroHardForkInfo::m_version) - .def_readwrite("num_votes", &PyMoneroHardForkInfo::m_num_votes) - .def_readwrite("window", &PyMoneroHardForkInfo::m_window) - .def_readwrite("voting", &PyMoneroHardForkInfo::m_voting) - .def_readwrite("credits", &PyMoneroHardForkInfo::m_credits) - .def_readwrite("top_block_hash", &PyMoneroHardForkInfo::m_top_block_hash); + .def_readwrite("earliest_height", &monero_hard_fork_info::m_earliest_height) + .def_readwrite("is_enabled", &monero_hard_fork_info::m_is_enabled) + .def_readwrite("state", &monero_hard_fork_info::m_state) + .def_readwrite("threshold", &monero_hard_fork_info::m_threshold) + .def_readwrite("version", &monero_hard_fork_info::m_version) + .def_readwrite("num_votes", &monero_hard_fork_info::m_num_votes) + .def_readwrite("window", &monero_hard_fork_info::m_window) + .def_readwrite("voting", &monero_hard_fork_info::m_voting); // monero_prune_result - py::class_>(m, "MoneroPruneResult") + py::class_>(m, "MoneroPruneResult") .def(py::init<>()) - .def_readwrite("is_pruned", &PyMoneroPruneResult::m_is_pruned) - .def_readwrite("pruning_seed", &PyMoneroPruneResult::m_pruning_seed); + .def_readwrite("is_pruned", &monero_prune_result::m_is_pruned) + .def_readwrite("pruning_seed", &monero_prune_result::m_pruning_seed); // monero_daemon_sync_info - py::class_>(m, "MoneroDaemonSyncInfo") + py::class_>(m, "MoneroDaemonSyncInfo") .def(py::init<>()) - .def_readwrite("height", &PyMoneroDaemonSyncInfo::m_height) - .def_readwrite("peers", &PyMoneroDaemonSyncInfo::m_peers) - .def_readwrite("spans", &PyMoneroDaemonSyncInfo::m_spans) - .def_readwrite("target_height", &PyMoneroDaemonSyncInfo::m_target_height) - .def_readwrite("next_needed_pruning_seed", &PyMoneroDaemonSyncInfo::m_next_needed_pruning_seed) - .def_readwrite("overview", &PyMoneroDaemonSyncInfo::m_overview) - .def_readwrite("credits", &PyMoneroDaemonSyncInfo::m_credits) - .def_readwrite("top_block_hash", &PyMoneroDaemonSyncInfo::m_top_block_hash); + .def_readwrite("height", &monero_daemon_sync_info::m_height) + .def_readwrite("peers", &monero_daemon_sync_info::m_peers) + .def_readwrite("spans", &monero_daemon_sync_info::m_spans) + .def_readwrite("target_height", &monero_daemon_sync_info::m_target_height) + .def_readwrite("next_needed_pruning_seed", &monero_daemon_sync_info::m_next_needed_pruning_seed) + .def_readwrite("overview", &monero_daemon_sync_info::m_overview); // monero_daemon_info - py::class_>(m, "MoneroDaemonInfo") + py::class_>(m, "MoneroDaemonInfo") .def(py::init<>()) - .def_readwrite("version", &PyMoneroDaemonInfo::m_version) - .def_readwrite("num_alt_blocks", &PyMoneroDaemonInfo::m_num_alt_blocks) - .def_readwrite("block_size_limit", &PyMoneroDaemonInfo::m_block_size_limit) - .def_readwrite("block_size_median", &PyMoneroDaemonInfo::m_block_size_median) - .def_readwrite("block_weight_limit", &PyMoneroDaemonInfo::m_block_weight_limit) - .def_readwrite("block_weight_median", &PyMoneroDaemonInfo::m_block_weight_median) - .def_readwrite("bootstrap_daemon_address", &PyMoneroDaemonInfo::m_bootstrap_daemon_address) - .def_readwrite("difficulty", &PyMoneroDaemonInfo::m_difficulty) - .def_readwrite("cumulative_difficulty", &PyMoneroDaemonInfo::m_cumulative_difficulty) - .def_readwrite("free_space", &PyMoneroDaemonInfo::m_free_space) - .def_readwrite("num_offline_peers", &PyMoneroDaemonInfo::m_num_offline_peers) - .def_readwrite("num_online_peers", &PyMoneroDaemonInfo::m_num_online_peers) - .def_readwrite("height", &PyMoneroDaemonInfo::m_height) - .def_readwrite("height_without_bootstrap", &PyMoneroDaemonInfo::m_height_without_bootstrap) - .def_readwrite("network_type", &PyMoneroDaemonInfo::m_network_type) - .def_readwrite("is_offline", &PyMoneroDaemonInfo::m_is_offline) - .def_readwrite("num_incoming_connections", &PyMoneroDaemonInfo::m_num_incoming_connections) - .def_readwrite("num_outgoing_connections", &PyMoneroDaemonInfo::m_num_outgoing_connections) - .def_readwrite("num_rpc_connections", &PyMoneroDaemonInfo::m_num_rpc_connections) - .def_readwrite("start_timestamp", &PyMoneroDaemonInfo::m_start_timestamp) - .def_readwrite("adjusted_timestamp", &PyMoneroDaemonInfo::m_adjusted_timestamp) - .def_readwrite("target", &PyMoneroDaemonInfo::m_target) - .def_readwrite("target_height", &PyMoneroDaemonInfo::m_target_height) - .def_readwrite("top_block_hash", &PyMoneroDaemonInfo::m_top_block_hash) - .def_readwrite("num_txs", &PyMoneroDaemonInfo::m_num_txs) - .def_readwrite("num_txs_pool", &PyMoneroDaemonInfo::m_num_txs_pool) - .def_readwrite("was_bootstrap_ever_used", &PyMoneroDaemonInfo::m_was_bootstrap_ever_used) - .def_readwrite("database_size", &PyMoneroDaemonInfo::m_database_size) - .def_readwrite("update_available", &PyMoneroDaemonInfo::m_update_available) - .def_readwrite("credits", &PyMoneroDaemonInfo::m_credits) - .def_readwrite("is_busy_syncing", &PyMoneroDaemonInfo::m_is_busy_syncing) - .def_readwrite("is_synchronized", &PyMoneroDaemonInfo::m_is_synchronized) - .def_readwrite("is_restricted", &PyMoneroDaemonInfo::m_is_restricted); + .def_readwrite("version", &monero_daemon_info::m_version) + .def_readwrite("num_alt_blocks", &monero_daemon_info::m_num_alt_blocks) + .def_readwrite("block_size_limit", &monero_daemon_info::m_block_size_limit) + .def_readwrite("block_size_median", &monero_daemon_info::m_block_size_median) + .def_readwrite("block_weight_limit", &monero_daemon_info::m_block_weight_limit) + .def_readwrite("block_weight_median", &monero_daemon_info::m_block_weight_median) + .def_readwrite("bootstrap_daemon_address", &monero_daemon_info::m_bootstrap_daemon_address) + .def_readwrite("difficulty", &monero_daemon_info::m_difficulty) + .def_readwrite("cumulative_difficulty", &monero_daemon_info::m_cumulative_difficulty) + .def_readwrite("free_space", &monero_daemon_info::m_free_space) + .def_readwrite("num_offline_peers", &monero_daemon_info::m_num_offline_peers) + .def_readwrite("num_online_peers", &monero_daemon_info::m_num_online_peers) + .def_readwrite("height", &monero_daemon_info::m_height) + .def_readwrite("height_without_bootstrap", &monero_daemon_info::m_height_without_bootstrap) + .def_readwrite("network_type", &monero_daemon_info::m_network_type) + .def_readwrite("is_offline", &monero_daemon_info::m_is_offline) + .def_readwrite("num_incoming_connections", &monero_daemon_info::m_num_incoming_connections) + .def_readwrite("num_outgoing_connections", &monero_daemon_info::m_num_outgoing_connections) + .def_readwrite("num_rpc_connections", &monero_daemon_info::m_num_rpc_connections) + .def_readwrite("start_timestamp", &monero_daemon_info::m_start_timestamp) + .def_readwrite("adjusted_timestamp", &monero_daemon_info::m_adjusted_timestamp) + .def_readwrite("target", &monero_daemon_info::m_target) + .def_readwrite("target_height", &monero_daemon_info::m_target_height) + .def_readwrite("num_txs", &monero_daemon_info::m_num_txs) + .def_readwrite("num_txs_pool", &monero_daemon_info::m_num_txs_pool) + .def_readwrite("was_bootstrap_ever_used", &monero_daemon_info::m_was_bootstrap_ever_used) + .def_readwrite("database_size", &monero_daemon_info::m_database_size) + .def_readwrite("update_available", &monero_daemon_info::m_update_available) + .def_readwrite("is_busy_syncing", &monero_daemon_info::m_is_busy_syncing) + .def_readwrite("is_synchronized", &monero_daemon_info::m_is_synchronized) + .def_readwrite("is_restricted", &monero_daemon_info::m_is_restricted); // monero_daemon_update_check_result - py::class_>(m, "MoneroDaemonUpdateCheckResult") + py::class_>(m, "MoneroDaemonUpdateCheckResult") .def(py::init<>()) - .def_readwrite("is_update_available", &PyMoneroDaemonUpdateCheckResult::m_is_update_available) - .def_readwrite("version", &PyMoneroDaemonUpdateCheckResult::m_version) - .def_readwrite("hash", &PyMoneroDaemonUpdateCheckResult::m_hash) - .def_readwrite("auto_uri", &PyMoneroDaemonUpdateCheckResult::m_auto_uri) - .def_readwrite("user_uri", &PyMoneroDaemonUpdateCheckResult::m_user_uri); + .def_readwrite("is_update_available", &monero_daemon_update_check_result::m_is_update_available) + .def_readwrite("version", &monero_daemon_update_check_result::m_version) + .def_readwrite("hash", &monero_daemon_update_check_result::m_hash) + .def_readwrite("auto_uri", &monero_daemon_update_check_result::m_auto_uri) + .def_readwrite("user_uri", &monero_daemon_update_check_result::m_user_uri); // monero_daemon_update_check_result - py::class_>(m, "MoneroDaemonUpdateDownloadResult") + py::class_>(m, "MoneroDaemonUpdateDownloadResult") .def(py::init<>()) - .def_readwrite("download_path", &PyMoneroDaemonUpdateDownloadResult::m_download_path); + .def_readwrite("download_path", &monero_daemon_update_download_result::m_download_path); // monero_submit_tx_result - py::class_>(m, "MoneroSubmitTxResult") + py::class_>(m, "MoneroSubmitTxResult") .def(py::init<>()) - .def_readwrite("is_good", &PyMoneroSubmitTxResult::m_is_good) - .def_readwrite("is_relayed", &PyMoneroSubmitTxResult::m_is_relayed) - .def_readwrite("is_double_spend", &PyMoneroSubmitTxResult::m_is_double_spend) - .def_readwrite("is_fee_too_low", &PyMoneroSubmitTxResult::m_is_fee_too_low) - .def_readwrite("is_mixin_too_low", &PyMoneroSubmitTxResult::m_is_mixin_too_low) - .def_readwrite("has_invalid_input", &PyMoneroSubmitTxResult::m_has_invalid_input) - .def_readwrite("has_invalid_output", &PyMoneroSubmitTxResult::m_has_invalid_output) - .def_readwrite("has_too_few_outputs", &PyMoneroSubmitTxResult::m_has_too_few_outputs) - .def_readwrite("is_overspend", &PyMoneroSubmitTxResult::m_is_overspend) - .def_readwrite("is_too_big", &PyMoneroSubmitTxResult::m_is_too_big) - .def_readwrite("sanity_check_failed", &PyMoneroSubmitTxResult::m_sanity_check_failed) - .def_readwrite("reason", &PyMoneroSubmitTxResult::m_reason) - .def_readwrite("credits", &PyMoneroSubmitTxResult::m_credits) - .def_readwrite("top_block_hash", &PyMoneroSubmitTxResult::m_top_block_hash) - .def_readwrite("is_tx_extra_too_big", &PyMoneroSubmitTxResult::m_is_tx_extra_too_big) - .def_readwrite("is_nonzero_unlock_time", &PyMoneroSubmitTxResult::m_is_nonzero_unlock_time); + .def_readwrite("is_good", &monero_submit_tx_result::m_is_good) + .def_readwrite("is_relayed", &monero_submit_tx_result::m_is_relayed) + .def_readwrite("is_double_spend", &monero_submit_tx_result::m_is_double_spend) + .def_readwrite("is_fee_too_low", &monero_submit_tx_result::m_is_fee_too_low) + .def_readwrite("is_mixin_too_low", &monero_submit_tx_result::m_is_mixin_too_low) + .def_readwrite("has_invalid_input", &monero_submit_tx_result::m_has_invalid_input) + .def_readwrite("has_invalid_output", &monero_submit_tx_result::m_has_invalid_output) + .def_readwrite("has_too_few_outputs", &monero_submit_tx_result::m_has_too_few_outputs) + .def_readwrite("is_overspend", &monero_submit_tx_result::m_is_overspend) + .def_readwrite("is_too_big", &monero_submit_tx_result::m_is_too_big) + .def_readwrite("sanity_check_failed", &monero_submit_tx_result::m_sanity_check_failed) + .def_readwrite("reason", &monero_submit_tx_result::m_reason) + .def_readwrite("is_tx_extra_too_big", &monero_submit_tx_result::m_is_tx_extra_too_big) + .def_readwrite("is_nonzero_unlock_time", &monero_submit_tx_result::m_is_nonzero_unlock_time); // monero_tx_pool_stats - py::class_>(m, "MoneroTxPoolStats") + py::class_>(m, "MoneroTxPoolStats") .def(py::init<>()) - .def_readwrite("num_txs", &PyMoneroTxPoolStats::m_num_txs) - .def_readwrite("num_not_relayed", &PyMoneroTxPoolStats::m_num_not_relayed) - .def_readwrite("num_failing", &PyMoneroTxPoolStats::m_num_failing) - .def_readwrite("num_double_spends", &PyMoneroTxPoolStats::m_num_double_spends) - .def_readwrite("num10m", &PyMoneroTxPoolStats::m_num10m) - .def_readwrite("fee_total", &PyMoneroTxPoolStats::m_fee_total) - .def_readwrite("bytes_max", &PyMoneroTxPoolStats::m_bytes_max) - .def_readwrite("bytes_med", &PyMoneroTxPoolStats::m_bytes_med) - .def_readwrite("bytes_min", &PyMoneroTxPoolStats::m_bytes_min) - .def_readwrite("bytes_total", &PyMoneroTxPoolStats::m_bytes_total) - .def_readwrite("histo98pc", &PyMoneroTxPoolStats::m_histo98pc) - .def_readwrite("oldest_timestamp", &PyMoneroTxPoolStats::m_oldest_timestamp); + .def_readwrite("num_txs", &monero_tx_pool_stats::m_num_txs) + .def_readwrite("num_not_relayed", &monero_tx_pool_stats::m_num_not_relayed) + .def_readwrite("num_failing", &monero_tx_pool_stats::m_num_failing) + .def_readwrite("num_double_spends", &monero_tx_pool_stats::m_num_double_spends) + .def_readwrite("num10m", &monero_tx_pool_stats::m_num10m) + .def_readwrite("fee_total", &monero_tx_pool_stats::m_fee_total) + .def_readwrite("bytes_max", &monero_tx_pool_stats::m_bytes_max) + .def_readwrite("bytes_med", &monero_tx_pool_stats::m_bytes_med) + .def_readwrite("bytes_min", &monero_tx_pool_stats::m_bytes_min) + .def_readwrite("bytes_total", &monero_tx_pool_stats::m_bytes_total) + .def_readwrite("histo98pc", &monero_tx_pool_stats::m_histo98pc) + .def_readwrite("oldest_timestamp", &monero_tx_pool_stats::m_oldest_timestamp); // monero_mining_status - py::class_>(m, "MoneroMiningStatus") + py::class_>(m, "MoneroMiningStatus") .def(py::init<>()) - .def_readwrite("is_active", &PyMoneroMiningStatus::m_is_active) - .def_readwrite("is_background", &PyMoneroMiningStatus::m_is_background) - .def_readwrite("address", &PyMoneroMiningStatus::m_address) - .def_readwrite("speed", &PyMoneroMiningStatus::m_speed) - .def_readwrite("num_threads", &PyMoneroMiningStatus::m_num_threads); + .def_readwrite("is_active", &monero_mining_status::m_is_active) + .def_readwrite("is_background", &monero_mining_status::m_is_background) + .def_readwrite("address", &monero_mining_status::m_address) + .def_readwrite("speed", &monero_mining_status::m_speed) + .def_readwrite("num_threads", &monero_mining_status::m_num_threads); // monero_miner_tx_sum - py::class_>(m, "MoneroMinerTxSum") + py::class_>(m, "MoneroMinerTxSum") .def(py::init<>()) - .def_readwrite("emission_sum", &PyMoneroMinerTxSum::m_emission_sum) - .def_readwrite("fee_sum", &PyMoneroMinerTxSum::m_fee_sum); + .def_readwrite("emission_sum", &monero_miner_tx_sum::m_emission_sum) + .def_readwrite("fee_sum", &monero_miner_tx_sum::m_fee_sum); // monero_tx py_monero_tx @@ -688,7 +738,7 @@ PYBIND11_MODULE(monero, m) { MONERO_CATCH_AND_RETHROW(self.get_height()); }) .def("__lt__", [](const std::shared_ptr& a, const std::shared_ptr& b){ - PyTxHeightComparator comp; + monero_tx_height_comparator comp; return comp(a, b); }); @@ -728,26 +778,26 @@ PYBIND11_MODULE(monero, m) { // monero_wallet_config py_monero_wallet_config .def(py::init<>()) - .def(py::init(), py::arg("config"), py::keep_alive<1, 2>()) + .def(py::init(), py::arg("config"), py::keep_alive<1, 2>()) .def_static("deserialize", [](const std::string& config_json) { - MONERO_CATCH_AND_RETHROW(PyMoneroWalletConfig::deserialize(config_json)); + MONERO_CATCH_AND_RETHROW(monero::monero_wallet_config::deserialize(config_json)); }, py::arg("config_json")) - .def_readwrite("path", &PyMoneroWalletConfig::m_path) - .def_readwrite("password", &PyMoneroWalletConfig::m_password) - .def_readwrite("network_type", &PyMoneroWalletConfig::m_network_type) - .def_readwrite("server", &PyMoneroWalletConfig::m_server) - .def_readwrite("seed", &PyMoneroWalletConfig::m_seed) - .def_readwrite("seed_offset", &PyMoneroWalletConfig::m_seed_offset) - .def_readwrite("primary_address", &PyMoneroWalletConfig::m_primary_address) - .def_readwrite("private_view_key", &PyMoneroWalletConfig::m_private_view_key) - .def_readwrite("private_spend_key", &PyMoneroWalletConfig::m_private_spend_key) - .def_readwrite("save_current", &PyMoneroWalletConfig::m_save_current) - .def_readwrite("language", &PyMoneroWalletConfig::m_language) - .def_readwrite("restore_height", &PyMoneroWalletConfig::m_restore_height) - .def_readwrite("account_lookahead", &PyMoneroWalletConfig::m_account_lookahead) - .def_readwrite("subaddress_lookahead", &PyMoneroWalletConfig::m_subaddress_lookahead) - .def_readwrite("is_multisig", &PyMoneroWalletConfig::m_is_multisig) - .def("copy", [](PyMoneroWalletConfig& self) { + .def_readwrite("path", &monero::monero_wallet_config::m_path) + .def_readwrite("password", &monero::monero_wallet_config::m_password) + .def_readwrite("network_type", &monero::monero_wallet_config::m_network_type) + .def_readwrite("server", &monero::monero_wallet_config::m_server) + .def_readwrite("seed", &monero::monero_wallet_config::m_seed) + .def_readwrite("seed_offset", &monero::monero_wallet_config::m_seed_offset) + .def_readwrite("primary_address", &monero::monero_wallet_config::m_primary_address) + .def_readwrite("private_view_key", &monero::monero_wallet_config::m_private_view_key) + .def_readwrite("private_spend_key", &monero::monero_wallet_config::m_private_spend_key) + .def_readwrite("save_current", &monero::monero_wallet_config::m_save_current) + .def_readwrite("language", &monero::monero_wallet_config::m_language) + .def_readwrite("restore_height", &monero::monero_wallet_config::m_restore_height) + .def_readwrite("account_lookahead", &monero::monero_wallet_config::m_account_lookahead) + .def_readwrite("subaddress_lookahead", &monero::monero_wallet_config::m_subaddress_lookahead) + .def_readwrite("is_multisig", &monero::monero_wallet_config::m_is_multisig) + .def("copy", [](monero::monero_wallet_config& self) { MONERO_CATCH_AND_RETHROW(self.copy()); }); @@ -786,9 +836,9 @@ PYBIND11_MODULE(monero, m) { .def(py::init<>()) .def(py::init(), py::arg("tag"), py::arg("label")) .def(py::init>(), py::arg("tag"), py::arg("label"), py::arg("account_indices")) - .def_readwrite("tag", &PyMoneroAccountTag::m_tag) - .def_readwrite("label", &PyMoneroAccountTag::m_label) - .def_readwrite("account_indices", &PyMoneroAccountTag::m_account_indices); + .def_readwrite("tag", &monero_account_tag::m_tag) + .def_readwrite("label", &monero_account_tag::m_label) + .def_readwrite("account_indices", &monero_account_tag::m_account_indices); // monero_destination py_monero_destination @@ -836,7 +886,7 @@ PYBIND11_MODULE(monero, m) { MONERO_CATCH_AND_RETHROW(self->copy(self, tgt)); }) .def("__lt__", [](const monero::monero_incoming_transfer& a, const monero::monero_incoming_transfer& b){ - PyIncomingTransferComparator comp; + monero_incoming_transfer_comparator comp; return comp(a, b); }); @@ -910,7 +960,7 @@ PYBIND11_MODULE(monero, m) { MONERO_CATCH_AND_RETHROW(self->merge(self, other)); }, py::arg("other")) .def("__lt__", [](const monero::monero_output_wallet& a, const monero::monero_output_wallet& b){ - PyOutputComparator comp; + monero_output_comparator comp; return comp(a, b); }); @@ -1070,10 +1120,10 @@ PYBIND11_MODULE(monero, m) { // monero_decoded_address py_monero_decoded_address - .def(py::init(), py::arg("address"), py::arg("address_type"), py::arg("network_type")) - .def_readwrite("address", &PyMoneroDecodedAddress::m_address) - .def_readwrite("address_type", &PyMoneroDecodedAddress::m_address_type) - .def_readwrite("network_type", &PyMoneroDecodedAddress::m_network_type); + .def(py::init(), py::arg("address"), py::arg("address_type"), py::arg("network_type")) + .def_readwrite("address", &monero_decoded_address::m_address) + .def_readwrite("address_type", &monero_decoded_address::m_address_type) + .def_readwrite("network_type", &monero_decoded_address::m_network_type); // monero_tx_config py_monero_tx_config @@ -1100,7 +1150,7 @@ PYBIND11_MODULE(monero, m) { .def_readwrite("sweep_each_subaddress", &monero::monero_tx_config::m_sweep_each_subaddress) .def_readwrite("key_image", &monero::monero_tx_config::m_key_image) .def("set_address", [](monero::monero_tx_config& self, const std::string& address) { - if (self.m_destinations.size() > 1) throw PyMoneroError("Cannot set address because MoneroTxConfig already has multiple destinations"); + if (self.m_destinations.size() > 1) throw monero_error("Cannot set address because MoneroTxConfig already has multiple destinations"); if (self.m_destinations.empty()) { auto dest = std::make_shared(); dest->m_address = address; @@ -1200,222 +1250,219 @@ PYBIND11_MODULE(monero, m) { // monero_daemon_listener py_monero_daemon_listener .def(py::init<>()) - .def_readwrite("last_header", &PyMoneroDaemonListener::m_last_header) - .def("on_block_header", [](PyMoneroDaemonListener& self, const std::shared_ptr& header) { + .def_readwrite("last_header", &monero_daemon_listener::m_last_header) + .def("on_block_header", [](monero_daemon_listener& self, const std::shared_ptr& header) { MONERO_CATCH_AND_RETHROW(self.on_block_header(header)); }, py::arg("header")); // monero_daemon py_monero_daemon .def(py::init<>()) - .def("add_listener", [](PyMoneroDaemon& self, const std::shared_ptr& listener) { + .def("add_listener", [](monero_daemon& self, const std::shared_ptr& listener) { MONERO_CATCH_AND_RETHROW(self.add_listener(listener)); }, py::arg("listener"), py::call_guard()) - .def("remove_listener", [](PyMoneroDaemon& self, const std::shared_ptr& listener) { + .def("remove_listener", [](monero_daemon& self, const std::shared_ptr& listener) { MONERO_CATCH_AND_RETHROW(self.remove_listener(listener)); }, py::arg("listener"), py::call_guard()) - .def("get_listeners", [](PyMoneroDaemon& self) { + .def("get_listeners", [](monero_daemon& self) { MONERO_CATCH_AND_RETHROW(self.get_listeners()); }, py::call_guard()) - .def("get_version", [](PyMoneroDaemon& self) { + .def("get_version", [](monero_daemon& self) { MONERO_CATCH_AND_RETHROW(self.get_version()); }, py::call_guard()) - .def("is_trusted", [](PyMoneroDaemon& self) { + .def("is_trusted", [](monero_daemon& self) { MONERO_CATCH_AND_RETHROW(self.is_trusted()); }, py::call_guard()) - .def("get_height", [](PyMoneroDaemon& self) { + .def("get_height", [](monero_daemon& self) { MONERO_CATCH_AND_RETHROW(self.get_height()); }, py::call_guard()) - .def("get_block_hash", [](PyMoneroDaemon& self, uint64_t height) { + .def("get_block_hash", [](monero_daemon& self, uint64_t height) { MONERO_CATCH_AND_RETHROW(self.get_block_hash(height)); }, py::arg("height"), py::call_guard()) - .def("get_block_template", [](PyMoneroDaemon& self, const std::string& wallet_address) { - MONERO_CATCH_AND_RETHROW(self.get_block_template(wallet_address)); - }, py::arg("wallet_address"), py::call_guard()) - .def("get_block_template", [](PyMoneroDaemon& self, const std::string& wallet_address, int reserve_size) { + .def("get_block_template", [](monero_daemon& self, const std::string& wallet_address, const boost::optional& reserve_size) { MONERO_CATCH_AND_RETHROW(self.get_block_template(wallet_address, reserve_size)); - }, py::arg("wallet_address"), py::arg("reserve_size"), py::call_guard()) - .def("get_last_block_header", [](PyMoneroDaemon& self) { + }, py::arg("wallet_address"), py::arg("reserve_size") = py::none(), py::call_guard()) + .def("get_last_block_header", [](monero_daemon& self) { MONERO_CATCH_AND_RETHROW(self.get_last_block_header()); }, py::call_guard()) - .def("get_block_header_by_hash", [](PyMoneroDaemon& self, const std::string& hash) { + .def("get_block_header_by_hash", [](monero_daemon& self, const std::string& hash) { MONERO_CATCH_AND_RETHROW(self.get_block_header_by_hash(hash)); }, py::arg("hash"), py::call_guard()) - .def("get_block_header_by_height", [](PyMoneroDaemon& self, uint64_t height) { + .def("get_block_header_by_height", [](monero_daemon& self, uint64_t height) { MONERO_CATCH_AND_RETHROW(self.get_block_header_by_height(height)); }, py::arg("height"), py::call_guard()) - .def("get_block_headers_by_range", [](PyMoneroDaemon& self, uint64_t start_height, uint64_t end_height) { + .def("get_block_headers_by_range", [](monero_daemon& self, uint64_t start_height, uint64_t end_height) { MONERO_CATCH_AND_RETHROW(self.get_block_headers_by_range(start_height, end_height)); }, py::arg("start_height"), py::arg("end_height"), py::call_guard()) - .def("get_block_by_hash", [](PyMoneroDaemon& self, const std::string& hash) { + .def("get_block_by_hash", [](monero_daemon& self, const std::string& hash) { MONERO_CATCH_AND_RETHROW(self.get_block_by_hash(hash)); }, py::arg("hash"), py::call_guard()) - .def("get_blocks_by_hash", [](PyMoneroDaemon& self, const std::vector& block_hashes, uint64_t start_height, bool prune) { + .def("get_blocks_by_hash", [](monero_daemon& self, const std::vector& block_hashes, uint64_t start_height, bool prune) { MONERO_CATCH_AND_RETHROW(self.get_blocks_by_hash(block_hashes, start_height, prune)); }, py::arg("block_hashes"), py::arg("start_height"), py::arg("prune"), py::call_guard()) - .def("get_block_by_height", [](PyMoneroDaemon& self, uint64_t height) { + .def("get_block_by_height", [](monero_daemon& self, uint64_t height) { MONERO_CATCH_AND_RETHROW(self.get_block_by_height(height)); }, py::arg("height"), py::call_guard()) - .def("get_blocks_by_height", [](PyMoneroDaemon& self, const std::vector& heights) { + .def("get_blocks_by_height", [](monero_daemon& self, const std::vector& heights) { MONERO_CATCH_AND_RETHROW(self.get_blocks_by_height(heights)); }, py::arg("heights"), py::call_guard()) - .def("get_blocks_by_range", [](PyMoneroDaemon& self, boost::optional start_height, boost::optional end_height) { + .def("get_blocks_by_range", [](monero_daemon& self, boost::optional start_height, boost::optional end_height) { MONERO_CATCH_AND_RETHROW(self.get_blocks_by_range(start_height, end_height)); }, py::arg("start_height"), py::arg("end_height"), py::call_guard()) - .def("get_blocks_by_range_chunked", [](PyMoneroDaemon& self, boost::optional start_height, boost::optional end_height, boost::optional max_chunk_size) { + .def("get_blocks_by_range_chunked", [](monero_daemon& self, boost::optional start_height, boost::optional end_height, boost::optional max_chunk_size) { MONERO_CATCH_AND_RETHROW(self.get_blocks_by_range_chunked(start_height, end_height, max_chunk_size)); }, py::arg("start_height"), py::arg("end_height"), py::arg("max_chunk_size") = py::none(), py::call_guard()) - .def("get_block_hashes", [](PyMoneroDaemon& self, const std::vector& block_hashes, uint64_t start_height) { + .def("get_block_hashes", [](monero_daemon& self, const std::vector& block_hashes, uint64_t start_height) { MONERO_CATCH_AND_RETHROW(self.get_block_hashes(block_hashes, start_height)); }, py::arg("block_hashes"), py::arg("start_height"), py::call_guard()) - .def("get_tx", [](PyMoneroDaemon& self, const std::string& tx_hash, bool prune) { + .def("get_tx", [](monero_daemon& self, const std::string& tx_hash, bool prune) { MONERO_CATCH_AND_RETHROW(self.get_tx(tx_hash, prune)); }, py::arg("tx_hash"), py::arg("prune") = false, py::call_guard()) - .def("get_txs", [](PyMoneroDaemon& self, const std::vector& tx_hashes, bool prune) { + .def("get_txs", [](monero_daemon& self, const std::vector& tx_hashes, bool prune) { MONERO_CATCH_AND_RETHROW(self.get_txs(tx_hashes, prune)); }, py::arg("tx_hashes"), py::arg("prune") = false, py::call_guard()) - .def("get_tx_hex", [](PyMoneroDaemon& self, const std::string& tx_hash, bool prune) { + .def("get_tx_hex", [](monero_daemon& self, const std::string& tx_hash, bool prune) { MONERO_CATCH_AND_RETHROW(self.get_tx_hex(tx_hash, prune)); }, py::arg("tx_hash"), py::arg("prune") = false, py::call_guard()) - .def("get_tx_hexes", [](PyMoneroDaemon& self, const std::vector& tx_hashes, bool prune) { + .def("get_tx_hexes", [](monero_daemon& self, const std::vector& tx_hashes, bool prune) { MONERO_CATCH_AND_RETHROW(self.get_tx_hexes(tx_hashes, prune)); }, py::arg("tx_hashes"), py::arg("prune") = false, py::call_guard()) - .def("get_miner_tx_sum", [](PyMoneroDaemon& self, uint64_t height, uint64_t num_blocks) { + .def("get_miner_tx_sum", [](monero_daemon& self, uint64_t height, uint64_t num_blocks) { MONERO_CATCH_AND_RETHROW(self.get_miner_tx_sum(height, num_blocks)); }, py::arg("height"), py::arg("num_blocks"), py::call_guard()) - .def("get_fee_estimate", [](PyMoneroDaemon& self, uint64_t grace_blocks) { + .def("get_fee_estimate", [](monero_daemon& self, uint64_t grace_blocks) { MONERO_CATCH_AND_RETHROW(self.get_fee_estimate(grace_blocks)); }, py::arg("grace_blocks") = 0, py::call_guard()) - .def("submit_tx_hex", [](PyMoneroDaemon& self, const std::string& tx_hex, bool do_not_relay) { + .def("submit_tx_hex", [](monero_daemon& self, const std::string& tx_hex, bool do_not_relay) { MONERO_CATCH_AND_RETHROW(self.submit_tx_hex(tx_hex, do_not_relay)); }, py::arg("tx_hex"), py::arg("do_not_relay") = false, py::call_guard()) - .def("relay_tx_by_hash", [](PyMoneroDaemon& self, const std::string& tx_hash) { + .def("relay_tx_by_hash", [](monero_daemon& self, const std::string& tx_hash) { MONERO_CATCH_AND_RETHROW(self.relay_tx_by_hash(tx_hash)); }, py::arg("tx_hash"), py::call_guard()) - .def("relay_txs_by_hash", [](PyMoneroDaemon& self, const std::vector& tx_hashes) { + .def("relay_txs_by_hash", [](monero_daemon& self, const std::vector& tx_hashes) { MONERO_CATCH_AND_RETHROW(self.relay_txs_by_hash(tx_hashes)); }, py::arg("tx_hashes"), py::call_guard()) - .def("get_tx_pool", [](PyMoneroDaemon& self) { + .def("get_tx_pool", [](monero_daemon& self) { MONERO_CATCH_AND_RETHROW(self.get_tx_pool()); }, py::call_guard()) - .def("get_tx_pool_hashes", [](PyMoneroDaemon& self) { + .def("get_tx_pool_hashes", [](monero_daemon& self) { MONERO_CATCH_AND_RETHROW(self.get_tx_pool_hashes()); }, py::call_guard()) - .def("get_tx_pool_backlog", [](PyMoneroDaemon& self) { + .def("get_tx_pool_backlog", [](monero_daemon& self) { MONERO_CATCH_AND_RETHROW(self.get_tx_pool_backlog()); }, py::call_guard()) - .def("get_tx_pool_stats", [](PyMoneroDaemon& self) { + .def("get_tx_pool_stats", [](monero_daemon& self) { MONERO_CATCH_AND_RETHROW(self.get_tx_pool_stats()); }, py::call_guard()) - .def("flush_tx_pool", [](PyMoneroDaemon& self) { + .def("flush_tx_pool", [](monero_daemon& self) { MONERO_CATCH_AND_RETHROW(self.flush_tx_pool()); }, py::call_guard()) - .def("flush_tx_pool", [](PyMoneroDaemon& self, const std::vector& hashes) { + .def("flush_tx_pool", [](monero_daemon& self, const std::vector& hashes) { MONERO_CATCH_AND_RETHROW(self.flush_tx_pool(hashes)); }, py::arg("hashes"), py::call_guard()) - .def("flush_tx_pool", [](PyMoneroDaemon& self, const std::string& hash) { + .def("flush_tx_pool", [](monero_daemon& self, const std::string& hash) { MONERO_CATCH_AND_RETHROW(self.flush_tx_pool(hash)); }, py::arg("hash"), py::call_guard()) - .def("get_key_image_spent_status", [](PyMoneroDaemon& self, const std::string& key_image) { + .def("get_key_image_spent_status", [](monero_daemon& self, const std::string& key_image) { MONERO_CATCH_AND_RETHROW(self.get_key_image_spent_status(key_image)); }, py::arg("key_image"), py::call_guard()) - .def("get_key_image_spent_statuses", [](PyMoneroDaemon& self, const std::vector& key_images) { + .def("get_key_image_spent_statuses", [](monero_daemon& self, const std::vector& key_images) { MONERO_CATCH_AND_RETHROW(self.get_key_image_spent_statuses(key_images)); }, py::arg("key_images"), py::call_guard()) - .def("get_outputs", [](PyMoneroDaemon& self, const std::vector& outputs) { + .def("get_outputs", [](monero_daemon& self, const std::vector& outputs) { MONERO_CATCH_AND_RETHROW(self.get_outputs(outputs)); }, py::arg("outputs"), py::call_guard()) - .def("get_output_histogram", [](PyMoneroDaemon& self, const std::vector& amounts, const boost::optional& min_count, const boost::optional& max_count, const boost::optional& is_unlocked, const boost::optional& recent_cutoff) { + .def("get_output_histogram", [](monero_daemon& self, const std::vector& amounts, const boost::optional& min_count, const boost::optional& max_count, const boost::optional& is_unlocked, const boost::optional& recent_cutoff) { MONERO_CATCH_AND_RETHROW(self.get_output_histogram(amounts, min_count, max_count, is_unlocked, recent_cutoff)); }, py::arg("amounts"), py::arg("min_count"), py::arg("max_count"), py::arg("is_unlocked"), py::arg("recent_cutoff"), py::call_guard()) - .def("get_output_distribution", [](PyMoneroDaemon& self, const std::vector& amounts) { + .def("get_output_distribution", [](monero_daemon& self, const std::vector& amounts) { MONERO_CATCH_AND_RETHROW(self.get_output_distribution(amounts)); }, py::arg("amounts"), py::call_guard()) - .def("get_output_distribution", [](PyMoneroDaemon& self, const std::vector& amounts, bool is_cumulative, uint64_t start_height, uint64_t end_height) { + .def("get_output_distribution", [](monero_daemon& self, const std::vector& amounts, bool is_cumulative, uint64_t start_height, uint64_t end_height) { MONERO_CATCH_AND_RETHROW(self.get_output_distribution(amounts, is_cumulative, start_height, end_height)); }, py::arg("amounts"), py::arg("is_cumulative"), py::arg("start_height"), py::arg("end_height"), py::call_guard()) - .def("get_info", [](PyMoneroDaemon& self) { + .def("get_info", [](monero_daemon& self) { MONERO_CATCH_AND_RETHROW(self.get_info()); }, py::call_guard()) - .def("get_sync_info", [](PyMoneroDaemon& self) { + .def("get_sync_info", [](monero_daemon& self) { MONERO_CATCH_AND_RETHROW(self.get_sync_info()); }, py::call_guard()) - .def("get_hard_fork_info", [](PyMoneroDaemon& self) { + .def("get_hard_fork_info", [](monero_daemon& self) { MONERO_CATCH_AND_RETHROW(self.get_hard_fork_info()); }, py::call_guard()) - .def("get_alt_chains", [](PyMoneroDaemon& self) { + .def("get_alt_chains", [](monero_daemon& self) { MONERO_CATCH_AND_RETHROW(self.get_alt_chains()); }, py::call_guard()) - .def("get_alt_block_hashes", [](PyMoneroDaemon& self) { + .def("get_alt_block_hashes", [](monero_daemon& self) { MONERO_CATCH_AND_RETHROW(self.get_alt_block_hashes()); }, py::call_guard()) - .def("get_download_limit", [](PyMoneroDaemon& self) { + .def("get_download_limit", [](monero_daemon& self) { MONERO_CATCH_AND_RETHROW(self.get_download_limit()); }, py::call_guard()) - .def("set_download_limit", [](PyMoneroDaemon& self, int limit) { + .def("set_download_limit", [](monero_daemon& self, int limit) { MONERO_CATCH_AND_RETHROW(self.set_download_limit(limit)); }, py::arg("limit"), py::call_guard()) - .def("reset_download_limit", [](PyMoneroDaemon& self) { + .def("reset_download_limit", [](monero_daemon& self) { MONERO_CATCH_AND_RETHROW(self.reset_download_limit()); }, py::call_guard()) - .def("get_upload_limit", [](PyMoneroDaemon& self) { + .def("get_upload_limit", [](monero_daemon& self) { MONERO_CATCH_AND_RETHROW(self.get_upload_limit()); }, py::call_guard()) - .def("set_upload_limit", [](PyMoneroDaemon& self, int limit) { + .def("set_upload_limit", [](monero_daemon& self, int limit) { MONERO_CATCH_AND_RETHROW(self.set_upload_limit(limit)); }, py::arg("limit"), py::call_guard()) - .def("reset_upload_limit", [](PyMoneroDaemon& self) { + .def("reset_upload_limit", [](monero_daemon& self) { MONERO_CATCH_AND_RETHROW(self.reset_upload_limit()); }, py::call_guard()) - .def("get_peers", [](PyMoneroDaemon& self) { + .def("get_peers", [](monero_daemon& self) { MONERO_CATCH_AND_RETHROW(self.get_peers()); }, py::call_guard()) - .def("get_known_peers", [](PyMoneroDaemon& self) { + .def("get_known_peers", [](monero_daemon& self) { MONERO_CATCH_AND_RETHROW(self.get_known_peers()); }, py::call_guard()) - .def("set_outgoing_peer_limit", [](PyMoneroDaemon& self, int limit) { + .def("set_outgoing_peer_limit", [](monero_daemon& self, int limit) { MONERO_CATCH_AND_RETHROW(self.set_outgoing_peer_limit(limit)); }, py::arg("limit"), py::call_guard()) - .def("set_incoming_peer_limit", [](PyMoneroDaemon& self, int limit) { + .def("set_incoming_peer_limit", [](monero_daemon& self, int limit) { MONERO_CATCH_AND_RETHROW(self.set_incoming_peer_limit(limit)); }, py::arg("limit"), py::call_guard()) - .def("get_peer_bans", [](PyMoneroDaemon& self) { + .def("get_peer_bans", [](monero_daemon& self) { MONERO_CATCH_AND_RETHROW(self.get_peer_bans()); }, py::call_guard()) - .def("set_peer_bans", [](PyMoneroDaemon& self, const std::vector>& bans) { + .def("set_peer_bans", [](monero_daemon& self, const std::vector>& bans) { MONERO_CATCH_AND_RETHROW(self.set_peer_bans(bans)); }, py::arg("bans"), py::call_guard()) - .def("set_peer_ban", [](PyMoneroDaemon& self, const std::shared_ptr& ban) { + .def("set_peer_ban", [](monero_daemon& self, const std::shared_ptr& ban) { MONERO_CATCH_AND_RETHROW(self.set_peer_ban(ban)); }, py::arg("ban"), py::call_guard()) - .def("start_mining", [](PyMoneroDaemon& self, const std::string& address, uint64_t num_threads, bool is_background, bool ignore_battery) { + .def("start_mining", [](monero_daemon& self, const std::string& address, uint64_t num_threads, bool is_background, bool ignore_battery) { MONERO_CATCH_AND_RETHROW(self.start_mining(address, num_threads, is_background, ignore_battery)); }, py::arg("address"), py::arg("num_threads"), py::arg("is_background"), py::arg("ignore_battery"), py::call_guard()) - .def("stop_mining", [](PyMoneroDaemon& self) { + .def("stop_mining", [](monero_daemon& self) { MONERO_CATCH_AND_RETHROW(self.stop_mining()); }, py::call_guard()) - .def("get_mining_status", [](PyMoneroDaemon& self) { + .def("get_mining_status", [](monero_daemon& self) { MONERO_CATCH_AND_RETHROW(self.get_mining_status()); }, py::call_guard()) - .def("submit_block", [](PyMoneroDaemon& self, const std::string& block_blob) { + .def("submit_block", [](monero_daemon& self, const std::string& block_blob) { MONERO_CATCH_AND_RETHROW(self.submit_block(block_blob)); }, py::arg("block_blob"), py::call_guard()) - .def("submit_blocks", [](PyMoneroDaemon& self, const std::vector& block_blobs) { + .def("submit_blocks", [](monero_daemon& self, const std::vector& block_blobs) { MONERO_CATCH_AND_RETHROW(self.submit_blocks(block_blobs)); }, py::arg("block_blobs"), py::call_guard()) - .def("prune_blockchain", [](PyMoneroDaemon& self, bool check) { + .def("prune_blockchain", [](monero_daemon& self, bool check) { MONERO_CATCH_AND_RETHROW(self.prune_blockchain(check)); }, py::arg("check"), py::call_guard()) - .def("check_for_update", [](PyMoneroDaemon& self) { + .def("check_for_update", [](monero_daemon& self) { MONERO_CATCH_AND_RETHROW(self.check_for_update()); }, py::call_guard()) - .def("download_update", [](PyMoneroDaemon& self, const std::string& path) { + .def("download_update", [](monero_daemon& self, const std::string& path) { MONERO_CATCH_AND_RETHROW(self.download_update(path)); }, py::arg("path") = "", py::call_guard()) - .def("stop", [](PyMoneroDaemon& self) { + .def("stop", [](monero_daemon& self) { MONERO_CATCH_AND_RETHROW(self.stop()); }, py::call_guard()) - .def("wait_for_next_block_header", [](PyMoneroDaemon& self) { + .def("wait_for_next_block_header", [](monero_daemon& self) { MONERO_CATCH_AND_RETHROW(self.wait_for_next_block_header()); }, py::call_guard()); @@ -1423,10 +1470,10 @@ PYBIND11_MODULE(monero, m) { py_monero_daemon_rpc .def(py::init&>(), py::arg("rpc"), py::call_guard()) .def(py::init(), py::arg("uri"), py::arg("username") = "", py::arg("password") = "", py::arg("proxy_uri") = "", py::arg("zmq_uri") = "", py::arg("timeout") = 20000, py::call_guard()) - .def("get_rpc_connection", [](const PyMoneroDaemonRpc& self) { + .def("get_rpc_connection", [](const monero_daemon_rpc& self) { MONERO_CATCH_AND_RETHROW(self.get_rpc_connection()); }) - .def("is_connected", [](PyMoneroDaemonRpc& self) { + .def("is_connected", [](monero_daemon_rpc& self) { MONERO_CATCH_AND_RETHROW(self.is_connected()); }, py::call_guard()); @@ -1599,10 +1646,10 @@ PYBIND11_MODULE(monero, m) { 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) { + } catch (const monero_rpc_error& e) { throw; } catch (const std::exception& e) { - throw PyMoneroError(e.what()); + throw monero_error(e.what()); } }, py::arg("account_idx"), py::arg("subaddress_idx"), py::call_guard()) .def("get_subaddresses", [](PyMoneroWallet& self, uint32_t account_idx) { @@ -1801,16 +1848,16 @@ PYBIND11_MODULE(monero, m) { MONERO_CATCH_AND_RETHROW(self.delete_address_book_entry(index)); }, py::arg("index"), py::call_guard()) .def("tag_accounts", [](monero::monero_wallet& self, const std::string& tag, const std::vector& account_indices) { - throw PyMoneroError("MoneroWallet.tag_accounts(): not supported"); + throw monero_error("MoneroWallet.tag_accounts(): not supported"); }, py::arg("tag"), py::arg("account_indices"), py::call_guard()) .def("untag_accounts", [](monero::monero_wallet& self, const std::vector& account_indices) { - throw PyMoneroError("MoneroWallet.untag_accounts(): not supported"); + throw monero_error("MoneroWallet.untag_accounts(): not supported"); }, py::arg("account_indices"), py::call_guard()) .def("get_account_tags", [](monero::monero_wallet& self) { - throw PyMoneroError("MoneroWallet.get_account_tags(): not supported"); + throw monero_error("MoneroWallet.get_account_tags(): not supported"); }, py::call_guard()) .def("set_account_tag_label", [](monero::monero_wallet& self, const std::string& tag, const std::string& label) { - throw PyMoneroError("MoneroWallet.set_account_tag_label(): not supported"); + throw monero_error("MoneroWallet.set_account_tag_label(): not supported"); }, py::arg("tag"), py::arg("label"), py::call_guard()) .def("set_account_label", [](PyMoneroWallet& self, uint32_t account_idx, const std::string& label) { MONERO_CATCH_AND_RETHROW(self.set_subaddress_label(account_idx, 0, label)); @@ -1827,7 +1874,7 @@ PYBIND11_MODULE(monero, m) { self.get_attribute(key, val); return val; } catch (const std::exception& ex) { - throw PyMoneroError(ex.what()); + throw monero_error(ex.what()); } }, py::arg("key"), py::call_guard()) .def("set_attribute", [](PyMoneroWallet& self, const std::string& key, const std::string& val) { @@ -1887,29 +1934,29 @@ PYBIND11_MODULE(monero, m) { // monero_wallet_keys py_monero_wallet_keys - .def_static("create_wallet_random", [](const PyMoneroWalletConfig& config) { + .def_static("create_wallet_random", [](const monero::monero_wallet_config& config) { MONERO_CATCH_AND_RETHROW(monero::monero_wallet_keys::create_wallet_random(config)); }, py::arg("config"), py::call_guard()) - .def_static("create_wallet_from_seed", [](const PyMoneroWalletConfig& config) { + .def_static("create_wallet_from_seed", [](const monero::monero_wallet_config& config) { MONERO_CATCH_AND_RETHROW(monero::monero_wallet_keys::create_wallet_from_seed(config)); }, py::arg("config"), py::call_guard()) - .def_static("create_wallet_from_keys", [](const PyMoneroWalletConfig& config) { + .def_static("create_wallet_from_keys", [](const monero::monero_wallet_config& config) { MONERO_CATCH_AND_RETHROW(monero::monero_wallet_keys::create_wallet_from_keys(config)); }, py::arg("config"), py::call_guard()) .def_static("get_seed_languages", []() { MONERO_CATCH_AND_RETHROW(monero::monero_wallet_keys::get_seed_languages()); }, py::call_guard()) .def("tag_accounts", [](monero::monero_wallet_keys& self, const std::string& tag, const std::vector& account_indices) { - throw PyMoneroError("MoneroWalletKeys.tag_accounts(): not supported"); + throw monero_error("MoneroWalletKeys.tag_accounts(): not supported"); }, py::arg("tag"), py::arg("account_indices"), py::call_guard()) .def("untag_accounts", [](monero::monero_wallet_keys& self, const std::vector& account_indices) { - throw PyMoneroError("MoneroWalletKeys.untag_accounts(): not supported"); + throw monero_error("MoneroWalletKeys.untag_accounts(): not supported"); }, py::arg("account_indices"), py::call_guard()) .def("get_account_tags", [](monero::monero_wallet_keys& self) { - throw PyMoneroError("MoneroWalletKeys.get_account_tags(): not supported"); + throw monero_error("MoneroWalletKeys.get_account_tags(): not supported"); }, py::call_guard()) .def("set_account_tag_label", [](monero::monero_wallet_keys& self, const std::string& tag, const std::string& label) { - throw PyMoneroError("MoneroWalletKeys.set_account_tag_label(): not supported"); + throw monero_error("MoneroWalletKeys.set_account_tag_label(): not supported"); }, py::arg("tag"), py::arg("label"), py::call_guard()); // monero_wallet_full @@ -1926,14 +1973,14 @@ PYBIND11_MODULE(monero, m) { .def_static("open_wallet_data", [](const std::string& password, monero::monero_network_type nettype, const std::string& keys_data, const std::string& cache_data, const monero_rpc_connection& daemon_connection) { MONERO_CATCH_AND_RETHROW(monero::monero_wallet_full::open_wallet_data(password, nettype, keys_data, cache_data, daemon_connection)); }, py::arg("password"), py::arg("nettype"), py::arg("keys_data"), py::arg("cache_data"), py::arg("daemon_connection"), py::call_guard()) - .def_static("create_wallet", [](const PyMoneroWalletConfig& config) { + .def_static("create_wallet", [](const monero::monero_wallet_config& config) { try { return monero_wallet_full::create_wallet(config); } catch(const std::exception& ex) { std::string msg = ex.what(); if (msg.find("file already exists") != std::string::npos && config.m_path != boost::none) msg = std::string("Wallet already exists: ") + config.m_path.get(); - throw PyMoneroError(msg); + throw monero_error(msg); } }, py::arg("config"), py::call_guard()) .def_static("get_seed_languages", []() { @@ -1946,38 +1993,38 @@ PYBIND11_MODULE(monero, m) { MONERO_CATCH_AND_RETHROW(self.get_cache_file_buffer()); }, py::call_guard()) .def("tag_accounts", [](monero::monero_wallet_full& self, const std::string& tag, const std::vector& account_indices) { - throw PyMoneroError("MoneroWalletFull.tag_accounts(): not implemented"); + throw monero_error("MoneroWalletFull.tag_accounts(): not implemented"); }, py::arg("tag"), py::arg("account_indices"), py::call_guard()) .def("untag_accounts", [](monero::monero_wallet_full& self, const std::vector& account_indices) { - throw PyMoneroError("MoneroWalletFull.untag_accounts(): not implemented"); + throw monero_error("MoneroWalletFull.untag_accounts(): not implemented"); }, py::arg("account_indices"), py::call_guard()) .def("get_account_tags", [](monero::monero_wallet_full& self) { - throw PyMoneroError("MoneroWalletFull.get_account_tags(): not implemented"); + throw monero_error("MoneroWalletFull.get_account_tags(): not implemented"); }, py::call_guard()) .def("set_account_tag_label", [](monero::monero_wallet_full& self, const std::string& tag, const std::string& label) { - throw PyMoneroError("MoneroWalletFull.set_account_tag_label(): not implemented"); + throw monero_error("MoneroWalletFull.set_account_tag_label(): not implemented"); }, py::arg("tag"), py::arg("label"), py::call_guard()); // monero_wallet_rpc py_monero_wallet_rpc .def(py::init&>(), py::arg("rpc_connection"), py::call_guard()) .def(py::init(), py::arg("uri") = "", py::arg("username") = "", py::arg("password") = "", py::arg("proxy_uri") = "", py::arg("zmq_uri") = "", py::arg("timeout") = 20000, py::call_guard()) - .def("create_wallet", [](PyMoneroWalletRpc& self, const std::shared_ptr& config) { + .def("create_wallet", [](monero_wallet_rpc& self, const std::shared_ptr& config) { MONERO_CATCH_AND_RETHROW(self.create_wallet(config)); }, py::arg("config"), py::call_guard()) - .def("open_wallet", [](PyMoneroWalletRpc& self, const std::shared_ptr& config) { + .def("open_wallet", [](monero_wallet_rpc& self, const std::shared_ptr& config) { MONERO_CATCH_AND_RETHROW(self.open_wallet(config)); }, py::arg("config"), py::call_guard()) - .def("open_wallet", [](PyMoneroWalletRpc& self, const std::string& name, const std::string& password) { + .def("open_wallet", [](monero_wallet_rpc& self, const std::string& name, const std::string& password) { MONERO_CATCH_AND_RETHROW(self.open_wallet(name, password)); }, py::arg("name"), py::arg("password"), py::call_guard()) - .def("is_closed", [](const PyMoneroWalletRpc& self) { + .def("is_closed", [](const monero_wallet_rpc& self) { MONERO_CATCH_AND_RETHROW(self.is_closed()); }, py::call_guard()) - .def("get_seed_languages", [](PyMoneroWalletRpc& self) { + .def("get_seed_languages", [](monero_wallet_rpc& self) { MONERO_CATCH_AND_RETHROW(self.get_seed_languages()); }, py::call_guard()) - .def("get_rpc_connection", [](PyMoneroWalletRpc& self) { + .def("get_rpc_connection", [](monero_wallet_rpc& self) { MONERO_CATCH_AND_RETHROW(self.get_rpc_connection()); }, py::call_guard()) // this because of function hiding @@ -1987,22 +2034,22 @@ PYBIND11_MODULE(monero, m) { .def("set_daemon_connection", [](PyMoneroWallet& self, const std::string& uri, const std::string& username, const std::string& password, const std::string& proxy) { MONERO_CATCH_AND_RETHROW(self.set_daemon_connection(uri, username, password, proxy)); }, py::arg("uri"), py::arg("username") = "", py::arg("password") = "", py::arg("proxy") = "", py::call_guard()) - .def("set_daemon_connection", [](PyMoneroWalletRpc& self, const boost::optional& connection, bool is_trusted, const boost::optional& ssl_options) { + .def("set_daemon_connection", [](monero_wallet_rpc& self, const boost::optional& connection, bool is_trusted, const boost::optional& ssl_options) { MONERO_CATCH_AND_RETHROW(self.set_daemon_connection(connection, is_trusted, ssl_options)); }, py::arg("connection"), py::arg("is_trusted"), py::arg("ssl_options"), py::call_guard()) - .def("stop", [](PyMoneroWalletRpc& self) { + .def("stop", [](monero_wallet_rpc& self) { MONERO_CATCH_AND_RETHROW(self.stop()); }, py::call_guard()) - .def("tag_accounts", [](PyMoneroWalletRpc& self, const std::string& tag, const std::vector& account_indices) { + .def("tag_accounts", [](monero_wallet_rpc& self, const std::string& tag, const std::vector& account_indices) { MONERO_CATCH_AND_RETHROW(self.tag_accounts(tag, account_indices)); }, py::arg("tag"), py::arg("account_indices"), py::call_guard()) - .def("untag_accounts", [](PyMoneroWalletRpc& self, const std::vector& account_indices) { + .def("untag_accounts", [](monero_wallet_rpc& self, const std::vector& account_indices) { MONERO_CATCH_AND_RETHROW(self.untag_accounts(account_indices)); }, py::arg("account_indices"), py::call_guard()) - .def("get_account_tags", [](PyMoneroWalletRpc& self) { + .def("get_account_tags", [](monero_wallet_rpc& self) { MONERO_CATCH_AND_RETHROW(self.get_account_tags()); }, py::call_guard()) - .def("set_account_tag_label", [](PyMoneroWalletRpc& self, const std::string& tag, const std::string& label) { + .def("set_account_tag_label", [](monero_wallet_rpc& self, const std::string& tag, const std::string& label) { MONERO_CATCH_AND_RETHROW(self.set_account_tag_label(tag, label)); }, py::arg("tag"), py::arg("label"), py::call_guard()); @@ -2109,19 +2156,19 @@ PYBIND11_MODULE(monero, m) { py_tx_height_comparator .def_static("compare", [](const std::shared_ptr& tx1, const std::shared_ptr& tx2) { - PyTxHeightComparator tx_comp; + monero_tx_height_comparator tx_comp; MONERO_CATCH_AND_RETHROW(tx_comp(tx1, tx2)); }, py::arg("tx1"), py::arg("tx2")); py_incoming_transfer_comparator .def_static("compare", [](const monero::monero_incoming_transfer& t1, const monero::monero_incoming_transfer& t2){ - PyIncomingTransferComparator tr_comp; + monero_incoming_transfer_comparator tr_comp; MONERO_CATCH_AND_RETHROW(tr_comp(t1, t2)); }, py::arg("transfer1"), py::arg("transfer2")); py_output_comparator .def_static("compare", [](const monero::monero_output_wallet& o1, const monero::monero_output_wallet& o2) { - PyOutputComparator out_comp; + monero_output_comparator out_comp; MONERO_CATCH_AND_RETHROW(out_comp(o1, o2)); }, py::arg("output1"), py::arg("output2")); diff --git a/src/cpp/utils/py_monero_utils.cpp b/src/cpp/utils/py_monero_utils.cpp index d72aea8..c9933f2 100644 --- a/src/cpp/utils/py_monero_utils.cpp +++ b/src/cpp/utils/py_monero_utils.cpp @@ -1,3 +1,56 @@ +/** + * Copyright (c) everoddandeven + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2025-2026 woodser + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ #include #include "py_monero_utils.h" #include "cryptonote_basic/cryptonote_format_utils.h" @@ -320,4 +373,3 @@ std::vector> PyMoneroUtils::get_and_so tx_query.m_hashes = tx_hashes; return get_and_sort_txs(wallet, tx_query); } - diff --git a/src/cpp/utils/py_monero_utils.h b/src/cpp/utils/py_monero_utils.h index 62a5c8a..43652b5 100644 --- a/src/cpp/utils/py_monero_utils.h +++ b/src/cpp/utils/py_monero_utils.h @@ -1,3 +1,56 @@ +/** + * Copyright (c) everoddandeven + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2025-2026 woodser + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ #pragma once #include "common/py_monero_common.h" diff --git a/src/cpp/wallet/py_monero_wallet.cpp b/src/cpp/wallet/py_monero_wallet.cpp index ae30584..c7fd402 100644 --- a/src/cpp/wallet/py_monero_wallet.cpp +++ b/src/cpp/wallet/py_monero_wallet.cpp @@ -1,32 +1,65 @@ +/** + * Copyright (c) everoddandeven + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2025-2026 woodser + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ #include "py_monero_wallet.h" -void PyMoneroWalletListener::on_sync_progress(uint64_t height, uint64_t start_height, uint64_t end_height, double percent_done, const std::string& message) { - PYBIND11_OVERRIDE(void, monero_wallet_listener, on_sync_progress, height, start_height, end_height, percent_done, message); -} - -void PyMoneroWalletListener::on_new_block(uint64_t height) { - PYBIND11_OVERRIDE(void, monero_wallet_listener, on_new_block, height); -} - -void PyMoneroWalletListener::on_balances_changed(uint64_t new_balance, uint64_t new_unlocked_balance) { - PYBIND11_OVERRIDE(void, monero_wallet_listener, on_balances_changed, new_balance, new_unlocked_balance); -} - -void PyMoneroWalletListener::on_output_received(const monero_output_wallet& output) { - PYBIND11_OVERRIDE(void, monero_wallet_listener, on_output_received, output); -} - -void PyMoneroWalletListener::on_output_spent(const monero_output_wallet& output) { - PYBIND11_OVERRIDE(void, monero_wallet_listener, on_output_spent, output); -} - void PyMoneroWallet::announce_new_block(uint64_t height) { for (const auto &listener : m_listeners) { try { listener->on_new_block(height); } catch (const std::exception &e) { - std::cout << "Error: " << e.what() << std::endl; + MERROR(e.what()); } } } @@ -36,7 +69,7 @@ void PyMoneroWallet::announce_sync_progress(uint64_t height, uint64_t start_heig try { listener->on_sync_progress(height, start_height, end_height, percent_done, message); } catch (const std::exception &e) { - std::cout << "Error: " << e.what() << std::endl; + MERROR(e.what()); } } } @@ -46,7 +79,7 @@ void PyMoneroWallet::announce_balances_changed(uint64_t balance, uint64_t unlock try { listener->on_balances_changed(balance, unlocked_balance); } catch (const std::exception &e) { - std::cout << "Error: " << e.what() << std::endl; + MERROR(e.what()); } } } @@ -56,7 +89,7 @@ void PyMoneroWallet::announce_output_spent(const std::shared_ptron_output_spent(*output); } catch (const std::exception &e) { - std::cout << "Error: " << e.what() << std::endl; + MERROR(e.what()); } } } @@ -66,11 +99,7 @@ void PyMoneroWallet::announce_output_received(const std::shared_ptron_output_received(*output); } catch (const std::exception &e) { - std::cout << "Error: " << e.what() << std::endl; + MERROR(e.what()); } } } - -std::shared_ptr PyMoneroWallet::get_balances(boost::optional account_idx, boost::optional subaddress_idx) const { - throw std::runtime_error("MoneroWallet::get_balances(): not implemented"); -} diff --git a/src/cpp/wallet/py_monero_wallet.h b/src/cpp/wallet/py_monero_wallet.h index e037c2e..95a28f0 100644 --- a/src/cpp/wallet/py_monero_wallet.h +++ b/src/cpp/wallet/py_monero_wallet.h @@ -1,3 +1,56 @@ +/** + * Copyright (c) everoddandeven + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2025-2026 woodser + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ #pragma once #include @@ -11,11 +64,25 @@ std::vector> get_and_sort_txs(const mo class PyMoneroWalletListener : public monero_wallet_listener { public: - void on_sync_progress(uint64_t height, uint64_t start_height, uint64_t end_height, double percent_done, const std::string& message) override; - void on_new_block(uint64_t height) override; - void on_balances_changed(uint64_t new_balance, uint64_t new_unlocked_balance) override; - void on_output_received(const monero_output_wallet& output) override; - void on_output_spent(const monero_output_wallet& output) override; + void on_sync_progress(uint64_t height, uint64_t start_height, uint64_t end_height, double percent_done, const std::string& message) override { + PYBIND11_OVERRIDE(void, monero_wallet_listener, on_sync_progress, height, start_height, end_height, percent_done, message); + } + + void on_new_block(uint64_t height) override { + PYBIND11_OVERRIDE(void, monero_wallet_listener, on_new_block, height); + } + + void on_balances_changed(uint64_t new_balance, uint64_t new_unlocked_balance) override { + PYBIND11_OVERRIDE(void, monero_wallet_listener, on_balances_changed, new_balance, new_unlocked_balance); + } + + void on_output_received(const monero_output_wallet& output) override { + PYBIND11_OVERRIDE(void, monero_wallet_listener, on_output_received, output); + } + + void on_output_spent(const monero_output_wallet& output) override { + PYBIND11_OVERRIDE(void, monero_wallet_listener, on_output_spent, output); + } }; PYBIND11_MAKE_OPAQUE(std::vector); @@ -30,7 +97,7 @@ PYBIND11_MAKE_OPAQUE(std::vector>); PYBIND11_MAKE_OPAQUE(std::vector); PYBIND11_MAKE_OPAQUE(std::vector>); -PYBIND11_MAKE_OPAQUE(std::vector>); +PYBIND11_MAKE_OPAQUE(std::vector>); class PyMoneroWallet : public monero::monero_wallet { public: @@ -46,7 +113,7 @@ class PyMoneroWallet : public monero::monero_wallet { } // TODO define method in monero-cpp wallet interface - virtual std::vector> get_account_tags() { + virtual std::vector> get_account_tags() { throw std::runtime_error("MoneroWallet.get_account_tags(): not implemented"); } @@ -539,7 +606,6 @@ class PyMoneroWallet : public monero::monero_wallet { virtual void announce_balances_changed(uint64_t balance, uint64_t unlocked_balance); virtual void announce_output_spent(const std::shared_ptr &output); virtual void announce_output_received(const std::shared_ptr &output); - virtual std::shared_ptr get_balances(boost::optional account_idx, boost::optional subaddress_idx) const; virtual bool is_closed() const { return m_is_closed; } protected: diff --git a/src/cpp/wallet/py_monero_wallet_model.cpp b/src/cpp/wallet/py_monero_wallet_model.cpp index 5fbb205..a75547f 100644 --- a/src/cpp/wallet/py_monero_wallet_model.cpp +++ b/src/cpp/wallet/py_monero_wallet_model.cpp @@ -1,344 +1,79 @@ -#include "py_monero_wallet_model.h" -#include "utils/monero_utils.h" - - /** - * ---------------- DUPLICATED MONERO-CPP WALLET FULL CODE --------------------- + * Copyright (c) everoddandeven + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2025-2026 woodser + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers */ +#include "py_monero_wallet_model.h" +#include "utils/monero_utils.h" -bool bool_equals_2(bool val, const boost::optional& opt_val) { - return opt_val == boost::none ? false : val == *opt_val; -} - -/** - * Returns true iff tx1's height is known to be less than tx2's height for sorting. - */ -bool tx_height_less_than(const std::shared_ptr& tx1, const std::shared_ptr& tx2) { - if (tx1->m_block != boost::none && tx2->m_block != boost::none) return tx1->get_height() < tx2->get_height(); - else if (tx1->m_block == boost::none) return false; - else return true; -} - -/** - * Returns true iff transfer1 is ordered before transfer2 by ascending account and subaddress indices. - */ -bool incoming_transfer_before(const std::shared_ptr& transfer1, const std::shared_ptr& transfer2) { - - // compare by height - if (tx_height_less_than(transfer1->m_tx, transfer2->m_tx)) return true; - - // compare by account and subaddress index - if (transfer1->m_account_index.get() < transfer2->m_account_index.get()) return true; - else if (transfer1->m_account_index.get() == transfer2->m_account_index.get()) return transfer1->m_subaddress_index.get() < transfer2->m_subaddress_index.get(); - else return false; -} - -/** - * Returns true iff wallet vout1 is ordered before vout2 by ascending account and subaddress indices then index. - */ -bool vout_before(const std::shared_ptr& o1, const std::shared_ptr& o2) { - if (o1 == o2) return false; // ignore equal references - std::shared_ptr ow1 = std::static_pointer_cast(o1); - std::shared_ptr ow2 = std::static_pointer_cast(o2); - - // compare by height - if (tx_height_less_than(ow1->m_tx, ow2->m_tx)) return true; - - // compare by account index, subaddress index, output index, then key image hex - if (ow1->m_account_index.get() < ow2->m_account_index.get()) return true; - if (ow1->m_account_index.get() == ow2->m_account_index.get()) { - if (ow1->m_subaddress_index.get() < ow2->m_subaddress_index.get()) return true; - if (ow1->m_subaddress_index.get() == ow2->m_subaddress_index.get()) { - if (ow1->m_index.get() < ow2->m_index.get()) return true; - if (ow1->m_index.get() == ow2->m_index.get()) throw std::runtime_error("Should never sort outputs with duplicate indices"); - } - } - return false; -} - -bool PyTxHeightComparator::operator()(const std::shared_ptr& tx1, const std::shared_ptr& tx2) const { - auto h1 = tx1->get_height(); - auto h2 = tx2->get_height(); - - if (h1 == boost::none && h2 == boost::none) { - // both unconfirmed - return false; - } - else if (h1 == boost::none) { - // tx1 is unconfirmed - return false; - } - else if (h2 == boost::none) { - // tx2 is unconfirmed - return true; - } - - if (*h1 != *h2) { - return *h1 < *h2; - } - - // txs are in the same block so retain their original order - const auto& txs = tx1->m_block.get()->m_txs; - auto it1 = std::find(txs.begin(), txs.end(), tx1); - auto it2 = std::find(txs.begin(), txs.end(), tx2); - - return std::distance(txs.begin(), it1) < std::distance(txs.begin(), it2); -} - -bool PyIncomingTransferComparator::operator()(const std::shared_ptr& t1, const std::shared_ptr& t2) const { - return (*this)(*t1, *t2); -} - -bool PyIncomingTransferComparator::operator()(const monero::monero_incoming_transfer& t1, const monero::monero_incoming_transfer& t2) const { - PyTxHeightComparator tx_comp; - - // compare by height - if (tx_comp(t1.m_tx, t2.m_tx)) return true; - if (tx_comp(t2.m_tx, t1.m_tx)) return false; - - // compare by account and subaddress index - if (t1.m_account_index.value() != t2.m_account_index.value()) { - return t1.m_account_index.value() < t2.m_account_index.value(); - } - - return t1.m_subaddress_index.value() < t2.m_subaddress_index.value(); -} - -bool PyOutputComparator::operator()(const monero::monero_output_wallet& o1, const monero::monero_output_wallet& o2) const { - PyTxHeightComparator tx_comp; - - if (tx_comp(o1.m_tx, o2.m_tx)) return true; - if (tx_comp(o2.m_tx, o1.m_tx)) return false; - - if (o1.m_account_index.value() != o2.m_account_index.value()) { - return o1.m_account_index.value() < o2.m_account_index.value(); - } - - if (o1.m_subaddress_index.value() != o2.m_subaddress_index.value()) { - return o1.m_subaddress_index.value() < o2.m_subaddress_index.value(); - } - - if (o1.m_index.value() != o2.m_index.value()) { - return o1.m_index.value() < o2.m_index.value(); - } - - return o1.m_key_image.get()->m_hex.value() < o2.m_key_image.get()->m_hex.value(); -} - -PyMoneroWalletConfig::PyMoneroWalletConfig(const PyMoneroWalletConfig& config) { - m_path = config.m_path; - m_password = config.m_password; - m_network_type = config.m_network_type; - m_server = config.m_server; - m_seed = config.m_seed; - m_seed_offset = config.m_seed_offset; - m_primary_address = config.m_primary_address; - m_private_view_key = config.m_private_view_key; - m_private_spend_key = config.m_private_spend_key; - m_restore_height = config.m_restore_height; - m_language = config.m_language; - m_save_current = config.m_save_current; - m_account_lookahead = config.m_account_lookahead; - m_subaddress_lookahead = config.m_subaddress_lookahead; - m_is_multisig = config.m_is_multisig; -} +// ------------------------------ Custom Data Model --------------------------------- PyMoneroKeyImage::PyMoneroKeyImage(const monero::monero_key_image &key_image) { m_hex = key_image.m_hex; m_signature = key_image.m_signature; } -PyMoneroDecodedAddress::PyMoneroDecodedAddress(const std::string& address, PyMoneroAddressType address_type, monero::monero_network_type network_type): - m_address(address), - m_address_type(address_type), - m_network_type(network_type) { -} - -PyMoneroGetBalanceParams::PyMoneroGetBalanceParams(uint32_t account_idx, const std::vector& address_indices, bool all_accounts, bool strict): - m_account_idx(account_idx), - m_address_indices(address_indices), - m_all_accounts(all_accounts), - m_strict(strict) { -} - -PyMoneroGetBalanceParams::PyMoneroGetBalanceParams(uint32_t account_idx, boost::optional address_idx, bool all_accounts, bool strict): - m_account_idx(account_idx), - m_all_accounts(all_accounts), - m_strict(strict) { - if (address_idx != boost::none) m_address_indices.push_back(address_idx.get()); -} - -PyMoneroAddressBookEntryParams::PyMoneroAddressBookEntryParams(uint64_t index, bool set_address, const std::string& address, bool set_description, const std::string& description): - m_index(index), - m_set_address(set_address), - m_address(address), - m_set_description(set_description), - m_description(description) { -} - -PyMoneroVerifySignMessageParams::PyMoneroVerifySignMessageParams(const std::string &data, const std::string &address, const std::string& signature): - m_data(data), - m_address(address), - m_signature(signature) { -} - -PyMoneroVerifySignMessageParams::PyMoneroVerifySignMessageParams(const std::string &data, monero::monero_message_signature_type signature_type, uint32_t account_index, uint32_t address_index): - m_data(data), - m_signature_type(signature_type), - m_account_index(account_index), - m_address_index(address_index) { -} - -PyMoneroCheckTxKeyParams::PyMoneroCheckTxKeyParams(const std::string &tx_hash, const std::string &tx_key, const std::string &address): - m_tx_hash(tx_hash), - m_tx_key(tx_key), - m_address(address) { -} - -PyMoneroImportExportKeyImagesParams::PyMoneroImportExportKeyImagesParams(const std::vector> &key_images) { - for(const auto &key_image : key_images) { - m_key_images.push_back(std::make_shared(*key_image)); - } -} - -PyMoneroGetParsePaymentUri::PyMoneroGetParsePaymentUri(const monero_tx_config& config): - m_recipient_name(config.m_recipient_name), - m_tx_description(config.m_note), - m_payment_id(config.m_payment_id) { - - if (config.m_destinations.empty()) { - m_address = config.m_address; - m_amount = config.m_amount; - } else { - const auto& dest = config.m_destinations[0]; - m_address = dest->m_address; - m_amount = dest->m_amount; - } -} - -PyMoneroSweepParams::PyMoneroSweepParams(const monero_tx_config& config): - m_address(config.m_address), - m_account_index(config.m_account_index), - m_subaddr_indices(config.m_subaddress_indices), - m_key_image(config.m_key_image), - m_relay(config.m_relay), - m_priority(config.m_priority), - m_payment_id(config.m_payment_id), - m_below_amount(config.m_below_amount), - m_get_tx_key(true), - m_get_tx_hex(true), - m_get_tx_metadata(true) { -} - -PyMoneroCreateOpenWalletParams::PyMoneroCreateOpenWalletParams(const boost::optional& filename, const boost::optional &password): - m_filename(filename), m_password(password), m_autosave_current(false) { -} - -PyMoneroCreateOpenWalletParams::PyMoneroCreateOpenWalletParams(const boost::optional& filename, const boost::optional &password, const boost::optional &language): - m_filename(filename), m_password(password), m_language(language), m_autosave_current(false) { -} - -PyMoneroCreateOpenWalletParams::PyMoneroCreateOpenWalletParams(const boost::optional& filename, const boost::optional &password, const boost::optional &seed, const boost::optional &seed_offset, const boost::optional &restore_height, const boost::optional &language, const boost::optional &autosave_current, const boost::optional &enable_multisig_experimental): - m_filename(filename), - m_password(password), - m_seed(seed), - m_seed_offset(seed_offset), - m_restore_height(restore_height), - m_language(language), - m_autosave_current(autosave_current), - m_enable_multisig_experimental(enable_multisig_experimental) { -} - -PyMoneroCreateOpenWalletParams::PyMoneroCreateOpenWalletParams(const boost::optional& filename, const boost::optional &password, const boost::optional &address, const boost::optional &view_key, const boost::optional &spend_key, const boost::optional &restore_height, const boost::optional &autosave_current): - m_filename(filename), - m_password(password), - m_address(address), - m_view_key(view_key), - m_spend_key(spend_key), - m_restore_height(restore_height), - m_autosave_current(autosave_current) { -} - -PyMoneroReserveProofParams::PyMoneroReserveProofParams(const std::string &message, bool all): - m_all(all), m_message(message) { -} - -PyMoneroReserveProofParams::PyMoneroReserveProofParams(const std::string &address, const std::string &message, const std::string &signature): - m_address(address), m_message(message), m_signature(signature) { -} - -PyMoneroReserveProofParams::PyMoneroReserveProofParams(const std::string &tx_hash, const std::string &address, const std::string &message, const std::string &signature): - m_tx_hash(tx_hash), m_address(address), m_message(message), m_signature(signature) { -} - -PyMoneroReserveProofParams::PyMoneroReserveProofParams(const std::string &tx_hash, const std::string &message): - m_tx_hash(tx_hash), m_message(message) { -} - -PyMoneroReserveProofParams::PyMoneroReserveProofParams(uint32_t account_index, uint64_t amount, const std::string &message): - m_account_index(account_index), m_amount(amount), m_message(message) { -} - -PyMoneroTransferParams::PyMoneroTransferParams(const monero::monero_tx_config &config) { - for (const auto& sub_idx : config.m_subaddress_indices) { - m_subaddress_indices.push_back(sub_idx); - } - - if (config.m_address != boost::none) { - auto dest = std::make_shared(); - dest->m_address = config.m_address; - dest->m_amount = config.m_amount; - m_destinations.push_back(dest); - } - - for (const auto &dest : config.m_destinations) { - if (dest->m_address == boost::none) throw std::runtime_error("Destination address is not defined"); - if (dest->m_amount == boost::none) throw std::runtime_error("Destination amount is not defined"); - if (config.m_address != boost::none && *dest->m_address == *config.m_address) continue; - m_destinations.push_back(dest); - } - - m_subtract_fee_from_outputs = config.m_subtract_fee_from; - m_account_index = config.m_account_index; - m_payment_id = config.m_payment_id; - if (bool_equals_2(true, config.m_relay)) { - m_do_not_relay = false; - } - else { - m_do_not_relay = true; - } - if (config.m_priority == monero_tx_priority::DEFAULT) { - m_priority = 0; - } - else if (config.m_priority == monero_tx_priority::UNIMPORTANT) { - m_priority = 1; - } - else if (config.m_priority == monero_tx_priority::NORMAL) { - m_priority = 2; - } - else if (config.m_priority == monero_tx_priority::ELEVATED) { - m_priority = 3; - } - m_get_tx_hex = true; - m_get_tx_metadata = true; - if (bool_equals_2(true, config.m_can_split)) m_get_tx_keys = true; - else m_get_tx_key = true; -} +rapidjson::Value PyMoneroKeyImage::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); -PyMoneroGetIncomingTransfersParams::PyMoneroGetIncomingTransfersParams(const std::string& transfer_type, bool verbose): - m_transfer_type(transfer_type), m_verbose(verbose) { -} + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_hex != boost::none) monero_utils::add_json_member("key_image", m_hex.get(), allocator, root, value_str); + if (m_signature != boost::none) monero_utils::add_json_member("signature", m_signature.get(), allocator, root, value_str); -std::shared_ptr PyMoneroTxQuery::decontextualize(const std::shared_ptr &query) { - query->m_is_incoming = boost::none; - query->m_is_outgoing = boost::none; - query->m_transfer_query = boost::none; - query->m_input_query = boost::none; - query->m_output_query = boost::none; - return query; + // return root + return root; } - bool PyMoneroOutputQuery::is_contextual(const monero::monero_output_query &query) { if (query.m_tx_query == boost::none) return false; if (query.m_tx_query.get()->m_is_incoming != boost::none) return true; // requires context of all transfers @@ -359,6 +94,15 @@ bool PyMoneroTransferQuery::is_contextual(const monero::monero_transfer_query &q return false; } +std::shared_ptr PyMoneroTxQuery::decontextualize(const std::shared_ptr &query) { + query->m_is_incoming = boost::none; + query->m_is_outgoing = boost::none; + query->m_transfer_query = boost::none; + query->m_input_query = boost::none; + query->m_output_query = boost::none; + return query; +} + bool PyMoneroTxWallet::decode_rpc_type(const std::string &rpc_type, const std::shared_ptr &tx) { bool is_outgoing = false; if (rpc_type == std::string("in")) { @@ -1116,29 +860,6 @@ void PyMoneroMultisigSignResult::from_property_tree(const boost::property_tree:: } } -void PyMoneroAccountTag::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& account_tag) { - for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { - std::string key = it->first; - if (key == std::string("tag")) account_tag->m_tag = it->second.data(); - else if (key == std::string("label") && !it->second.data().empty()) account_tag->m_label = it->second.data(); - } -} - -void PyMoneroAccountTag::from_property_tree(const boost::property_tree::ptree& node, std::vector>& account_tags) { - for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { - std::string key = it->first; - if (key == std::string("account_tags")) { - auto account_tags_node = it->second; - - for (auto it2 = account_tags_node.begin(); it2 != account_tags_node.end(); ++it2) { - auto account_tag = std::make_shared(); - from_property_tree(it2->second, account_tag); - account_tags.push_back(account_tag); - } - } - } -} - void PyMoneroSubaddress::from_rpc_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& subaddress) { for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; @@ -1232,37 +953,6 @@ void PyMoneroAccount::from_property_tree(const boost::property_tree::ptree& node } } -uint64_t PyMoneroWalletGetHeightResponse::from_property_tree(const boost::property_tree::ptree& node) { - for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { - std::string key = it->first; - if (key == std::string("height")) return it->second.get_value(); - } - throw std::runtime_error("Invalid get_height response"); -} - -void PyMoneroGetParsePaymentUri::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& response) { - for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { - std::string key = it->first; - if (key == std::string("uri")) { - PyMoneroGetParsePaymentUri::from_property_tree(it->second, response); - return; - } - if (key == std::string("address") && !it->second.data().empty()) response->m_address = it->second.data(); - else if (key == std::string("amount")) response->m_amount = it->second.get_value(); - else if (key == std::string("payment_id") && !it->second.data().empty()) response->m_payment_id = it->second.data(); - else if (key == std::string("recipient_name") && !it->second.data().empty()) response->m_recipient_name = it->second.data(); - else if (key == std::string("tx_description") && !it->second.data().empty()) response->m_tx_description = it->second.data(); - } -} - -int PyMoneroImportMultisigHexResponse::from_property_tree(const boost::property_tree::ptree& node) { - for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { - std::string key = it->first; - if (key == std::string("n_outputs")) return it->second.get_value(); - } - throw std::runtime_error("Invalid prepare multisig response"); -} - void PyMoneroAddressBookEntry::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& entry) { for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; @@ -1289,59 +979,192 @@ void PyMoneroAddressBookEntry::from_property_tree(const boost::property_tree::pt } } -void PyMoneroGetBalanceResponse::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& response) { +void PyMoneroCheckReserve::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& check) { for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; - if (key == std::string("balance")) response->m_balance = it->second.get_value(); - else if (key == std::string("unlocked_balance")) response->m_unlocked_balance = it->second.get_value(); - else if (key == std::string("multisig_import_needed")) response->m_multisig_import_needed = it->second.get_value(); - else if (key == std::string("time_to_unlock")) response->m_time_to_unlock = it->second.get_value(); - else if (key == std::string("blocks_to_unlock")) response->m_blocks_to_unlock = it->second.get_value(); - else if (key == std::string("per_subaddress")) { - auto node2 = it->second; + if (key == std::string("good")) check->m_is_good = it->second.get_value(); + else if (key == std::string("total")) check->m_total_amount = it->second.get_value(); + else if (key == std::string("spent")) check->m_unconfirmed_spent_amount = it->second.get_value(); + } - for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) { - auto sub = std::make_shared(); - PyMoneroSubaddress::from_rpc_property_tree(it2->second, sub); - response->m_per_subaddress.push_back(sub); + 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& check) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("good")) check->m_is_good = it->second.get_value(); + if (key == std::string("in_pool")) check->m_in_tx_pool = it->second.get_value(); + else if (key == std::string("confirmations")) check->m_num_confirmations = it->second.get_value(); + else if (key == std::string("received")) check->m_received_amount = it->second.get_value(); + } + + if (!bool_equals_2(true, check->m_is_good)) { + // normalize invalid tx proof + check->m_in_tx_pool = boost::none; + check->m_num_confirmations = boost::none; + check->m_received_amount = boost::none; + } +} + +void PyMoneroMessageSignatureResult::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr result) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("good")) result->m_is_good = it->second.get_value(); + else if (key == std::string("old")) result->m_is_old = it->second.get_value(); + else if (key == std::string("signature_type")) { + std::string sig_type = it->second.data(); + if (sig_type == std::string("view")) { + result->m_signature_type = monero::monero_message_signature_type::SIGN_WITH_VIEW_KEY; + } + else { + result->m_signature_type = monero::monero_message_signature_type::SIGN_WITH_SPEND_KEY; } } + else if (key == std::string("version")) result->m_version = it->second.get_value(); } } -std::string PyMoneroExportMultisigHexResponse::from_property_tree(const boost::property_tree::ptree& node) { +// ------------------------------ Extended Data Model --------------------------------- + +bool monero_tx_height_comparator::operator()(const std::shared_ptr& tx1, const std::shared_ptr& tx2) const { + auto h1 = tx1->get_height(); + auto h2 = tx2->get_height(); + + if (h1 == boost::none && h2 == boost::none) { + // both unconfirmed + return false; + } + else if (h1 == boost::none) { + // tx1 is unconfirmed + return false; + } + else if (h2 == boost::none) { + // tx2 is unconfirmed + return true; + } + + if (*h1 != *h2) { + return *h1 < *h2; + } + + // txs are in the same block so retain their original order + const auto& txs = tx1->m_block.get()->m_txs; + auto it1 = std::find(txs.begin(), txs.end(), tx1); + auto it2 = std::find(txs.begin(), txs.end(), tx2); + + return std::distance(txs.begin(), it1) < std::distance(txs.begin(), it2); +} + +bool monero_incoming_transfer_comparator::operator()(const std::shared_ptr& t1, const std::shared_ptr& t2) const { + return (*this)(*t1, *t2); +} + +bool monero_incoming_transfer_comparator::operator()(const monero::monero_incoming_transfer& t1, const monero::monero_incoming_transfer& t2) const { + monero_tx_height_comparator tx_comp; + + // compare by height + if (tx_comp(t1.m_tx, t2.m_tx)) return true; + if (tx_comp(t2.m_tx, t1.m_tx)) return false; + + // compare by account and subaddress index + if (t1.m_account_index.value() != t2.m_account_index.value()) { + return t1.m_account_index.value() < t2.m_account_index.value(); + } + + return t1.m_subaddress_index.value() < t2.m_subaddress_index.value(); +} + +bool monero_output_comparator::operator()(const monero::monero_output_wallet& o1, const monero::monero_output_wallet& o2) const { + monero_tx_height_comparator tx_comp; + + if (tx_comp(o1.m_tx, o2.m_tx)) return true; + if (tx_comp(o2.m_tx, o1.m_tx)) return false; + + if (o1.m_account_index.value() != o2.m_account_index.value()) { + return o1.m_account_index.value() < o2.m_account_index.value(); + } + + if (o1.m_subaddress_index.value() != o2.m_subaddress_index.value()) { + return o1.m_subaddress_index.value() < o2.m_subaddress_index.value(); + } + + if (o1.m_index.value() != o2.m_index.value()) { + return o1.m_index.value() < o2.m_index.value(); + } + + return o1.m_key_image.get()->m_hex.value() < o2.m_key_image.get()->m_hex.value(); +} + +// --------------------------- MONERO DECODED ADDRESS --------------------------- + +monero_decoded_address::monero_decoded_address(const std::string& address, monero_address_type address_type, monero::monero_network_type network_type): + m_address(address), + m_address_type(address_type), + m_network_type(network_type) { +} + +// --------------------------- MONERO ACCOUNT TAG --------------------------- + +void monero_account_tag::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& account_tag) { for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; - if (key == std::string("info")) return it->second.data(); + if (key == std::string("tag")) account_tag->m_tag = it->second.data(); + else if (key == std::string("label") && !it->second.data().empty()) account_tag->m_label = it->second.data(); } - throw std::runtime_error("Invalid prepare multisig response"); } -std::vector PyMoneroSubmitMultisigTxHexResponse::from_property_tree(const boost::property_tree::ptree& node) { +void monero_account_tag::from_property_tree(const boost::property_tree::ptree& node, std::vector>& account_tags) { for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; - if (key == std::string("tx_hash_list")) { - auto node2 = it->second; - std::vector hashes; - for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) { - hashes.push_back(it2->second.data()); - } + if (key == std::string("account_tags")) { + auto account_tags_node = it->second; - return hashes; + for (auto it2 = account_tags_node.begin(); it2 != account_tags_node.end(); ++it2) { + auto account_tag = std::make_shared(); + from_property_tree(it2->second, account_tag); + account_tags.push_back(account_tag); + } } } - throw std::runtime_error("Invalid prepare multisig response"); } -std::string PyMoneroPrepareMakeMultisigResponse::from_property_tree(const boost::property_tree::ptree& node) { - for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { - std::string key = it->first; - if (key == std::string("multisig_info")) return it->second.data(); +rapidjson::Value monero_account_tag::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root + rapidjson::Value root(rapidjson::kObjectType); + + // set string values + rapidjson::Value value_str(rapidjson::kStringType); + if (m_tag != boost::none) monero_utils::add_json_member("tag", m_tag.get(), allocator, root, value_str); + if (m_label != boost::none) monero_utils::add_json_member("label", m_label.get(), allocator, root, value_str); + if (!m_account_indices.empty()) root.AddMember("accountIndices", monero_utils::to_rapidjson_val(allocator, m_account_indices), allocator); + + // return root + return root; +} + +// --------------------------- MONERO GET PAYMENT URI --------------------------- + +monero_get_payment_uri::monero_get_payment_uri(const monero_tx_config& config): + m_recipient_name(config.m_recipient_name), + m_tx_description(config.m_note), + m_payment_id(config.m_payment_id) { + + if (config.m_destinations.empty()) { + m_address = config.m_address; + m_amount = config.m_amount; + } else { + const auto& dest = config.m_destinations[0]; + m_address = dest->m_address; + m_amount = dest->m_amount; } - throw std::runtime_error("Invalid prepare multisig response"); } -std::string PyMoneroGetParsePaymentUri::from_property_tree(const boost::property_tree::ptree& node) { +std::string monero_get_payment_uri::from_property_tree(const boost::property_tree::ptree& node) { for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; if (key == std::string("uri")) return it->second.data(); @@ -1349,50 +1172,69 @@ std::string PyMoneroGetParsePaymentUri::from_property_tree(const boost::property throw std::runtime_error("Invalid make uri response"); } -void PyMoneroKeyValue::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& attributes) { - attributes->m_key = boost::none; - attributes->m_value = boost::none; - +void monero_get_payment_uri::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& response) { for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; - if (key == std::string("key")) attributes->m_key = it->second.data(); - else if (key == std::string("value")) attributes->m_value = it->second.data(); + if (key == std::string("uri")) { + monero_get_payment_uri::from_property_tree(it->second, response); + return; + } + if (key == std::string("address") && !it->second.data().empty()) response->m_address = it->second.data(); + else if (key == std::string("amount")) response->m_amount = it->second.get_value(); + else if (key == std::string("payment_id") && !it->second.data().empty()) response->m_payment_id = it->second.data(); + else if (key == std::string("recipient_name") && !it->second.data().empty()) response->m_recipient_name = it->second.data(); + else if (key == std::string("tx_description") && !it->second.data().empty()) response->m_tx_description = it->second.data(); } } -void PyMoneroCheckReserve::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& check) { - for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { - std::string key = it->first; - if (key == std::string("good")) check->m_is_good = it->second.get_value(); - else if (key == std::string("total")) check->m_total_amount = it->second.get_value(); - else if (key == std::string("spent")) check->m_unconfirmed_spent_amount = it->second.get_value(); - } +std::shared_ptr monero_get_payment_uri::to_tx_config() const { + auto tx_config = std::make_shared(); + tx_config->m_payment_id = m_payment_id; + tx_config->m_recipient_name = m_recipient_name; + tx_config->m_note = m_tx_description; + auto dest = std::make_shared(); + dest->m_amount = m_amount; + dest->m_address = m_address; + tx_config->m_destinations.push_back(dest); + return tx_config; +} - 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; - } +rapidjson::Value monero_get_payment_uri::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + rapidjson::Value root(rapidjson::kObjectType); + rapidjson::Value value_str(rapidjson::kStringType); + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, value_str); + if (m_amount != boost::none) monero_utils::add_json_member("amount", m_amount.get(), allocator, root, value_num); + if (m_payment_id != boost::none) monero_utils::add_json_member("payment_id", m_payment_id.get(), allocator, root, value_str); + if (m_recipient_name != boost::none) monero_utils::add_json_member("recipient_name", m_recipient_name.get(), allocator, root, value_str); + if (m_tx_description != boost::none) monero_utils::add_json_member("tx_description", m_tx_description.get(), allocator, root, value_str); + return root; } -void PyMoneroCheckTxProof::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& check) { +// --------------------------- MONERO KEY VALUE --------------------------- + +void monero_key_value::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& attributes) { + attributes->m_key = boost::none; + attributes->m_value = boost::none; + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; - if (key == std::string("good")) check->m_is_good = it->second.get_value(); - if (key == std::string("in_pool")) check->m_in_tx_pool = it->second.get_value(); - else if (key == std::string("confirmations")) check->m_num_confirmations = it->second.get_value(); - else if (key == std::string("received")) check->m_received_amount = it->second.get_value(); + if (key == std::string("key")) attributes->m_key = it->second.data(); + else if (key == std::string("value")) attributes->m_value = it->second.data(); } +} - if (!bool_equals_2(true, check->m_is_good)) { - // normalize invalid tx proof - check->m_in_tx_pool = boost::none; - check->m_num_confirmations = boost::none; - check->m_received_amount = boost::none; - } +rapidjson::Value monero_key_value::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + rapidjson::Value root(rapidjson::kObjectType); + rapidjson::Value value_str(rapidjson::kStringType); + if (m_key != boost::none) monero_utils::add_json_member("key", m_key.get(), allocator, root, value_str); + if (m_value != boost::none) monero_utils::add_json_member("value", m_value.get(), allocator, root, value_str); + return root; } -std::string PyMoneroReserveProofSignature::from_property_tree(const boost::property_tree::ptree& node) { +// --------------------------- MONERO SIGNATURE --------------------------- + +std::string monero_signature::from_property_tree(const boost::property_tree::ptree& node) { for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { std::string key = it->first; if (key == std::string("signature")) return it->second.data(); @@ -1401,73 +1243,191 @@ std::string PyMoneroReserveProofSignature::from_property_tree(const boost::prope throw std::runtime_error("Invalid reserve proof response"); } -void PyMoneroMessageSignatureResult::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr result) { - for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { - std::string key = it->first; - if (key == std::string("good")) result->m_is_good = it->second.get_value(); - else if (key == std::string("old")) result->m_is_old = it->second.get_value(); - else if (key == std::string("signature_type")) { - std::string sig_type = it->second.data(); - if (sig_type == std::string("view")) { - result->m_signature_type = monero::monero_message_signature_type::SIGN_WITH_VIEW_KEY; - } - else { - result->m_signature_type = monero::monero_message_signature_type::SIGN_WITH_SPEND_KEY; - } +// --------------------------- MONERO GET BALANCE PARAMS --------------------------- + +monero_get_balance_params::monero_get_balance_params(uint32_t account_idx, boost::optional address_idx, bool all_accounts, bool strict): + m_account_idx(account_idx), + m_all_accounts(all_accounts), + m_strict(strict) { + if (address_idx != boost::none) m_address_indices.push_back(address_idx.get()); +} + +rapidjson::Value monero_get_balance_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + rapidjson::Value root(rapidjson::kObjectType); + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_account_idx != boost::none) monero_utils::add_json_member("account_index", m_account_idx.get(), allocator, root, value_num); + if (!m_address_indices.empty()) root.AddMember("address_indices", monero_utils::to_rapidjson_val(allocator, m_address_indices), allocator); + if (m_all_accounts != boost::none) monero_utils::add_json_member("all_accounts", m_all_accounts.get(), allocator, root); + if (m_strict != boost::none) monero_utils::add_json_member("strict", m_strict.get(), allocator, root); + return root; +} + +// --------------------------- MONERO IMPORT EXPORT KEY IMAGES PARAMS --------------------------- + +monero_import_export_key_images_params::monero_import_export_key_images_params(const std::vector> &key_images) { + for(const auto &key_image : key_images) { + m_key_images.push_back(std::make_shared(*key_image)); + } +} + +rapidjson::Value monero_import_export_key_images_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + rapidjson::Value root(rapidjson::kObjectType); + rapidjson::Value val_str(rapidjson::kStringType); + if (m_all != boost::none) monero_utils::add_json_member("all", m_all.get(), allocator, root); + else if (m_key_images.size() > 0) { + rapidjson::Value value_arr(rapidjson::kArrayType); + + for (const auto &key_image : m_key_images) { + value_arr.PushBack(key_image->to_rapidjson_val(allocator), allocator); } - else if (key == std::string("version")) result->m_version = it->second.get_value(); + root.AddMember("signed_key_images", value_arr, allocator); + return root; } + return root; } -rapidjson::Value PyMoneroAccountTag::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { - // create root +// --------------------------- MONERO SWEEP PARAMS --------------------------- + +monero_sweep_params::monero_sweep_params(const monero_tx_config& config): + m_address(config.m_address), + m_account_index(config.m_account_index), + m_subaddr_indices(config.m_subaddress_indices), + m_key_image(config.m_key_image), + m_relay(config.m_relay), + m_priority(config.m_priority), + m_payment_id(config.m_payment_id), + m_below_amount(config.m_below_amount), + m_get_tx_key(true), + m_get_tx_hex(true), + m_get_tx_metadata(true) { +} + +rapidjson::Value monero_sweep_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); + rapidjson::Value val_str(rapidjson::kStringType); + rapidjson::Value val_num(rapidjson::kNumberType); - // set string values - rapidjson::Value value_str(rapidjson::kStringType); - if (m_tag != boost::none) monero_utils::add_json_member("tag", m_tag.get(), allocator, root, value_str); - if (m_label != boost::none) monero_utils::add_json_member("label", m_label.get(), allocator, root, value_str); - if (!m_account_indices.empty()) root.AddMember("accountIndices", monero_utils::to_rapidjson_val(allocator, m_account_indices), allocator); + if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, val_str); + if (m_account_index != boost::none) monero_utils::add_json_member("account_index", m_account_index.get(), allocator, root, val_num); + if (m_subaddr_indices.size() > 0) root.AddMember("subaddr_indices", monero_utils::to_rapidjson_val(allocator, m_subaddr_indices), allocator); + if (m_key_image != boost::none) monero_utils::add_json_member("key_image", m_key_image.get(), allocator, root, val_str); + if (m_priority != boost::none) monero_utils::add_json_member("priority", m_priority.get(), allocator, root, val_num); + if (m_payment_id != boost::none) monero_utils::add_json_member("payment_id", m_payment_id.get(), allocator, root, val_str); + if (m_get_tx_key != boost::none) monero_utils::add_json_member("get_tx_key", m_get_tx_key.get(), allocator, root); + if (m_get_tx_keys != boost::none) monero_utils::add_json_member("get_tx_keys", m_get_tx_keys.get(), allocator, root); + if (m_get_tx_hex != boost::none) monero_utils::add_json_member("get_tx_hex", m_get_tx_hex.get(), allocator, root); + if (m_get_tx_metadata != boost::none) monero_utils::add_json_member("get_tx_metadata", m_get_tx_metadata.get(), allocator, root); + if (m_below_amount != boost::none) monero_utils::add_json_member("below_amount", m_below_amount.get(), allocator, root, val_num); - // return root + bool relay = bool_equals_2(true, m_relay); + monero_utils::add_json_member("do_not_relay", !relay, allocator, root); return root; } -rapidjson::Value PyMoneroKeyImage::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { - // create root +// --------------------------- MONERO TRANSFER PARAMS --------------------------- + +monero_transfer_params::monero_transfer_params(const monero::monero_tx_config &config) { + for (const auto& sub_idx : config.m_subaddress_indices) { + m_subaddress_indices.push_back(sub_idx); + } + + if (config.m_address != boost::none) { + auto dest = std::make_shared(); + dest->m_address = config.m_address; + dest->m_amount = config.m_amount; + m_destinations.push_back(dest); + } + + for (const auto &dest : config.m_destinations) { + if (dest->m_address == boost::none) throw std::runtime_error("Destination address is not defined"); + if (dest->m_amount == boost::none) throw std::runtime_error("Destination amount is not defined"); + if (config.m_address != boost::none && *dest->m_address == *config.m_address) continue; + m_destinations.push_back(dest); + } + + m_subtract_fee_from_outputs = config.m_subtract_fee_from; + m_account_index = config.m_account_index; + m_payment_id = config.m_payment_id; + if (bool_equals_2(true, config.m_relay)) { + m_do_not_relay = false; + } + else { + m_do_not_relay = true; + } + if (config.m_priority == monero_tx_priority::DEFAULT) { + m_priority = 0; + } + else if (config.m_priority == monero_tx_priority::UNIMPORTANT) { + m_priority = 1; + } + else if (config.m_priority == monero_tx_priority::NORMAL) { + m_priority = 2; + } + else if (config.m_priority == monero_tx_priority::ELEVATED) { + m_priority = 3; + } + m_get_tx_hex = true; + m_get_tx_metadata = true; + if (bool_equals_2(true, config.m_can_split)) m_get_tx_keys = true; + else m_get_tx_key = true; +} + +rapidjson::Value monero_transfer_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); + rapidjson::Value value_num(rapidjson::kNumberType); + rapidjson::Value value_str(rapidjson::kStringType); + if (!m_subtract_fee_from_outputs.empty()) root.AddMember("subtract_fee_from_outputs", monero_utils::to_rapidjson_val(allocator, m_subtract_fee_from_outputs), allocator); + if (m_account_index != boost::none) monero_utils::add_json_member("account_index", m_account_index.get(), allocator, root, value_num); + if (!m_subaddress_indices.empty()) root.AddMember("subaddr_indices", monero_utils::to_rapidjson_val(allocator, m_subaddress_indices), allocator); + if (m_payment_id != boost::none) monero_utils::add_json_member("payment_id", m_payment_id.get(), allocator, root, value_str); + if (m_do_not_relay != boost::none) monero_utils::add_json_member("do_not_relay", m_do_not_relay.get(), allocator, root); + if (m_priority != boost::none) monero_utils::add_json_member("priority", m_priority.get(), allocator, root, value_num); + if (m_get_tx_hex != boost::none) monero_utils::add_json_member("get_tx_hex", m_get_tx_hex.get(), allocator, root); + if (m_get_tx_metadata != boost::none) monero_utils::add_json_member("get_tx_metadata", m_get_tx_metadata.get(), allocator, root); + if (m_get_tx_keys != boost::none) monero_utils::add_json_member("get_tx_keys", m_get_tx_keys.get(), allocator, root); + if (m_get_tx_key != boost::none) monero_utils::add_json_member("get_tx_key", m_get_tx_key.get(), allocator, root); + if (!m_destinations.empty()) { + rapidjson::Value value_arr(rapidjson::kArrayType); - // set string values - rapidjson::Value value_str(rapidjson::kStringType); - if (m_hex != boost::none) monero_utils::add_json_member("key_image", m_hex.get(), allocator, root, value_str); - if (m_signature != boost::none) monero_utils::add_json_member("signature", m_signature.get(), allocator, root, value_str); + for (const auto &dest : m_destinations) { + value_arr.PushBack(dest->to_rapidjson_val(allocator), allocator); + } - // return root + root.AddMember("destinations", value_arr, allocator); + } return root; } -rapidjson::Value PyMoneroMultisigTxDataParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO MULTISIG TX DATA PARAMS --------------------------- + +rapidjson::Value monero_multisig_tx_data_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); rapidjson::Value value_str(rapidjson::kStringType); if (m_multisig_tx_hex != boost::none) monero_utils::add_json_member("tx_data_hex", m_multisig_tx_hex.get(), allocator, root, value_str); return root; } -rapidjson::Value PyMoneroQueryKeyParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO QUERY KEY PARAMS --------------------------- + +rapidjson::Value monero_query_key_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); rapidjson::Value value_str(rapidjson::kStringType); if (m_key_type != boost::none) monero_utils::add_json_member("key_type", m_key_type.get(), allocator, root, value_str); return root; } -rapidjson::Value PyMoneroQueryOutputParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO QUERY OUTPUT PARAMS --------------------------- + +rapidjson::Value monero_query_output_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); rapidjson::Value value_str(rapidjson::kStringType); if (m_key_image != boost::none) monero_utils::add_json_member("key_image", m_key_image.get(), allocator, root, value_str); return root; } -rapidjson::Value PyMoneroGetAddressParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO GET ADDRESS PARAMS --------------------------- + +rapidjson::Value monero_get_address_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); rapidjson::Value value_num(rapidjson::kNumberType); if (!m_subaddress_indices.empty()) root.AddMember("address_index", monero_utils::to_rapidjson_val(allocator, m_subaddress_indices), allocator); @@ -1475,14 +1435,18 @@ rapidjson::Value PyMoneroGetAddressParams::to_rapidjson_val(rapidjson::Document: return root; } -rapidjson::Value PyMoneroGetAddressIndexParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO GET ADDRESS INDEX PARAMS --------------------------- + +rapidjson::Value monero_get_address_index_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); rapidjson::Value value_str(rapidjson::kStringType); if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, value_str); return root; } -rapidjson::Value PyMoneroMakeIntegratedAddressParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO MAKE INTEGRATED ADDRESS PARAMS --------------------------- + +rapidjson::Value monero_make_integrated_address_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); rapidjson::Value value_str(rapidjson::kStringType); if (m_standard_address != boost::none) monero_utils::add_json_member("standard_address", m_standard_address.get(), allocator, root, value_str); @@ -1490,26 +1454,34 @@ rapidjson::Value PyMoneroMakeIntegratedAddressParams::to_rapidjson_val(rapidjson return root; } -rapidjson::Value PyMoneroSplitIntegratedAddressParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO SPLIT INTEGRATED ADDRESS PARAMS --------------------------- + +rapidjson::Value monero_split_integrated_address_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); rapidjson::Value value_str(rapidjson::kStringType); if (m_integrated_address != boost::none) monero_utils::add_json_member("integrated_address", m_integrated_address.get(), allocator, root, value_str); return root; } -rapidjson::Value PyMoneroPrepareMultisigParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO PREPARE MULTISIG PARAMS --------------------------- + +rapidjson::Value monero_prepare_multisig_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); if (m_enable_multisig_experimental != boost::none) monero_utils::add_json_member("enable_multisig_experimental", m_enable_multisig_experimental.get(), allocator, root); return root; } -rapidjson::Value PyMoneroImportMultisigHexParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO IMPORT MULTISIG HEX PARAMS --------------------------- + +rapidjson::Value monero_import_multisig_hex_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); if (!m_multisig_hexes.empty()) root.AddMember("info", monero_utils::to_rapidjson_val(allocator, m_multisig_hexes), allocator); return root; } -rapidjson::Value PyMoneroMakeMultisigParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO MAKE MULTISIG PARAMS --------------------------- + +rapidjson::Value monero_make_multisig_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); rapidjson::Value val_num(rapidjson::kNumberType); rapidjson::Value val_str(rapidjson::kStringType); @@ -1519,49 +1491,35 @@ rapidjson::Value PyMoneroMakeMultisigParams::to_rapidjson_val(rapidjson::Documen return root; } -rapidjson::Value PyMoneroParsePaymentUriParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO PARSE PAYMENT URI PARAMS --------------------------- + +rapidjson::Value monero_parse_payment_uri_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); rapidjson::Value val_str(rapidjson::kStringType); if (m_uri != boost::none) monero_utils::add_json_member("uri", m_uri.get(), allocator, root, val_str); return root; } -rapidjson::Value PyMoneroGetParsePaymentUri::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { - rapidjson::Value root(rapidjson::kObjectType); - rapidjson::Value value_str(rapidjson::kStringType); - rapidjson::Value value_num(rapidjson::kNumberType); - if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, value_str); - if (m_amount != boost::none) monero_utils::add_json_member("amount", m_amount.get(), allocator, root, value_num); - if (m_payment_id != boost::none) monero_utils::add_json_member("payment_id", m_payment_id.get(), allocator, root, value_str); - if (m_recipient_name != boost::none) monero_utils::add_json_member("recipient_name", m_recipient_name.get(), allocator, root, value_str); - if (m_tx_description != boost::none) monero_utils::add_json_member("tx_description", m_tx_description.get(), allocator, root, value_str); - return root; -} - -rapidjson::Value PyMoneroGetBalanceParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { - rapidjson::Value root(rapidjson::kObjectType); - rapidjson::Value value_num(rapidjson::kNumberType); - if (m_account_idx != boost::none) monero_utils::add_json_member("account_index", m_account_idx.get(), allocator, root, value_num); - if (!m_address_indices.empty()) root.AddMember("address_indices", monero_utils::to_rapidjson_val(allocator, m_address_indices), allocator); - if (m_all_accounts != boost::none) monero_utils::add_json_member("all_accounts", m_all_accounts.get(), allocator, root); - if (m_strict != boost::none) monero_utils::add_json_member("strict", m_strict.get(), allocator, root); - return root; -} +// --------------------------- MONERO CREATE ACCOUNT PARAMS --------------------------- -rapidjson::Value PyMoneroCreateAccountParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +rapidjson::Value monero_create_account_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); rapidjson::Value value_str(rapidjson::kStringType); if (m_tag != boost::none) monero_utils::add_json_member("label", m_tag.get(), allocator, root, value_str); return root; } -rapidjson::Value PyMoneroCloseWalletParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO CLOSE WALLET PARAMS --------------------------- + +rapidjson::Value monero_close_wallet_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); if (m_save != boost::none) monero_utils::add_json_member("autosave_current", m_save.get(), allocator, root); return root; } -rapidjson::Value PyMoneroChangeWalletPasswordParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO CHANGE WALLET PASSWORD PARAMS --------------------------- + +rapidjson::Value monero_change_wallet_password_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); rapidjson::Value value_str(rapidjson::kStringType); if (m_old_password != boost::none) monero_utils::add_json_member("old_password", m_old_password.get(), allocator, root, value_str); @@ -1569,15 +1527,9 @@ rapidjson::Value PyMoneroChangeWalletPasswordParams::to_rapidjson_val(rapidjson: return root; } -rapidjson::Value PyMoneroKeyValue::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { - rapidjson::Value root(rapidjson::kObjectType); - rapidjson::Value value_str(rapidjson::kStringType); - if (m_key != boost::none) monero_utils::add_json_member("key", m_key.get(), allocator, root, value_str); - if (m_value != boost::none) monero_utils::add_json_member("value", m_value.get(), allocator, root, value_str); - return root; -} +// --------------------------- MONERO SET DAEMON PARAMS --------------------------- -rapidjson::Value PyMoneroSetDaemonParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +rapidjson::Value monero_set_daemon_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); rapidjson::Value value_str(rapidjson::kStringType); if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, value_str); @@ -1594,13 +1546,17 @@ rapidjson::Value PyMoneroSetDaemonParams::to_rapidjson_val(rapidjson::Document:: return root; } -rapidjson::Value PyMoneroAutoRefreshParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO AUTO REFRESH PARAMS --------------------------- + +rapidjson::Value monero_auto_refresh_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); if (m_enable != boost::none) monero_utils::add_json_member("enable", m_enable.get(), allocator, root); return root; } -rapidjson::Value PyMoneroTagAccountsParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO TAG ACCOUNT PARAMS --------------------------- + +rapidjson::Value monero_tag_accounts_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); rapidjson::Value value_str(rapidjson::kStringType); if (m_tag != boost::none) monero_utils::add_json_member("tag", m_tag.get(), allocator, root, value_str); @@ -1609,14 +1565,18 @@ rapidjson::Value PyMoneroTagAccountsParams::to_rapidjson_val(rapidjson::Document return root; } -rapidjson::Value PyMoneroTxNotesParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO TX NOTES PARAMS --------------------------- + +rapidjson::Value monero_tx_notes_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); if (!m_tx_hashes.empty()) root.AddMember("txids", monero_utils::to_rapidjson_val(allocator, m_tx_hashes), allocator); if (!m_notes.empty()) root.AddMember("notes", monero_utils::to_rapidjson_val(allocator, m_notes), allocator); return root; } -rapidjson::Value PyMoneroAddressBookEntryParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO ADDRESS BOOK ENTRY PARAMS --------------------------- + +rapidjson::Value monero_address_book_entry_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); rapidjson::Value value_str(rapidjson::kStringType); rapidjson::Value value_num(rapidjson::kNumberType); @@ -1629,7 +1589,9 @@ rapidjson::Value PyMoneroAddressBookEntryParams::to_rapidjson_val(rapidjson::Doc return root; } -rapidjson::Value PyMoneroVerifySignMessageParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO VERIFY SIGN MESSAGE PARAMS --------------------------- + +rapidjson::Value monero_verify_sign_message_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); rapidjson::Value value_str(rapidjson::kStringType); rapidjson::Value value_num(rapidjson::kNumberType); @@ -1650,7 +1612,9 @@ rapidjson::Value PyMoneroVerifySignMessageParams::to_rapidjson_val(rapidjson::Do return root; } -rapidjson::Value PyMoneroCheckTxKeyParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO CHECK TX KEY PARAMS --------------------------- + +rapidjson::Value monero_check_tx_key_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); rapidjson::Value value_str(rapidjson::kStringType); if (m_tx_hash != boost::none) monero_utils::add_json_member("txid", m_tx_hash.get(), allocator, root, value_str); @@ -1659,7 +1623,9 @@ rapidjson::Value PyMoneroCheckTxKeyParams::to_rapidjson_val(rapidjson::Document: return root; } -rapidjson::Value PyMoneroSignDescribeTransferParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO SIGN DESCRIBE TRANSFER PARAMS --------------------------- + +rapidjson::Value monero_sign_describe_transfer_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); rapidjson::Value value_str(rapidjson::kStringType); if (m_unsigned_txset != boost::none) monero_utils::add_json_member("unsigned_txset", m_unsigned_txset.get(), allocator, root, value_str); @@ -1668,43 +1634,27 @@ rapidjson::Value PyMoneroSignDescribeTransferParams::to_rapidjson_val(rapidjson: return root; } -rapidjson::Value PyMoneroWalletRelayTxParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO WALLET RELAY TX PARAMS --------------------------- + +rapidjson::Value monero_wallet_relay_tx_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); rapidjson::Value value_str(rapidjson::kStringType); if (m_hex != boost::none) monero_utils::add_json_member("hex", m_hex.get(), allocator, root, value_str); return root; } -rapidjson::Value PyMoneroSweepParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { - rapidjson::Value root(rapidjson::kObjectType); - rapidjson::Value val_str(rapidjson::kStringType); - rapidjson::Value val_num(rapidjson::kNumberType); - - if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, val_str); - if (m_account_index != boost::none) monero_utils::add_json_member("account_index", m_account_index.get(), allocator, root, val_num); - if (m_subaddr_indices.size() > 0) root.AddMember("subaddr_indices", monero_utils::to_rapidjson_val(allocator, m_subaddr_indices), allocator); - if (m_key_image != boost::none) monero_utils::add_json_member("key_image", m_key_image.get(), allocator, root, val_str); - if (m_priority != boost::none) monero_utils::add_json_member("priority", m_priority.get(), allocator, root, val_num); - if (m_payment_id != boost::none) monero_utils::add_json_member("payment_id", m_payment_id.get(), allocator, root, val_str); - if (m_get_tx_key != boost::none) monero_utils::add_json_member("get_tx_key", m_get_tx_key.get(), allocator, root); - if (m_get_tx_keys != boost::none) monero_utils::add_json_member("get_tx_keys", m_get_tx_keys.get(), allocator, root); - if (m_get_tx_hex != boost::none) monero_utils::add_json_member("get_tx_hex", m_get_tx_hex.get(), allocator, root); - if (m_get_tx_metadata != boost::none) monero_utils::add_json_member("get_tx_metadata", m_get_tx_metadata.get(), allocator, root); - if (m_below_amount != boost::none) monero_utils::add_json_member("below_amount", m_below_amount.get(), allocator, root, val_num); - - bool relay = bool_equals_2(true, m_relay); - monero_utils::add_json_member("do_not_relay", !relay, allocator, root); - return root; -} +// --------------------------- MONERO SUBMIT TRANSFER PARAMS --------------------------- -rapidjson::Value PyMoneroSubmitTransferParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +rapidjson::Value monero_submit_transfer_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); rapidjson::Value val_str(rapidjson::kStringType); if (m_signed_tx_hex != boost::none) monero_utils::add_json_member("tx_data_hex", m_signed_tx_hex.get(), allocator, root, val_str); return root; } -rapidjson::Value PyMoneroCreateEditSubaddressParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO CREATE EDIT SUBADDRESS PARAMS --------------------------- + +rapidjson::Value monero_create_edit_subaddress_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); rapidjson::Value val_str(rapidjson::kStringType); rapidjson::Value val_num(rapidjson::kNumberType); @@ -1719,7 +1669,9 @@ rapidjson::Value PyMoneroCreateEditSubaddressParams::to_rapidjson_val(rapidjson: return root; } -rapidjson::Value PyMoneroImportExportOutputsParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO IMPORT EXPORT OUTPUTS PARAMS --------------------------- + +rapidjson::Value monero_import_export_outputs_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); rapidjson::Value val_str(rapidjson::kStringType); if (m_all != boost::none) monero_utils::add_json_member("all", m_all.get(), allocator, root); @@ -1727,23 +1679,9 @@ rapidjson::Value PyMoneroImportExportOutputsParams::to_rapidjson_val(rapidjson:: return root; } -rapidjson::Value PyMoneroImportExportKeyImagesParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { - rapidjson::Value root(rapidjson::kObjectType); - rapidjson::Value val_str(rapidjson::kStringType); - if (m_all != boost::none) monero_utils::add_json_member("all", m_all.get(), allocator, root); - else if (m_key_images.size() > 0) { - rapidjson::Value value_arr(rapidjson::kArrayType); - - for (const auto &key_image : m_key_images) { - value_arr.PushBack(key_image->to_rapidjson_val(allocator), allocator); - } - root.AddMember("signed_key_images", value_arr, allocator); - return root; - } - return root; -} +// --------------------------- MONERO CREATE OPEN WALLET PARAMS --------------------------- -rapidjson::Value PyMoneroCreateOpenWalletParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +rapidjson::Value monero_create_open_wallet_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); rapidjson::Value val_str(rapidjson::kStringType); rapidjson::Value val_num(rapidjson::kNumberType); @@ -1761,7 +1699,9 @@ rapidjson::Value PyMoneroCreateOpenWalletParams::to_rapidjson_val(rapidjson::Doc return root; } -rapidjson::Value PyMoneroReserveProofParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO RESERVE PROOF PARAMS --------------------------- + +rapidjson::Value monero_reserve_proof_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); rapidjson::Value value_str(rapidjson::kStringType); rapidjson::Value value_num(rapidjson::kNumberType); @@ -1775,7 +1715,9 @@ rapidjson::Value PyMoneroReserveProofParams::to_rapidjson_val(rapidjson::Documen return root; } -rapidjson::Value PyMoneroRefreshWalletParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO REFRESH WALLET PARAMS --------------------------- + +rapidjson::Value monero_refresh_wallet_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); rapidjson::Value value_num(rapidjson::kNumberType); if (m_enable != boost::none) monero_utils::add_json_member("enable", m_enable.get(), allocator, root); @@ -1784,33 +1726,9 @@ rapidjson::Value PyMoneroRefreshWalletParams::to_rapidjson_val(rapidjson::Docume return root; } -rapidjson::Value PyMoneroTransferParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { - rapidjson::Value root(rapidjson::kObjectType); - rapidjson::Value value_num(rapidjson::kNumberType); - rapidjson::Value value_str(rapidjson::kStringType); - if (!m_subtract_fee_from_outputs.empty()) root.AddMember("subtract_fee_from_outputs", monero_utils::to_rapidjson_val(allocator, m_subtract_fee_from_outputs), allocator); - if (m_account_index != boost::none) monero_utils::add_json_member("account_index", m_account_index.get(), allocator, root, value_num); - if (!m_subaddress_indices.empty()) root.AddMember("subaddr_indices", monero_utils::to_rapidjson_val(allocator, m_subaddress_indices), allocator); - if (m_payment_id != boost::none) monero_utils::add_json_member("payment_id", m_payment_id.get(), allocator, root, value_str); - if (m_do_not_relay != boost::none) monero_utils::add_json_member("do_not_relay", m_do_not_relay.get(), allocator, root); - if (m_priority != boost::none) monero_utils::add_json_member("priority", m_priority.get(), allocator, root, value_num); - if (m_get_tx_hex != boost::none) monero_utils::add_json_member("get_tx_hex", m_get_tx_hex.get(), allocator, root); - if (m_get_tx_metadata != boost::none) monero_utils::add_json_member("get_tx_metadata", m_get_tx_metadata.get(), allocator, root); - if (m_get_tx_keys != boost::none) monero_utils::add_json_member("get_tx_keys", m_get_tx_keys.get(), allocator, root); - if (m_get_tx_key != boost::none) monero_utils::add_json_member("get_tx_key", m_get_tx_key.get(), allocator, root); - if (!m_destinations.empty()) { - rapidjson::Value value_arr(rapidjson::kArrayType); - - for (const auto &dest : m_destinations) { - value_arr.PushBack(dest->to_rapidjson_val(allocator), allocator); - } - - root.AddMember("destinations", value_arr, allocator); - } - return root; -} +// --------------------------- MONERO GET INCOMING TRANSFERS PARAMS --------------------------- -rapidjson::Value PyMoneroGetIncomingTransfersParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +rapidjson::Value monero_get_incoming_transfers_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); rapidjson::Value value_num(rapidjson::kNumberType); rapidjson::Value value_str(rapidjson::kStringType); @@ -1821,7 +1739,9 @@ rapidjson::Value PyMoneroGetIncomingTransfersParams::to_rapidjson_val(rapidjson: return root; } -rapidjson::Value PyMoneroGetTransfersParams::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { +// --------------------------- MONERO GET TRANSFERS PARAMS --------------------------- + +rapidjson::Value monero_get_transfers_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { rapidjson::Value root(rapidjson::kObjectType); rapidjson::Value value_num(rapidjson::kNumberType); rapidjson::Value value_str(rapidjson::kStringType); @@ -1840,14 +1760,136 @@ rapidjson::Value PyMoneroGetTransfersParams::to_rapidjson_val(rapidjson::Documen return root; } -std::shared_ptr PyMoneroGetParsePaymentUri::to_tx_config() const { - auto tx_config = std::make_shared(); - tx_config->m_payment_id = m_payment_id; - tx_config->m_recipient_name = m_recipient_name; - tx_config->m_note = m_tx_description; - auto dest = std::make_shared(); - dest->m_amount = m_amount; - dest->m_address = m_address; - tx_config->m_destinations.push_back(dest); - return tx_config; +// --------------------------- MONERO GET HEIGHT RESPONSE --------------------------- + +uint64_t monero_wallet_get_height_response::from_property_tree(const boost::property_tree::ptree& node) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("height")) return it->second.get_value(); + } + throw std::runtime_error("Invalid get_height response"); +} + +// --------------------------- MONERO IMPORT MULTISIG HEX RESPONSE --------------------------- + +int monero_import_multisig_hex_response::from_property_tree(const boost::property_tree::ptree& node) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("n_outputs")) return it->second.get_value(); + } + throw std::runtime_error("Invalid prepare multisig response"); +} + +// --------------------------- MONERO GET BALANCE RESPONSE --------------------------- + +void monero_get_balance_response::from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& response) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("balance")) response->m_balance = it->second.get_value(); + else if (key == std::string("unlocked_balance")) response->m_unlocked_balance = it->second.get_value(); + else if (key == std::string("multisig_import_needed")) response->m_multisig_import_needed = it->second.get_value(); + else if (key == std::string("time_to_unlock")) response->m_time_to_unlock = it->second.get_value(); + else if (key == std::string("blocks_to_unlock")) response->m_blocks_to_unlock = it->second.get_value(); + else if (key == std::string("per_subaddress")) { + auto node2 = it->second; + + for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) { + auto sub = std::make_shared(); + PyMoneroSubaddress::from_rpc_property_tree(it2->second, sub); + response->m_per_subaddress.push_back(sub); + } + } + } +} + +// --------------------------- MONERO EXPORT MULTISIG HEX RESPONSE --------------------------- + +std::string monero_export_multisig_hex_response::from_property_tree(const boost::property_tree::ptree& node) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("info")) return it->second.data(); + } + throw std::runtime_error("Invalid prepare multisig response"); +} + +// --------------------------- MONERO SUBMIT MULTISIG TX HEX RESPONSE --------------------------- + +std::vector monero_submit_multisig_tx_hex_response::from_property_tree(const boost::property_tree::ptree& node) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("tx_hash_list")) { + auto node2 = it->second; + std::vector hashes; + for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) { + hashes.push_back(it2->second.data()); + } + + return hashes; + } + } + throw std::runtime_error("Invalid prepare multisig response"); +} + +// --------------------------- MONERO PREPARE MAKE MULTISIG RESPONSE --------------------------- + +std::string monero_prepare_make_multisig_response::from_property_tree(const boost::property_tree::ptree& node) { + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("multisig_info")) return it->second.data(); + } + throw std::runtime_error("Invalid prepare multisig response"); +} + +/** + * ---------------- DUPLICATED MONERO-CPP WALLET FULL CODE --------------------- + */ + +bool bool_equals_2(bool val, const boost::optional& opt_val) { + return opt_val == boost::none ? false : val == *opt_val; +} + +/** + * Returns true iff tx1's height is known to be less than tx2's height for sorting. + */ +bool tx_height_less_than(const std::shared_ptr& tx1, const std::shared_ptr& tx2) { + if (tx1->m_block != boost::none && tx2->m_block != boost::none) return tx1->get_height() < tx2->get_height(); + else if (tx1->m_block == boost::none) return false; + else return true; +} + +/** + * Returns true iff transfer1 is ordered before transfer2 by ascending account and subaddress indices. + */ +bool incoming_transfer_before(const std::shared_ptr& transfer1, const std::shared_ptr& transfer2) { + + // compare by height + if (tx_height_less_than(transfer1->m_tx, transfer2->m_tx)) return true; + + // compare by account and subaddress index + if (transfer1->m_account_index.get() < transfer2->m_account_index.get()) return true; + else if (transfer1->m_account_index.get() == transfer2->m_account_index.get()) return transfer1->m_subaddress_index.get() < transfer2->m_subaddress_index.get(); + else return false; +} + +/** + * Returns true iff wallet vout1 is ordered before vout2 by ascending account and subaddress indices then index. + */ +bool vout_before(const std::shared_ptr& o1, const std::shared_ptr& o2) { + if (o1 == o2) return false; // ignore equal references + std::shared_ptr ow1 = std::static_pointer_cast(o1); + std::shared_ptr ow2 = std::static_pointer_cast(o2); + + // compare by height + if (tx_height_less_than(ow1->m_tx, ow2->m_tx)) return true; + + // compare by account index, subaddress index, output index, then key image hex + if (ow1->m_account_index.get() < ow2->m_account_index.get()) return true; + if (ow1->m_account_index.get() == ow2->m_account_index.get()) { + if (ow1->m_subaddress_index.get() < ow2->m_subaddress_index.get()) return true; + if (ow1->m_subaddress_index.get() == ow2->m_subaddress_index.get()) { + if (ow1->m_index.get() < ow2->m_index.get()) return true; + if (ow1->m_index.get() == ow2->m_index.get()) throw std::runtime_error("Should never sort outputs with duplicate indices"); + } + } + return false; } diff --git a/src/cpp/wallet/py_monero_wallet_model.h b/src/cpp/wallet/py_monero_wallet_model.h index ee5746b..d301188 100644 --- a/src/cpp/wallet/py_monero_wallet_model.h +++ b/src/cpp/wallet/py_monero_wallet_model.h @@ -1,76 +1,82 @@ +/** + * Copyright (c) everoddandeven + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2025-2026 woodser + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ #pragma once #include "daemon/py_monero_daemon_model.h" #include "wallet/monero_wallet_model.h" -template -void set_query(std::shared_ptr& self, boost::optional>& field, const boost::optional>& val) { - - const auto old = field; - field = val; - - // set new reference - if (field != boost::none) { - field.get()->m_tx_query = self; - } +// ------------------------------ Custom Data Model --------------------------------- - // cleanup old - if (old != boost::none) { - if (val != boost::none && val.get() == old.get()) { - return; - } - old.get()->m_tx_query = boost::none; - } -} - -enum PyMoneroAddressType : uint8_t { - PRIMARY_ADDRESS = 0, - INTEGRATED_ADDRESS, - SUBADDRESS -}; - -// Compares two transactions by their height -struct PyTxHeightComparator { - bool operator()(const std::shared_ptr& tx1, const std::shared_ptr& tx2) const; -}; - -// Compares two transfers by ascending account and subaddress indices -struct PyIncomingTransferComparator { - bool operator()(const std::shared_ptr& t1, const std::shared_ptr& t2) const; - bool operator()(const monero::monero_incoming_transfer& t1, const monero::monero_incoming_transfer& t2) const; -}; - -// Compares two outputs by ascending account and subaddress indices -struct PyOutputComparator { - bool operator()(const monero::monero_output_wallet& o1, const monero::monero_output_wallet& o2) const; -}; - -class PyMoneroTxQuery : public monero::monero_tx_query { +struct PyMoneroTxQuery : public monero::monero_tx_query { public: static std::shared_ptr decontextualize(const std::shared_ptr &query); }; -class PyMoneroOutputQuery : public monero::monero_output_query { +struct PyMoneroOutputQuery : public monero::monero_output_query { public: static bool is_contextual(const monero::monero_output_query &query); }; -class PyMoneroTransferQuery : public monero::monero_transfer_query { +struct PyMoneroTransferQuery : public monero::monero_transfer_query { public: static bool is_contextual(const monero::monero_transfer_query &query); }; -struct PyMoneroWalletConfig : public monero::monero_wallet_config { -public: - - PyMoneroWalletConfig() { } - PyMoneroWalletConfig(const PyMoneroWalletConfig& config); -}; - -class PyMoneroTxWallet : public monero::monero_tx_wallet { +struct PyMoneroTxWallet : public monero::monero_tx_wallet { public: static bool decode_rpc_type(const std::string &rpc_type, const std::shared_ptr &tx); @@ -84,7 +90,7 @@ class PyMoneroTxWallet : public monero::monero_tx_wallet { static void merge_tx(const std::shared_ptr& tx, std::unordered_map>& tx_map, std::unordered_map>& block_map); }; -class PyMoneroTxSet : public monero::monero_tx_set { +struct PyMoneroTxSet : public monero::monero_tx_set { public: static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& set); @@ -94,7 +100,7 @@ class PyMoneroTxSet : public monero::monero_tx_set { static void from_describe_transfer(const boost::property_tree::ptree& node, const std::shared_ptr& set); }; -class PyMoneroKeyImage : public monero::monero_key_image { +struct PyMoneroKeyImage : public monero::monero_key_image { public: PyMoneroKeyImage(const monero::monero_key_image &key_image); @@ -104,222 +110,258 @@ class PyMoneroKeyImage : public monero::monero_key_image { static void from_property_tree(const boost::property_tree::ptree& node, std::vector>& key_images); }; -class PyMoneroKeyImageImportResult : public monero::monero_key_image_import_result { +struct PyMoneroKeyImageImportResult : public monero::monero_key_image_import_result { public: static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& result); }; -class PyMoneroMultisigInfo : public monero::monero_multisig_info { +struct PyMoneroMultisigInfo : public monero::monero_multisig_info { public: static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& info); }; -class PyMoneroMultisigInitResult : public monero::monero_multisig_init_result { +struct PyMoneroMultisigInitResult : public monero::monero_multisig_init_result { public: static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& info); }; -class PyMoneroMultisigSignResult : public monero::monero_multisig_sign_result { +struct PyMoneroMultisigSignResult : public monero::monero_multisig_sign_result { public: static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& res); }; -class PyMoneroMultisigTxDataParams : public PyMoneroJsonRequestParams { +struct PyMoneroTransfer : public monero_transfer { public: - boost::optional m_multisig_tx_hex; + using monero_transfer::monero_transfer; - PyMoneroMultisigTxDataParams(const std::string& multisig_tx_hex): m_multisig_tx_hex(multisig_tx_hex) { } + boost::optional is_incoming() const override { + PYBIND11_OVERRIDE_PURE(boost::optional, monero_transfer, is_incoming); + } +}; - rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; +struct PyMoneroSubaddress : public monero::monero_subaddress { +public: + + static void from_rpc_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& subaddress); + static void from_rpc_property_tree(const boost::property_tree::ptree& node, std::vector>& subaddresses); + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& subaddress); }; -class PyMoneroDecodedAddress { +struct PyMoneroIntegratedAddress : public monero::monero_integrated_address { public: - std::string m_address; - PyMoneroAddressType m_address_type; - monero::monero_network_type m_network_type; - PyMoneroDecodedAddress(const std::string& address, PyMoneroAddressType address_type, monero::monero_network_type network_type); + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& subaddress); }; -struct PyMoneroAccountTag : public monero::serializable_struct { +struct PyMoneroAccount : public monero::monero_account { public: - boost::optional m_tag; - boost::optional m_label; - std::vector m_account_indices; - PyMoneroAccountTag() { } - PyMoneroAccountTag(const std::string& tag, const std::string& label): m_tag(tag), m_label(label) { } - PyMoneroAccountTag(const std::string& tag, const std::string& label, const std::vector& account_indices): m_tag(tag), m_label(label), m_account_indices(account_indices) { } + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& account); + static void from_property_tree(const boost::property_tree::ptree& node, std::vector>& accounts); + static void from_property_tree(const boost::property_tree::ptree& node, std::vector& accounts); +}; - rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; +struct PyMoneroAddressBookEntry : public monero::monero_address_book_entry { +public: - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& account_tag); - static void from_property_tree(const boost::property_tree::ptree& node, std::vector>& account_tags); + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& entry); + static void from_property_tree(const boost::property_tree::ptree& node, std::vector>& entries); }; -class PyMoneroTransfer : public monero_transfer { +struct PyMoneroCheckReserve : public monero::monero_check_reserve { public: - using monero_transfer::monero_transfer; - boost::optional is_incoming() const override { - PYBIND11_OVERRIDE_PURE(boost::optional, monero_transfer, is_incoming); - } + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& check); }; -class PyMoneroSubaddress : public monero::monero_subaddress { +struct PyMoneroCheckTxProof : public monero::monero_check_tx { public: - static void from_rpc_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& subaddress); - static void from_rpc_property_tree(const boost::property_tree::ptree& node, std::vector>& subaddresses); - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& subaddress); + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& check); }; -class PyMoneroIntegratedAddress : public monero::monero_integrated_address { +struct PyMoneroMessageSignatureResult : public monero::monero_message_signature_result { public: - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& subaddress); + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr result); }; -class PyMoneroAccount : public monero::monero_account { +// ------------------------------ Extended Data Model --------------------------------- + +enum monero_address_type : uint8_t { + PRIMARY_ADDRESS = 0, + INTEGRATED_ADDRESS, + SUBADDRESS +}; + +// Compares two transactions by their height +struct monero_tx_height_comparator { + bool operator()(const std::shared_ptr& tx1, const std::shared_ptr& tx2) const; +}; + +// Compares two transfers by ascending account and subaddress indices +struct monero_incoming_transfer_comparator { + bool operator()(const std::shared_ptr& t1, const std::shared_ptr& t2) const; + bool operator()(const monero::monero_incoming_transfer& t1, const monero::monero_incoming_transfer& t2) const; +}; + +// Compares two outputs by ascending account and subaddress indices +struct monero_output_comparator { + bool operator()(const monero::monero_output_wallet& o1, const monero::monero_output_wallet& o2) const; +}; + +struct monero_decoded_address { public: + std::string m_address; + monero_address_type m_address_type; + monero::monero_network_type m_network_type; - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& account); - static void from_property_tree(const boost::property_tree::ptree& node, std::vector>& accounts); - static void from_property_tree(const boost::property_tree::ptree& node, std::vector& accounts); + monero_decoded_address(const std::string& address, monero_address_type address_type, monero::monero_network_type network_type); }; -class PyMoneroWalletGetHeightResponse { +struct monero_account_tag : public monero::serializable_struct { public: + boost::optional m_tag; + boost::optional m_label; + std::vector m_account_indices; - static uint64_t from_property_tree(const boost::property_tree::ptree& node); + monero_account_tag() { } + monero_account_tag(const std::string& tag, const std::string& label): m_tag(tag), m_label(label) { } + monero_account_tag(const std::string& tag, const std::string& label, const std::vector& account_indices): m_tag(tag), m_label(label), m_account_indices(account_indices) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; + + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& account_tag); + static void from_property_tree(const boost::property_tree::ptree& node, std::vector>& account_tags); +}; + +struct monero_signature { +public: + + static std::string from_property_tree(const boost::property_tree::ptree& node); }; -class PyMoneroQueryKeyParams : public PyMoneroJsonRequestParams { +struct monero_wallet_balance { +public: + uint64_t m_balance; + uint64_t m_unlocked_balance; + + monero_wallet_balance(uint64_t balance = 0, uint64_t unlocked_balance = 0): m_balance(balance), m_unlocked_balance(unlocked_balance) { } +}; + +// ------------------------------ JSON-RPC Params --------------------------------- + +struct monero_multisig_tx_data_params : public monero_json_request_params { +public: + boost::optional m_multisig_tx_hex; + + monero_multisig_tx_data_params(const std::string& multisig_tx_hex): m_multisig_tx_hex(multisig_tx_hex) { } + + rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; +}; + +struct monero_query_key_params : public monero_json_request_params { public: boost::optional m_key_type; - PyMoneroQueryKeyParams(const std::string& key_type): m_key_type(key_type) { } + monero_query_key_params(const std::string& key_type): m_key_type(key_type) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroQueryOutputParams : public PyMoneroJsonRequestParams { +struct monero_query_output_params : public monero_json_request_params { public: boost::optional m_key_image; - PyMoneroQueryOutputParams(const std::string& key_image): m_key_image(key_image) { } + monero_query_output_params(const std::string& key_image): m_key_image(key_image) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroGetAddressParams : public PyMoneroJsonRequestParams { +struct monero_get_address_params : public monero_json_request_params { public: boost::optional m_account_index; std::vector m_subaddress_indices; - PyMoneroGetAddressParams(uint32_t account_index): m_account_index(account_index) { } - PyMoneroGetAddressParams(uint32_t account_index, const std::vector& subaddress_indices): m_account_index(account_index), m_subaddress_indices(subaddress_indices) { } + monero_get_address_params(uint32_t account_index): m_account_index(account_index) { } + monero_get_address_params(uint32_t account_index, const std::vector& subaddress_indices): m_account_index(account_index), m_subaddress_indices(subaddress_indices) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroGetAddressIndexParams : public PyMoneroJsonRequestParams { +struct monero_get_address_index_params : public monero_json_request_params { public: boost::optional m_address; - PyMoneroGetAddressIndexParams(const std::string& address): m_address(address) { } + monero_get_address_index_params(const std::string& address): m_address(address) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroMakeIntegratedAddressParams : public PyMoneroJsonRequestParams { +struct monero_make_integrated_address_params : public monero_json_request_params { public: boost::optional m_standard_address; boost::optional m_payment_id; - PyMoneroMakeIntegratedAddressParams(const std::string& standard_address, const std::string& payment_id): m_standard_address(standard_address), m_payment_id(payment_id) { } + monero_make_integrated_address_params(const std::string& standard_address, const std::string& payment_id): m_standard_address(standard_address), m_payment_id(payment_id) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroSplitIntegratedAddressParams : public PyMoneroJsonRequestParams { +struct monero_split_integrated_address_params : public monero_json_request_params { public: boost::optional m_integrated_address; - PyMoneroSplitIntegratedAddressParams(const std::string& integrated_address): m_integrated_address(integrated_address) { } + monero_split_integrated_address_params(const std::string& integrated_address): m_integrated_address(integrated_address) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroPrepareMultisigParams : public PyMoneroJsonRequestParams { +struct monero_prepare_multisig_params : public monero_json_request_params { public: // TODO monero-docs document this parameter boost::optional m_enable_multisig_experimental; - PyMoneroPrepareMultisigParams(bool enable_multisig_experimental = true): m_enable_multisig_experimental(enable_multisig_experimental) { } + monero_prepare_multisig_params(bool enable_multisig_experimental = true): m_enable_multisig_experimental(enable_multisig_experimental) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroExportMultisigHexResponse { -public: - - static std::string from_property_tree(const boost::property_tree::ptree& node); -}; - -class PyMoneroImportMultisigHexParams : public PyMoneroJsonRequestParams { +struct monero_import_multisig_hex_params : public monero_json_request_params { public: std::vector m_multisig_hexes; - PyMoneroImportMultisigHexParams(const std::vector& multisig_hexes): m_multisig_hexes(multisig_hexes) { } + monero_import_multisig_hex_params(const std::vector& multisig_hexes): m_multisig_hexes(multisig_hexes) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroImportMultisigHexResponse { -public: - - static int from_property_tree(const boost::property_tree::ptree& node); -}; - -class PyMoneroSubmitMultisigTxHexResponse { -public: - - static std::vector from_property_tree(const boost::property_tree::ptree& node); -}; - -class PyMoneroMakeMultisigParams : public PyMoneroJsonRequestParams { +struct monero_make_multisig_params : public monero_json_request_params { public: std::vector m_multisig_info; boost::optional m_threshold; boost::optional m_password; - PyMoneroMakeMultisigParams(const std::vector& multisig_hexes, const std::string& password): m_multisig_info(multisig_hexes), m_password(password) { } - PyMoneroMakeMultisigParams(const std::vector& multisig_hexes, int threshold, const std::string& password): m_multisig_info(multisig_hexes), m_threshold(threshold), m_password(password) { } + monero_make_multisig_params(const std::vector& multisig_hexes, const std::string& password): m_multisig_info(multisig_hexes), m_password(password) { } + monero_make_multisig_params(const std::vector& multisig_hexes, int threshold, const std::string& password): m_multisig_info(multisig_hexes), m_threshold(threshold), m_password(password) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroPrepareMakeMultisigResponse { -public: - static std::string from_property_tree(const boost::property_tree::ptree& node); -}; - -class PyMoneroParsePaymentUriParams : public PyMoneroJsonRequestParams { +struct monero_parse_payment_uri_params : public monero_json_request_params { public: boost::optional m_uri; - PyMoneroParsePaymentUriParams(const std::string& uri): m_uri(uri) { } + monero_parse_payment_uri_params(const std::string& uri): m_uri(uri) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroGetParsePaymentUri : public PyMoneroJsonRequestParams { +struct monero_get_payment_uri : public monero_json_request_params { public: boost::optional m_address; boost::optional m_amount; @@ -327,86 +369,72 @@ class PyMoneroGetParsePaymentUri : public PyMoneroJsonRequestParams { boost::optional m_recipient_name; boost::optional m_tx_description; - PyMoneroGetParsePaymentUri() { } - PyMoneroGetParsePaymentUri(const monero_tx_config& config); + monero_get_payment_uri() { } + monero_get_payment_uri(const monero_tx_config& config); std::shared_ptr to_tx_config() const; rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& response); + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& response); static std::string from_property_tree(const boost::property_tree::ptree& node); }; -class PyMoneroGetBalanceParams : public PyMoneroJsonRequestParams { +struct monero_get_balance_params : public monero_json_request_params { public: boost::optional m_account_idx; std::vector m_address_indices; boost::optional m_all_accounts; boost::optional m_strict; - PyMoneroGetBalanceParams(bool all_accounts, bool strict = false): m_all_accounts(all_accounts), m_strict(strict) { } - PyMoneroGetBalanceParams(uint32_t account_idx, const std::vector& address_indices, bool all_accounts = false, bool strict = false); - PyMoneroGetBalanceParams(uint32_t account_idx, boost::optional address_idx, bool all_accounts = false, bool strict = false); + monero_get_balance_params(bool all_accounts, bool strict = false): m_all_accounts(all_accounts), m_strict(strict) { } + monero_get_balance_params(uint32_t account_idx, const std::vector& address_indices, bool all_accounts = false, bool strict = false): m_account_idx(account_idx), m_address_indices(address_indices), m_all_accounts(all_accounts), m_strict(strict) { } + monero_get_balance_params(uint32_t account_idx, boost::optional address_idx, bool all_accounts = false, bool strict = false); rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroGetBalanceResponse { -public: - boost::optional m_balance; - boost::optional m_unlocked_balance; - boost::optional m_multisig_import_needed; - boost::optional m_time_to_unlock; - boost::optional m_blocks_to_unlock; - std::vector> m_per_subaddress; - - PyMoneroGetBalanceResponse(): m_balance(0), m_unlocked_balance(0) { } - - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& response); -}; - -class PyMoneroCreateAccountParams : public PyMoneroJsonRequestParams { +struct monero_create_account_params : public monero_json_request_params { public: boost::optional m_tag; - PyMoneroCreateAccountParams(const std::string& tag = ""): m_tag(tag) { } + monero_create_account_params(const std::string& tag = ""): m_tag(tag) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroCloseWalletParams : public PyMoneroJsonRequestParams { +struct monero_close_wallet_params : public monero_json_request_params { public: boost::optional m_save; - PyMoneroCloseWalletParams(bool save = false): m_save(save) { } + monero_close_wallet_params(bool save = false): m_save(save) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroChangeWalletPasswordParams : public PyMoneroJsonRequestParams { +struct monero_change_wallet_password_params : public monero_json_request_params { public: boost::optional m_old_password; boost::optional m_new_password; - PyMoneroChangeWalletPasswordParams(const std::string& old_password, const std::string& new_password): m_old_password(old_password), m_new_password(new_password) { } + monero_change_wallet_password_params(const std::string& old_password, const std::string& new_password): m_old_password(old_password), m_new_password(new_password) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroKeyValue : public PyMoneroJsonRequestParams { +struct monero_key_value : public monero_json_request_params { public: boost::optional m_key; boost::optional m_value; - PyMoneroKeyValue() { } - PyMoneroKeyValue(const std::string& key): m_key(key) { } - PyMoneroKeyValue(const std::string& key, const std::string& value): m_key(key), m_value(value) { } + monero_key_value() { } + monero_key_value(const std::string& key): m_key(key) { } + monero_key_value(const std::string& key, const std::string& value): m_key(key), m_value(value) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& attributes); + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& attributes); }; -class PyMoneroSetDaemonParams : public PyMoneroJsonRequestParams { +struct monero_set_daemon_params : public monero_json_request_params { public: boost::optional m_address; boost::optional m_username; @@ -422,40 +450,40 @@ class PyMoneroSetDaemonParams : public PyMoneroJsonRequestParams { rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroAutoRefreshParams : public PyMoneroJsonRequestParams { +struct monero_auto_refresh_params : public monero_json_request_params { public: boost::optional m_enable; - PyMoneroAutoRefreshParams(bool enable): m_enable(enable) { } + monero_auto_refresh_params(bool enable): m_enable(enable) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroTagAccountsParams : public PyMoneroJsonRequestParams { +struct monero_tag_accounts_params : public monero_json_request_params { public: std::vector m_account_indices; boost::optional m_tag; boost::optional m_label; - PyMoneroTagAccountsParams(const std::string& tag, const std::string& label = ""): m_tag(tag), m_label(label) { } - PyMoneroTagAccountsParams(const std::vector& account_indices): m_account_indices(account_indices) { } - PyMoneroTagAccountsParams(const std::string& tag, const std::vector& account_indices): m_tag(tag), m_account_indices(account_indices) { } + monero_tag_accounts_params(const std::string& tag, const std::string& label = ""): m_tag(tag), m_label(label) { } + monero_tag_accounts_params(const std::vector& account_indices): m_account_indices(account_indices) { } + monero_tag_accounts_params(const std::string& tag, const std::vector& account_indices): m_tag(tag), m_account_indices(account_indices) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroTxNotesParams : public PyMoneroJsonRequestParams { +struct monero_tx_notes_params : public monero_json_request_params { public: std::vector m_tx_hashes; std::vector m_notes; - PyMoneroTxNotesParams(const std::vector& tx_hashes): m_tx_hashes(tx_hashes) { } - PyMoneroTxNotesParams(const std::vector& tx_hashes, const std::vector& notes): m_tx_hashes(tx_hashes), m_notes(notes) { } + monero_tx_notes_params(const std::vector& tx_hashes): m_tx_hashes(tx_hashes) { } + monero_tx_notes_params(const std::vector& tx_hashes, const std::vector& notes): m_tx_hashes(tx_hashes), m_notes(notes) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroAddressBookEntryParams : public PyMoneroJsonRequestParams { +struct monero_address_book_entry_params : public monero_json_request_params { public: boost::optional m_index; // TODO: not boost::optional boost::optional m_set_address; @@ -464,30 +492,15 @@ class PyMoneroAddressBookEntryParams : public PyMoneroJsonRequestParams { boost::optional m_description; std::vector m_entries; - PyMoneroAddressBookEntryParams(uint64_t index): m_index(index) { } - PyMoneroAddressBookEntryParams(const std::vector& entries): m_entries(entries) { } - PyMoneroAddressBookEntryParams(uint64_t index, bool set_address, const std::string& address, bool set_description, const std::string& description); - PyMoneroAddressBookEntryParams(const std::string& address, const std::string& description): m_address(address), m_description(description) { } + monero_address_book_entry_params(uint64_t index): m_index(index) { } + monero_address_book_entry_params(const std::vector& entries): m_entries(entries) { } + monero_address_book_entry_params(uint64_t index, bool set_address, const std::string& address, bool set_description, const std::string& description): m_index(index), m_set_address(set_address), m_address(address), m_set_description(set_description), m_description(description) { } + monero_address_book_entry_params(const std::string& address, const std::string& description): m_address(address), m_description(description) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroAddressBookEntry : public monero::monero_address_book_entry { -public: - - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& entry); - static void from_property_tree(const boost::property_tree::ptree& node, std::vector>& entries); -}; - -class PyMoneroWalletBalance { -public: - uint64_t m_balance; - uint64_t m_unlocked_balance; - - PyMoneroWalletBalance(uint64_t balance = 0, uint64_t unlocked_balance = 0): m_balance(balance), m_unlocked_balance(unlocked_balance) { } -}; - -class PyMoneroVerifySignMessageParams : public PyMoneroJsonRequestParams { +struct monero_verify_sign_message_params : public monero_json_request_params { public: boost::optional m_data; boost::optional m_address; @@ -496,46 +509,46 @@ class PyMoneroVerifySignMessageParams : public PyMoneroJsonRequestParams { boost::optional m_account_index; boost::optional m_address_index; - PyMoneroVerifySignMessageParams(const std::string &data, const std::string &address, const std::string& signature); - PyMoneroVerifySignMessageParams(const std::string &data, monero::monero_message_signature_type signature_type, uint32_t account_index, uint32_t address_index); + monero_verify_sign_message_params(const std::string &data, const std::string &address, const std::string& signature): m_data(data), m_address(address), m_signature(signature) { } + monero_verify_sign_message_params(const std::string &data, monero::monero_message_signature_type signature_type, uint32_t account_index, uint32_t address_index): m_data(data), m_signature_type(signature_type), m_account_index(account_index), m_address_index(address_index) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroCheckTxKeyParams : public PyMoneroJsonRequestParams { +struct monero_check_tx_key_params : public monero_json_request_params { public: boost::optional m_tx_hash; boost::optional m_address; boost::optional m_tx_key; - PyMoneroCheckTxKeyParams(const std::string &tx_hash): m_tx_hash(tx_hash) { } - PyMoneroCheckTxKeyParams(const std::string &tx_hash, const std::string &tx_key, const std::string &address); + monero_check_tx_key_params(const std::string &tx_hash): m_tx_hash(tx_hash) { } + monero_check_tx_key_params(const std::string &tx_hash, const std::string &tx_key, const std::string &address): m_tx_hash(tx_hash), m_tx_key(tx_key), m_address(address) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroSignDescribeTransferParams : public PyMoneroJsonRequestParams { +struct monero_sign_describe_transfer_params : public monero_json_request_params { public: boost::optional m_unsigned_txset; boost::optional m_multisig_txset; - PyMoneroSignDescribeTransferParams() { } - PyMoneroSignDescribeTransferParams(const std::string &unsigned_txset) : m_unsigned_txset(unsigned_txset) { } - PyMoneroSignDescribeTransferParams(const std::string &unsigned_txset, const std::string &multisig_txset) : m_unsigned_txset(unsigned_txset), m_multisig_txset(multisig_txset) { } + monero_sign_describe_transfer_params() { } + monero_sign_describe_transfer_params(const std::string &unsigned_txset) : m_unsigned_txset(unsigned_txset) { } + monero_sign_describe_transfer_params(const std::string &unsigned_txset, const std::string &multisig_txset) : m_unsigned_txset(unsigned_txset), m_multisig_txset(multisig_txset) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroWalletRelayTxParams : public PyMoneroJsonRequestParams { +struct monero_wallet_relay_tx_params : public monero_json_request_params { public: boost::optional m_hex; - PyMoneroWalletRelayTxParams(const std::string &hex): m_hex(hex) { } + monero_wallet_relay_tx_params(const std::string &hex): m_hex(hex) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroSweepParams : public PyMoneroJsonRequestParams { +struct monero_sweep_params : public monero_json_request_params { public: boost::optional m_address; boost::optional m_account_index; @@ -550,57 +563,57 @@ class PyMoneroSweepParams : public PyMoneroJsonRequestParams { boost::optional m_get_tx_hex; boost::optional m_get_tx_metadata; - PyMoneroSweepParams(bool relay = false): m_relay(relay) { } - PyMoneroSweepParams(const monero_tx_config& config); + monero_sweep_params(bool relay = false): m_relay(relay) { } + monero_sweep_params(const monero_tx_config& config); rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroSubmitTransferParams : public PyMoneroJsonRequestParams { +struct monero_submit_transfer_params : public monero_json_request_params { public: boost::optional m_signed_tx_hex; - PyMoneroSubmitTransferParams(const std::string& signed_tx_hex): m_signed_tx_hex(signed_tx_hex) { } + monero_submit_transfer_params(const std::string& signed_tx_hex): m_signed_tx_hex(signed_tx_hex) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroCreateEditSubaddressParams : public PyMoneroJsonRequestParams { +struct monero_create_edit_subaddress_params : public monero_json_request_params { public: boost::optional m_label; boost::optional m_account_index; boost::optional m_subaddress_index; - PyMoneroCreateEditSubaddressParams(uint32_t account_idx, const std::string& label): m_account_index(account_idx), m_label(label) { } - PyMoneroCreateEditSubaddressParams(uint32_t account_idx, uint32_t subaddress_idx, const std::string& label): m_account_index(account_idx), m_subaddress_index(subaddress_idx), m_label(label) { } + monero_create_edit_subaddress_params(uint32_t account_idx, const std::string& label): m_account_index(account_idx), m_label(label) { } + monero_create_edit_subaddress_params(uint32_t account_idx, uint32_t subaddress_idx, const std::string& label): m_account_index(account_idx), m_subaddress_index(subaddress_idx), m_label(label) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroImportExportOutputsParams : public PyMoneroJsonRequestParams { +struct monero_import_export_outputs_params : public monero_json_request_params { public: boost::optional m_outputs_hex; boost::optional m_all; - PyMoneroImportExportOutputsParams(bool all): m_all(all) { } - PyMoneroImportExportOutputsParams(const std::string& outputs_hex): m_outputs_hex(outputs_hex) { } + monero_import_export_outputs_params(bool all): m_all(all) { } + monero_import_export_outputs_params(const std::string& outputs_hex): m_outputs_hex(outputs_hex) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroImportExportKeyImagesParams : public PyMoneroJsonRequestParams { +struct monero_import_export_key_images_params : public monero_json_request_params { public: boost::optional m_all; std::vector> m_key_images; - PyMoneroImportExportKeyImagesParams(const std::vector> &key_images); - PyMoneroImportExportKeyImagesParams(const std::vector> &key_images): m_key_images(key_images) { } - PyMoneroImportExportKeyImagesParams(bool all): m_all(all) { } + monero_import_export_key_images_params(const std::vector> &key_images); + monero_import_export_key_images_params(const std::vector> &key_images): m_key_images(key_images) { } + monero_import_export_key_images_params(bool all): m_all(all) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroCreateOpenWalletParams : public PyMoneroJsonRequestParams { +struct monero_create_open_wallet_params : public monero_json_request_params { public: boost::optional m_filename; boost::optional m_password; @@ -614,15 +627,15 @@ class PyMoneroCreateOpenWalletParams : public PyMoneroJsonRequestParams { boost::optional m_view_key; boost::optional m_spend_key; - PyMoneroCreateOpenWalletParams(const boost::optional& filename, const boost::optional &password); - PyMoneroCreateOpenWalletParams(const boost::optional& filename, const boost::optional &password, const boost::optional &language); - PyMoneroCreateOpenWalletParams(const boost::optional& filename, const boost::optional &password, const boost::optional &seed, const boost::optional &seed_offset, const boost::optional &restore_height, const boost::optional &language, const boost::optional &autosave_current, const boost::optional &enable_multisig_experimental); - PyMoneroCreateOpenWalletParams(const boost::optional& filename, const boost::optional &password, const boost::optional &address, const boost::optional &view_key, const boost::optional &spend_key, const boost::optional &restore_height, const boost::optional &autosave_current); + monero_create_open_wallet_params(const boost::optional& filename, const boost::optional &password): m_filename(filename), m_password(password), m_autosave_current(false) { } + monero_create_open_wallet_params(const boost::optional& filename, const boost::optional &password, const boost::optional &language): m_filename(filename), m_password(password), m_language(language), m_autosave_current(false) { } + monero_create_open_wallet_params(const boost::optional& filename, const boost::optional &password, const boost::optional &seed, const boost::optional &seed_offset, const boost::optional &restore_height, const boost::optional &language, const boost::optional &autosave_current, const boost::optional &enable_multisig_experimental): m_filename(filename), m_password(password), m_seed(seed), m_seed_offset(seed_offset), m_restore_height(restore_height), m_language(language), m_autosave_current(autosave_current), m_enable_multisig_experimental(enable_multisig_experimental) { } + monero_create_open_wallet_params(const boost::optional& filename, const boost::optional &password, const boost::optional &address, const boost::optional &view_key, const boost::optional &spend_key, const boost::optional &restore_height, const boost::optional &autosave_current): m_filename(filename), m_password(password), m_address(address), m_view_key(view_key), m_spend_key(spend_key), m_restore_height(restore_height), m_autosave_current(autosave_current) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroReserveProofParams : public PyMoneroJsonRequestParams { +struct monero_reserve_proof_params : public monero_json_request_params { public: boost::optional m_all; boost::optional m_message; @@ -632,29 +645,29 @@ class PyMoneroReserveProofParams : public PyMoneroJsonRequestParams { boost::optional m_address; boost::optional m_signature; - PyMoneroReserveProofParams(const std::string &message, bool all = true); - PyMoneroReserveProofParams(const std::string &address, const std::string &message, const std::string &signature); - PyMoneroReserveProofParams(const std::string &tx_hash, const std::string &address, const std::string &message, const std::string &signature); - PyMoneroReserveProofParams(const std::string &tx_hash, const std::string &message); - PyMoneroReserveProofParams(uint32_t account_index, uint64_t amount, const std::string &message); + monero_reserve_proof_params(const std::string &message, bool all = true): m_all(all), m_message(message) { } + monero_reserve_proof_params(const std::string &address, const std::string &message, const std::string &signature): m_address(address), m_message(message), m_signature(signature) { } + monero_reserve_proof_params(const std::string &tx_hash, const std::string &address, const std::string &message, const std::string &signature): m_tx_hash(tx_hash), m_address(address), m_message(message), m_signature(signature) { } + monero_reserve_proof_params(const std::string &tx_hash, const std::string &message): m_tx_hash(tx_hash), m_message(message) { } + monero_reserve_proof_params(uint32_t account_index, uint64_t amount, const std::string &message): m_account_index(account_index), m_amount(amount), m_message(message) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroRefreshWalletParams : public PyMoneroJsonRequestParams { +struct monero_refresh_wallet_params : public monero_json_request_params { public: boost::optional m_enable; boost::optional m_period; boost::optional m_start_height; - PyMoneroRefreshWalletParams() { } - PyMoneroRefreshWalletParams(bool enable, uint64_t period): m_enable(enable), m_period(period) { } - PyMoneroRefreshWalletParams(uint64_t start_height): m_start_height(start_height) { } + monero_refresh_wallet_params() { } + monero_refresh_wallet_params(bool enable, uint64_t period): m_enable(enable), m_period(period) { } + monero_refresh_wallet_params(uint64_t start_height): m_start_height(start_height) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroTransferParams : public PyMoneroJsonRequestParams { +struct monero_transfer_params : public monero_json_request_params { public: std::vector m_subtract_fee_from_outputs; boost::optional m_account_index; @@ -668,12 +681,12 @@ class PyMoneroTransferParams : public PyMoneroJsonRequestParams { boost::optional m_get_tx_key; std::vector> m_destinations; - PyMoneroTransferParams(const monero::monero_tx_config &config); + monero_transfer_params(const monero::monero_tx_config &config); rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroGetTransfersParams : public PyMoneroJsonRequestParams { +struct monero_get_transfers_params : public monero_json_request_params { public: boost::optional m_in; boost::optional m_out; @@ -690,44 +703,83 @@ class PyMoneroGetTransfersParams : public PyMoneroJsonRequestParams { rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroGetIncomingTransfersParams : public PyMoneroJsonRequestParams { +struct monero_get_incoming_transfers_params : public monero_json_request_params { public: boost::optional m_transfer_type; boost::optional m_verbose; boost::optional m_account_index; std::vector m_subaddr_indices; - PyMoneroGetIncomingTransfersParams(const std::string& transfer_type, bool verbose = true); + monero_get_incoming_transfers_params(const std::string& transfer_type, bool verbose = true): m_transfer_type(transfer_type), m_verbose(verbose) { } rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; -class PyMoneroCheckReserve : public monero::monero_check_reserve { -public: +// ------------------------------ JSON-RPC Response --------------------------------- - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& check); +struct monero_wallet_get_height_response { +public: + static uint64_t from_property_tree(const boost::property_tree::ptree& node); }; -class PyMoneroCheckTxProof : public monero::monero_check_tx { +struct monero_export_multisig_hex_response { public: + static std::string from_property_tree(const boost::property_tree::ptree& node); +}; - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& check); +struct monero_import_multisig_hex_response { +public: + static int from_property_tree(const boost::property_tree::ptree& node); }; -class PyMoneroReserveProofSignature { +struct monero_submit_multisig_tx_hex_response { public: + static std::vector from_property_tree(const boost::property_tree::ptree& node); +}; +struct monero_prepare_make_multisig_response { +public: static std::string from_property_tree(const boost::property_tree::ptree& node); }; -class PyMoneroMessageSignatureResult : public monero::monero_message_signature_result { +struct monero_get_balance_response { public: + boost::optional m_balance; + boost::optional m_unlocked_balance; + boost::optional m_multisig_import_needed; + boost::optional m_time_to_unlock; + boost::optional m_blocks_to_unlock; + std::vector> m_per_subaddress; - static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr result); + monero_get_balance_response(): m_balance(0), m_unlocked_balance(0) { } + + static void from_property_tree(const boost::property_tree::ptree& node, const std::shared_ptr& response); }; +// ------------------------------ Utilities --------------------------------- + // TODO expose bool_equals_2 from monero-cpp bool bool_equals_2(bool val, const boost::optional& opt_val); bool tx_height_less_than(const std::shared_ptr& tx1, const std::shared_ptr& tx2); bool incoming_transfer_before(const std::shared_ptr& transfer1, const std::shared_ptr& transfer2); bool vout_before(const std::shared_ptr& o1, const std::shared_ptr& o2); + +template +void set_query(std::shared_ptr& self, boost::optional>& field, const boost::optional>& val) { + + const auto old = field; + field = val; + + // set new reference + if (field != boost::none) { + field.get()->m_tx_query = self; + } + + // cleanup old + if (old != boost::none) { + if (val != boost::none && val.get() == old.get()) { + return; + } + old.get()->m_tx_query = boost::none; + } +} diff --git a/src/cpp/wallet/py_monero_wallet_rpc.cpp b/src/cpp/wallet/py_monero_wallet_rpc.cpp index 1d37c5a..97c32d7 100644 --- a/src/cpp/wallet/py_monero_wallet_rpc.cpp +++ b/src/cpp/wallet/py_monero_wallet_rpc.cpp @@ -1,222 +1,291 @@ +/** + * Copyright (c) everoddandeven + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2025-2026 woodser + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ #include "py_monero_wallet_rpc.h" #include "utils/monero_utils.h" -PyMoneroWalletPoller::PyMoneroWalletPoller(PyMoneroWallet *wallet): m_num_polling(0) { - m_wallet = wallet; - init_common("monero_wallet_rpc"); -} - -void PyMoneroWalletPoller::poll() { - // skip if next poll is queued - if (m_num_polling > 1) return; - m_num_polling++; - - // synchronize polls - boost::lock_guard lock(m_mutex); - try { - // skip if wallet is closed - if (m_wallet->is_closed()) { - m_num_polling--; - return; - } - // take initial snapshot - if (m_prev_balances == boost::none) { - m_prev_height = m_wallet->get_height(); - monero::monero_tx_query tx_query; - tx_query.m_is_locked = true; - m_prev_locked_txs = m_wallet->get_txs(tx_query); - m_prev_balances = m_wallet->get_balances(boost::none, boost::none); - m_num_polling--; - return; - } +/** + * Polls wallet and sends notifications in order to notify external wallet listeners. + */ +class monero_wallet_poller: public thread_poller { +public: + + explicit monero_wallet_poller(monero_wallet_rpc *wallet): m_num_polling(0) { + m_wallet = wallet; + init_common("monero_wallet_rpc"); + }; + + void poll() override { + // skip if next poll is queued + if (m_num_polling > 1) return; + m_num_polling++; + + // synchronize polls + boost::lock_guard lock(m_mutex); + try { + // skip if wallet is closed + if (m_wallet->is_closed()) { + m_num_polling--; + return; + } - // announce height changes - uint64_t height = m_wallet->get_height(); - if (m_prev_height.get() != height) { - for (uint64_t i = m_prev_height.get(); i < height; i++) { - on_new_block(i); + // take initial snapshot + if (m_prev_balances == boost::none) { + m_prev_height = m_wallet->get_height(); + monero::monero_tx_query tx_query; + tx_query.m_is_locked = true; + m_prev_locked_txs = m_wallet->get_txs(tx_query); + m_prev_balances = m_wallet->get_balances(boost::none, boost::none); + m_num_polling--; + return; } - m_prev_height = height; - } + // announce height changes + uint64_t height = m_wallet->get_height(); + if (m_prev_height.get() != height) { + for (uint64_t i = m_prev_height.get(); i < height; i++) { + on_new_block(i); + } - // get locked txs for comparison to previous - uint64_t min_height = 0; // only monitor recent txs - if (height > 70) min_height = height - 70; - monero::monero_tx_query tx_query; - tx_query.m_is_locked = true; - tx_query.m_min_height = min_height; - tx_query.m_include_outputs = true; - - auto locked_txs = m_wallet->get_txs(tx_query); - - // collect hashes of txs no longer locked - std::vector no_longer_locked_hashes; - for (const auto &prev_locked_tx : m_prev_locked_txs) { - if (get_tx(locked_txs, prev_locked_tx->m_hash.get()) == nullptr) { - no_longer_locked_hashes.push_back(prev_locked_tx->m_hash.get()); + m_prev_height = height; } - } - - // save locked txs for next comparison - m_prev_locked_txs = locked_txs; - std::vector> unlocked_txs; - if (!no_longer_locked_hashes.empty()) { - // fetch txs which are no longer locked - monero_tx_query tx_query; - tx_query.m_is_locked = false; + // get locked txs for comparison to previous + uint64_t min_height = 0; // only monitor recent txs + if (height > 70) min_height = height - 70; + monero::monero_tx_query tx_query; + tx_query.m_is_locked = true; tx_query.m_min_height = min_height; - tx_query.m_hashes = no_longer_locked_hashes; tx_query.m_include_outputs = true; - unlocked_txs = m_wallet->get_txs(tx_query); - } - // announce new unconfirmed and confirmed txs - for (const auto &locked_tx : locked_txs) { - bool announced = false; - const std::string& tx_hash = locked_tx->m_hash.get(); - if (bool_equals_2(true, locked_tx->m_is_confirmed)) { - if (std::find(m_prev_confirmed_notifications.begin(), m_prev_confirmed_notifications.end(), tx_hash) == m_prev_confirmed_notifications.end()) { - m_prev_confirmed_notifications.push_back(tx_hash); - announced = true; + auto locked_txs = m_wallet->get_txs(tx_query); + + // collect hashes of txs no longer locked + std::vector no_longer_locked_hashes; + for (const auto &prev_locked_tx : m_prev_locked_txs) { + if (get_tx(locked_txs, prev_locked_tx->m_hash.get()) == nullptr) { + no_longer_locked_hashes.push_back(prev_locked_tx->m_hash.get()); } } - else { - if (std::find(m_prev_unconfirmed_notifications.begin(), m_prev_unconfirmed_notifications.end(), tx_hash) == m_prev_unconfirmed_notifications.end()) { - m_prev_unconfirmed_notifications.push_back(tx_hash); - announced = true; - } + + // save locked txs for next comparison + m_prev_locked_txs = locked_txs; + std::vector> unlocked_txs; + + if (!no_longer_locked_hashes.empty()) { + // fetch txs which are no longer locked + monero_tx_query tx_query; + tx_query.m_is_locked = false; + tx_query.m_min_height = min_height; + tx_query.m_hashes = no_longer_locked_hashes; + tx_query.m_include_outputs = true; + unlocked_txs = m_wallet->get_txs(tx_query); } - if (announced) notify_outputs(locked_tx); - } + // announce new unconfirmed and confirmed txs + for (const auto &locked_tx : locked_txs) { + bool announced = false; + const std::string& tx_hash = locked_tx->m_hash.get(); + if (bool_equals_2(true, locked_tx->m_is_confirmed)) { + if (std::find(m_prev_confirmed_notifications.begin(), m_prev_confirmed_notifications.end(), tx_hash) == m_prev_confirmed_notifications.end()) { + m_prev_confirmed_notifications.push_back(tx_hash); + announced = true; + } + } + else { + if (std::find(m_prev_unconfirmed_notifications.begin(), m_prev_unconfirmed_notifications.end(), tx_hash) == m_prev_unconfirmed_notifications.end()) { + m_prev_unconfirmed_notifications.push_back(tx_hash); + announced = true; + } + } - // announce new unlocked outputs - for (const auto &unlocked_tx : unlocked_txs) { - std::string tx_hash = unlocked_tx->m_hash.get(); - // stop tracking tx notifications - m_prev_confirmed_notifications.erase(std::remove_if(m_prev_confirmed_notifications.begin(), m_prev_confirmed_notifications.end(), [&tx_hash](const std::string& iter){ return iter == tx_hash; }), m_prev_confirmed_notifications.end()); - m_prev_unconfirmed_notifications.erase(std::remove_if(m_prev_unconfirmed_notifications.begin(), m_prev_unconfirmed_notifications.end(), [&tx_hash](const std::string& iter){ return iter == tx_hash; }), m_prev_unconfirmed_notifications.end()); - notify_outputs(unlocked_tx); - } + if (announced) notify_outputs(locked_tx); + } - // announce balance changes - check_for_changed_balances(); + // announce new unlocked outputs + for (const auto &unlocked_tx : unlocked_txs) { + std::string tx_hash = unlocked_tx->m_hash.get(); + // stop tracking tx notifications + m_prev_confirmed_notifications.erase(std::remove_if(m_prev_confirmed_notifications.begin(), m_prev_confirmed_notifications.end(), [&tx_hash](const std::string& iter){ return iter == tx_hash; }), m_prev_confirmed_notifications.end()); + m_prev_unconfirmed_notifications.erase(std::remove_if(m_prev_unconfirmed_notifications.begin(), m_prev_unconfirmed_notifications.end(), [&tx_hash](const std::string& iter){ return iter == tx_hash; }), m_prev_unconfirmed_notifications.end()); + notify_outputs(unlocked_tx); + } - m_num_polling--; - } - catch (const std::exception &e) { - m_num_polling--; - if (m_is_polling) { - std::cout << "Failed to background poll wallet " << m_wallet->get_path() << ": " << e.what() << std::endl; - } - } -} + // announce balance changes + check_for_changed_balances(); -std::shared_ptr PyMoneroWalletPoller::get_tx(const std::vector>& txs, const std::string& tx_hash) { - for (const auto& tx : txs) { - if (tx->m_hash == tx_hash) return tx; + m_num_polling--; + } + catch (const std::exception &e) { + m_num_polling--; + if (m_is_polling) { + MERROR("Failed to background poll wallet " << m_wallet->get_path() << ": " << e.what()); + } + } } - return nullptr; -} +private: + monero_wallet_rpc *m_wallet; + std::atomic m_num_polling; -void PyMoneroWalletPoller::on_new_block(uint64_t height) { - m_wallet->announce_new_block(height); -} + std::vector m_prev_unconfirmed_notifications; + std::vector m_prev_confirmed_notifications; + boost::optional> m_prev_balances; + boost::optional m_prev_height; + std::vector> m_prev_locked_txs; -void PyMoneroWalletPoller::notify_outputs(const std::shared_ptr &tx) { - // notify spent outputs - // TODO (monero-project): monero-wallet-rpc does not allow scrape of tx inputs so providing one input with outgoing amount - if (tx->m_outgoing_transfer != boost::none) { - auto outgoing_transfer = tx->m_outgoing_transfer.get(); - if (!tx->m_inputs.empty()) throw std::runtime_error("Tx inputs should be empty"); - auto output = std::make_shared(); - output->m_amount = outgoing_transfer->m_amount.get() + tx->m_fee.get(); - output->m_account_index = outgoing_transfer->m_account_index; - output->m_tx = tx; - // initialize if transfer sourced from single subaddress - if (outgoing_transfer->m_subaddress_indices.size() == 1) { - output->m_subaddress_index = outgoing_transfer->m_subaddress_indices[0]; + std::shared_ptr get_tx(const std::vector>& txs, const std::string& tx_hash){ + for (const auto& tx : txs) { + if (tx->m_hash == tx_hash) return tx; } - tx->m_inputs.clear(); - tx->m_inputs.push_back(output); - m_wallet->announce_output_spent(output); + + return nullptr; } - // notify received outputs - if (tx->m_incoming_transfers.size() > 0) { - if (!tx->m_outputs.empty()) { - // TODO (monero-project): outputs only returned for confirmed txs - for(const auto &output : tx->get_outputs_wallet()) { - m_wallet->announce_output_received(output); + void on_new_block(uint64_t height) { + m_wallet->announce_new_block(height); + } + + void notify_outputs(const std::shared_ptr &tx) { + // notify spent outputs + // TODO (monero-project): monero-wallet-rpc does not allow scrape of tx inputs so providing one input with outgoing amount + if (tx->m_outgoing_transfer != boost::none) { + auto outgoing_transfer = tx->m_outgoing_transfer.get(); + if (!tx->m_inputs.empty()) throw std::runtime_error("Tx inputs should be empty"); + auto output = std::make_shared(); + output->m_amount = outgoing_transfer->m_amount.get() + tx->m_fee.get(); + output->m_account_index = outgoing_transfer->m_account_index; + output->m_tx = tx; + // initialize if transfer sourced from single subaddress + if (outgoing_transfer->m_subaddress_indices.size() == 1) { + output->m_subaddress_index = outgoing_transfer->m_subaddress_indices[0]; } + tx->m_inputs.clear(); + tx->m_inputs.push_back(output); + m_wallet->announce_output_spent(output); } - else { - // TODO (monero-project): monero-wallet-rpc does not allow scrape of unconfirmed received outputs so using incoming transfer values - tx->m_outputs.clear(); - for (const auto &transfer : tx->m_incoming_transfers) { - auto output = std::make_shared(); - output->m_account_index = transfer->m_account_index; - output->m_subaddress_index = transfer->m_subaddress_index; - output->m_amount = transfer->m_amount.get(); - output->m_tx = tx; - tx->m_outputs.push_back(output); + + // notify received outputs + if (tx->m_incoming_transfers.size() > 0) { + if (!tx->m_outputs.empty()) { + // TODO (monero-project): outputs only returned for confirmed txs + for(const auto &output : tx->get_outputs_wallet()) { + m_wallet->announce_output_received(output); + } } + else { + // TODO (monero-project): monero-wallet-rpc does not allow scrape of unconfirmed received outputs so using incoming transfer values + tx->m_outputs.clear(); + for (const auto &transfer : tx->m_incoming_transfers) { + auto output = std::make_shared(); + output->m_account_index = transfer->m_account_index; + output->m_subaddress_index = transfer->m_subaddress_index; + output->m_amount = transfer->m_amount.get(); + output->m_tx = tx; + tx->m_outputs.push_back(output); + } - for (const auto &output : tx->get_outputs_wallet()) { - m_wallet->announce_output_received(output); + for (const auto &output : tx->get_outputs_wallet()) { + m_wallet->announce_output_received(output); + } } } } -} -// TODO: factor to common wallet rpc listener -bool PyMoneroWalletPoller::check_for_changed_balances() { - auto balances = m_wallet->get_balances(boost::none, boost::none); - if (balances->m_balance != m_prev_balances.get()->m_balance || balances->m_unlocked_balance != m_prev_balances.get()->m_unlocked_balance) { - m_prev_balances = balances; - m_wallet->announce_balances_changed(balances->m_balance, balances->m_unlocked_balance); - return true; + bool check_for_changed_balances() { + auto balances = m_wallet->get_balances(boost::none, boost::none); + if (balances->m_balance != m_prev_balances.get()->m_balance || balances->m_unlocked_balance != m_prev_balances.get()->m_unlocked_balance) { + m_prev_balances = balances; + m_wallet->announce_balances_changed(balances->m_balance, balances->m_unlocked_balance); + return true; + } + return false; } - return false; -} +}; -PyMoneroWalletRpc::PyMoneroWalletRpc(const std::shared_ptr& rpc_connection) { - m_rpc = rpc_connection; +monero_wallet_rpc::monero_wallet_rpc(const std::shared_ptr& rpc_connection): m_rpc(rpc_connection) { if (!m_rpc->is_online() && m_rpc->m_uri != boost::none) m_rpc->check_connection(); } -PyMoneroWalletRpc::PyMoneroWalletRpc(const std::string& uri, const std::string& username, const std::string& password, const std::string& proxy_uri, const std::string& zmq_uri, uint64_t timeout) { - m_rpc = std::make_shared(uri, username, password, proxy_uri, zmq_uri, 0, timeout); +monero_wallet_rpc::monero_wallet_rpc(const std::string& uri, const std::string& username, const std::string& password, const std::string& proxy_uri, const std::string& zmq_uri, uint64_t timeout): m_rpc(std::make_shared(uri, username, password, proxy_uri, zmq_uri, 0, timeout)) { if (m_rpc->m_uri != boost::none) m_rpc->check_connection(); } -PyMoneroWalletRpc::~PyMoneroWalletRpc() { - MTRACE("~PyMoneroWalletRpc()"); +monero_wallet_rpc::~monero_wallet_rpc() { + MTRACE("~monero_wallet_rpc()"); clear(); } -void PyMoneroWalletRpc::add_listener(monero_wallet_listener& listener) { +void monero_wallet_rpc::add_listener(monero_wallet_listener& listener) { PyMoneroWallet::add_listener(listener); refresh_listening(); } -void PyMoneroWalletRpc::remove_listener(monero_wallet_listener& listener) { +void monero_wallet_rpc::remove_listener(monero_wallet_listener& listener) { PyMoneroWallet::remove_listener(listener); refresh_listening(); } -PyMoneroWalletRpc* PyMoneroWalletRpc::open_wallet(const std::shared_ptr &config) { +monero_wallet_rpc* monero_wallet_rpc::open_wallet(const std::shared_ptr &config) { + MTRACE("monero_wallet_rpc::open_wallet(...)"); if (config == nullptr) throw std::runtime_error("Must provide configuration of wallet to open"); if (config->m_path == boost::none || config->m_path->empty()) throw std::runtime_error("Filename is not initialized"); std::string path = config->m_path.get(); std::string password = std::string(""); if (config->m_password != boost::none) password = config->m_password.get(); - auto params = std::make_shared(path, password); + auto params = std::make_shared(path, password); m_rpc->send_json_request("open_wallet", params); clear(); @@ -227,22 +296,25 @@ PyMoneroWalletRpc* PyMoneroWalletRpc::open_wallet(const std::shared_ptr(); +monero_wallet_rpc* monero_wallet_rpc::open_wallet(const std::string& name, const std::string& password) { + MTRACE("monero_wallet_rpc::open_wallet(" << name << ", ***"); + auto config = std::make_shared(); config->m_path = name; config->m_password = password; return open_wallet(config); } -void handle_create_wallet_error(const PyMoneroRpcError& ex, const std::string& path) { +void handle_create_wallet_error(const monero_rpc_error& ex, const std::string& path) { std::string msg = ex.what(); std::transform(msg.begin(), msg.end(), msg.begin(), [](unsigned char c){ return std::tolower(c); }); - if (msg.find("already exists") != std::string::npos) throw PyMoneroRpcError(ex.code, std::string("Wallet already exists: ") + path); - if (msg == std::string("electrum-style word list failed verification")) throw PyMoneroRpcError(ex.code, std::string("Invalid mnemonic")); + if (msg.find("already exists") != std::string::npos) throw monero_rpc_error(ex.code, std::string("Wallet already exists: ") + path); + if (msg == std::string("electrum-style word list failed verification")) throw monero_rpc_error(ex.code, std::string("Invalid mnemonic")); throw ex; } -PyMoneroWalletRpc* PyMoneroWalletRpc::create_wallet(const std::shared_ptr &config) { +monero_wallet_rpc* monero_wallet_rpc::create_wallet(const std::shared_ptr &config) { + MTRACE("monero_wallet_rpc::create_wallet(...)"); + if (config == nullptr) throw std::runtime_error("Must specify config to create wallet"); if (config->m_network_type != boost::none) throw std::runtime_error("Cannot specify network type when creating RPC wallet"); if (config->m_seed != boost::none && (config->m_primary_address != boost::none || config->m_private_view_key != boost::none || config->m_private_spend_key != boost::none)) { @@ -261,7 +333,7 @@ PyMoneroWalletRpc* PyMoneroWalletRpc::create_wallet(const std::shared_ptr PyMoneroWalletRpc::get_seed_languages() const { +std::vector monero_wallet_rpc::get_seed_languages() const { auto node = m_rpc->send_json_request("get_languages"); std::vector languages; @@ -280,29 +352,33 @@ std::vector PyMoneroWalletRpc::get_seed_languages() const { return languages; } -void PyMoneroWalletRpc::stop() { +void monero_wallet_rpc::stop() { + MTRACE("monero_wallet_rpc::stop()"); m_rpc->send_json_request("stop_wallet"); } -bool PyMoneroWalletRpc::is_view_only() const { +bool monero_wallet_rpc::is_view_only() const { try { std::string key = "mnemonic"; query_key(key); return false; } - catch (const PyMoneroRpcError& e) { + catch (const monero_rpc_error& e) { if (e.code == -29) return true; if (e.code == -1) return false; throw; } } -boost::optional PyMoneroWalletRpc::get_daemon_connection() const { +boost::optional monero_wallet_rpc::get_daemon_connection() const { + MTRACE("monero_wallet_rpc::get_daemon_connection()"); if (m_daemon_connection == nullptr) return boost::none; return boost::optional(*m_daemon_connection); } -void PyMoneroWalletRpc::set_daemon_connection(const std::string& uri, const std::string& username, const std::string& password, const std::string& proxy_uri) { +void monero_wallet_rpc::set_daemon_connection(const std::string& uri, const std::string& username, const std::string& password, const std::string& proxy_uri) { + MTRACE("monero_wallet_rpc::set_daemon_connection(" << uri << ", " << username << ", " << "***" << ", " << proxy_uri << ")"); + if (uri.empty()) { set_daemon_connection(boost::none); return; @@ -311,8 +387,8 @@ void PyMoneroWalletRpc::set_daemon_connection(const std::string& uri, const std: set_daemon_connection(rpc); } -void PyMoneroWalletRpc::set_daemon_connection(const boost::optional& connection, bool is_trusted, const boost::optional& ssl_options) { - auto params = std::make_shared(); +void monero_wallet_rpc::set_daemon_connection(const boost::optional& connection, bool is_trusted, const boost::optional& ssl_options) { + auto params = std::make_shared(); if (connection == boost::none) { params->m_address = "placeholder"; params->m_username = ""; @@ -345,62 +421,66 @@ void PyMoneroWalletRpc::set_daemon_connection(const boost::optional& connection) { +void monero_wallet_rpc::set_daemon_connection(const boost::optional& connection) { set_daemon_connection(connection, false, boost::none); } -bool PyMoneroWalletRpc::is_connected_to_daemon() const { +bool monero_wallet_rpc::is_connected_to_daemon() const { try { check_reserve_proof(get_primary_address(), "", ""); return false; } - catch (const PyMoneroRpcError& e) { + catch (const monero_rpc_error& e) { if (e.code == -13) throw; // no wallet file return e.message.find("Failed to connect to daemon") == std::string::npos; } } -monero::monero_version PyMoneroWalletRpc::get_version() const { +monero::monero_version monero_wallet_rpc::get_version() const { auto res = m_rpc->send_json_request("get_version"); std::shared_ptr info = std::make_shared(); PyMoneroVersion::from_property_tree(res, info); return *info; } -std::string PyMoneroWalletRpc::get_path() const { +std::string monero_wallet_rpc::get_path() const { return m_path; } -std::string PyMoneroWalletRpc::get_seed() const { +std::string monero_wallet_rpc::get_seed() const { std::string key = "mnemonic"; return query_key(key); } -std::string PyMoneroWalletRpc::get_seed_language() const { +std::string monero_wallet_rpc::get_seed_language() const { throw std::runtime_error("MoneroWalletRpc::get_seed_language() not supported"); } -std::string PyMoneroWalletRpc::get_public_view_key() const { +std::string monero_wallet_rpc::get_public_view_key() const { + MTRACE("monero_wallet_rpc::get_public_view_key()"); std::string key = "public_view_key"; return query_key(key); } -std::string PyMoneroWalletRpc::get_private_view_key() const { +std::string monero_wallet_rpc::get_private_view_key() const { + MTRACE("monero_wallet_rpc::get_private_view_key()"); std::string key = "view_key"; return query_key(key); } -std::string PyMoneroWalletRpc::get_public_spend_key() const { +std::string monero_wallet_rpc::get_public_spend_key() const { + MTRACE("monero_wallet_rpc::get_public_spend_key()"); std::string key = "public_spend_key"; return query_key(key); } -std::string PyMoneroWalletRpc::get_private_spend_key() const { +std::string monero_wallet_rpc::get_private_spend_key() const { + MTRACE("monero_wallet_rpc::get_private_spend_key()"); std::string key = "spend_key"; return query_key(key); } -std::string PyMoneroWalletRpc::get_address(const uint32_t account_idx, const uint32_t subaddress_idx) const { +std::string monero_wallet_rpc::get_address(const uint32_t account_idx, const uint32_t subaddress_idx) const { auto it = m_address_cache.find(account_idx); if (it == m_address_cache.end()) { // cache's all addresses at this account @@ -427,24 +507,30 @@ std::string PyMoneroWalletRpc::get_address(const uint32_t account_idx, const uin return it2->second; } -monero_subaddress PyMoneroWalletRpc::get_address_index(const std::string& address) const { - auto params = std::make_shared(address); +monero_subaddress monero_wallet_rpc::get_address_index(const std::string& address) const { + MTRACE("monero_wallet_rpc:.get_address_index(" << address << ")"); + + auto params = std::make_shared(address); auto res = m_rpc->send_json_request("get_address_index", params); auto tmplt = std::make_shared(); PyMoneroSubaddress::from_property_tree(res, tmplt); return *tmplt; } -monero_integrated_address PyMoneroWalletRpc::get_integrated_address(const std::string& standard_address, const std::string& payment_id) const { - auto params = std::make_shared(standard_address, payment_id); +monero_integrated_address monero_wallet_rpc::get_integrated_address(const std::string& standard_address, const std::string& payment_id) const { + MTRACE("monero_wallet_rpc::get_integrated_address(" << standard_address << ", " << payment_id << ")"); + + auto params = std::make_shared(standard_address, payment_id); auto res = m_rpc->send_json_request("make_integrated_address", params); auto tmplt = std::make_shared(); PyMoneroIntegratedAddress::from_property_tree(res, tmplt); return decode_integrated_address(tmplt->m_integrated_address); } -monero_integrated_address PyMoneroWalletRpc::decode_integrated_address(const std::string& integrated_address) const { - auto params = std::make_shared(integrated_address); +monero_integrated_address monero_wallet_rpc::decode_integrated_address(const std::string& integrated_address) const { + MTRACE("monero_wallet_rpc::decode_integrated_address(" << integrated_address << ")"); + + auto params = std::make_shared(integrated_address); auto res = m_rpc->send_json_request("split_integrated_address", params); auto tmplt = std::make_shared(); PyMoneroIntegratedAddress::from_property_tree(res, tmplt); @@ -452,20 +538,20 @@ monero_integrated_address PyMoneroWalletRpc::decode_integrated_address(const std return *tmplt; } -uint64_t PyMoneroWalletRpc::get_height() const { +uint64_t monero_wallet_rpc::get_height() const { auto res = m_rpc->send_json_request("get_height"); - return PyMoneroWalletGetHeightResponse::from_property_tree(res); + return monero_wallet_get_height_response::from_property_tree(res); } -uint64_t PyMoneroWalletRpc::get_daemon_height() const { +uint64_t monero_wallet_rpc::get_daemon_height() const { throw std::runtime_error("monero-wallet-rpc does not support getting the chain height"); } -uint64_t PyMoneroWalletRpc::get_height_by_date(uint16_t year, uint8_t month, uint8_t day) const { +uint64_t monero_wallet_rpc::get_height_by_date(uint16_t year, uint8_t month, uint8_t day) const { throw std::runtime_error("monero-wallet-rpc does not support getting a height by date"); } -monero_sync_result PyMoneroWalletRpc::refresh(const std::shared_ptr& params) { +monero_sync_result monero_wallet_rpc::refresh(const std::shared_ptr& params) { boost::lock_guard lock(m_sync_mutex); try { auto node = m_rpc->send_json_request("refresh", params); @@ -480,36 +566,38 @@ monero_sync_result PyMoneroWalletRpc::refresh(const std::shared_ptr(); +monero_sync_result monero_wallet_rpc::sync() { + MTRACE("monero_wallet_rpc::sync()"); + auto params = std::make_shared(); return refresh(params); } -monero_sync_result PyMoneroWalletRpc::sync(monero_wallet_listener& listener) { +monero_sync_result monero_wallet_rpc::sync(monero_wallet_listener& listener) { throw std::runtime_error("Monero Wallet RPC does not support reporting sync progress"); } -monero_sync_result PyMoneroWalletRpc::sync(uint64_t start_height, monero_wallet_listener& listener) { +monero_sync_result monero_wallet_rpc::sync(uint64_t start_height, monero_wallet_listener& listener) { throw std::runtime_error("Monero Wallet RPC does not support reporting sync progress"); } -monero_sync_result PyMoneroWalletRpc::sync(uint64_t start_height) { - auto params = std::make_shared(start_height); +monero_sync_result monero_wallet_rpc::sync(uint64_t start_height) { + MTRACE("monero_wallet_rpc::sync(" << start_height << ")"); + auto params = std::make_shared(start_height); return refresh(params); } -void PyMoneroWalletRpc::start_syncing(uint64_t sync_period_in_ms) { +void monero_wallet_rpc::start_syncing(uint64_t sync_period_in_ms) { // convert ms to seconds for rpc parameter uint64_t sync_period_in_seconds = sync_period_in_ms / 1000; // send rpc request - auto params = std::make_shared(true, sync_period_in_seconds); + auto params = std::make_shared(true, sync_period_in_seconds); m_rpc->send_json_request("auto_refresh", params); // update sync period for poller @@ -520,61 +608,66 @@ void PyMoneroWalletRpc::start_syncing(uint64_t sync_period_in_ms) { poll(); } -void PyMoneroWalletRpc::stop_syncing() { - auto params = std::make_shared(false); +void monero_wallet_rpc::stop_syncing() { + auto params = std::make_shared(false); m_rpc->send_json_request("auto_refresh", params); } -void PyMoneroWalletRpc::scan_txs(const std::vector& tx_hashes) { +void monero_wallet_rpc::scan_txs(const std::vector& tx_hashes) { + MTRACE("monero_wallet_rpc::scan_txs()"); if (tx_hashes.empty()) throw std::runtime_error("No tx hashes given to scan"); - auto params = std::make_shared(tx_hashes); + auto params = std::make_shared(tx_hashes); m_rpc->send_json_request("scan_tx", params); poll(); } -void PyMoneroWalletRpc::rescan_spent() { +void monero_wallet_rpc::rescan_spent() { + MTRACE("monero_wallet_rpc::rescan_spent()"); m_rpc->send_json_request("rescan_spent"); } -void PyMoneroWalletRpc::rescan_blockchain() { +void monero_wallet_rpc::rescan_blockchain() { + MTRACE("monero_wallet_rpc::rescan_blockchain()"); m_rpc->send_json_request("rescan_blockchain"); } -uint64_t PyMoneroWalletRpc::get_balance() const { +uint64_t monero_wallet_rpc::get_balance() const { auto wallet_balance = get_balances(boost::none, boost::none); return wallet_balance->m_balance; } -uint64_t PyMoneroWalletRpc::get_balance(uint32_t account_index) const { +uint64_t monero_wallet_rpc::get_balance(uint32_t account_index) const { auto wallet_balance = get_balances(account_index, boost::none); return wallet_balance->m_balance; } -uint64_t PyMoneroWalletRpc::get_balance(uint32_t account_idx, uint32_t subaddress_idx) const { +uint64_t monero_wallet_rpc::get_balance(uint32_t account_idx, uint32_t subaddress_idx) const { auto wallet_balance = get_balances(account_idx, subaddress_idx); return wallet_balance->m_balance; } -uint64_t PyMoneroWalletRpc::get_unlocked_balance() const { +uint64_t monero_wallet_rpc::get_unlocked_balance() const { auto wallet_balance = get_balances(boost::none, boost::none); return wallet_balance->m_unlocked_balance; } -uint64_t PyMoneroWalletRpc::get_unlocked_balance(uint32_t account_index) const { +uint64_t monero_wallet_rpc::get_unlocked_balance(uint32_t account_index) const { auto wallet_balance = get_balances(account_index, boost::none); return wallet_balance->m_unlocked_balance; } -uint64_t PyMoneroWalletRpc::get_unlocked_balance(uint32_t account_idx, uint32_t subaddress_idx) const { +uint64_t monero_wallet_rpc::get_unlocked_balance(uint32_t account_idx, uint32_t subaddress_idx) const { auto wallet_balance = get_balances(account_idx, subaddress_idx); return wallet_balance->m_unlocked_balance; } -monero_account PyMoneroWalletRpc::get_account(const uint32_t account_idx, bool include_subaddresses) const { +monero_account monero_wallet_rpc::get_account(const uint32_t account_idx, bool include_subaddresses) const { return get_account(account_idx, include_subaddresses, false); } -monero_account PyMoneroWalletRpc::get_account(const uint32_t account_idx, bool include_subaddresses, bool skip_balances) const { +monero_account monero_wallet_rpc::get_account(const uint32_t account_idx, bool include_subaddresses, bool skip_balances) const { + MTRACE("monero_wallet_rpc::get_account(" << account_idx << ", " << include_subaddresses << ")"); + for(auto& account : monero::monero_wallet::get_accounts()) { if (account.m_index.get() == account_idx) { if (include_subaddresses) { @@ -587,12 +680,14 @@ monero_account PyMoneroWalletRpc::get_account(const uint32_t account_idx, bool i throw std::runtime_error("Account with index " + std::to_string(account_idx) + " does not exist"); } -std::vector PyMoneroWalletRpc::get_accounts(bool include_subaddresses, const std::string& tag) const { +std::vector monero_wallet_rpc::get_accounts(bool include_subaddresses, const std::string& tag) const { return get_accounts(include_subaddresses, tag, false); } -std::vector PyMoneroWalletRpc::get_accounts(bool include_subaddresses, const std::string& tag, bool skip_balances) const { - auto params = std::make_shared(tag); +std::vector monero_wallet_rpc::get_accounts(bool include_subaddresses, const std::string& tag, bool skip_balances) const { + MTRACE("monero_wallet_rpc::get_accounts(" << include_subaddresses << ", " << tag << ")"); + + auto params = std::make_shared(tag); auto node = m_rpc->send_json_request("get_accounts", params); std::vector accounts; PyMoneroAccount::from_property_tree(node, accounts); @@ -613,10 +708,10 @@ std::vector PyMoneroWalletRpc::get_accounts(bool include_subaddr } if (!skip_balances) { - auto params2 = std::make_shared(true); + auto params2 = std::make_shared(true); auto node2 = m_rpc->send_json_request("get_balance", params2); - auto bal_res = std::make_shared(); - PyMoneroGetBalanceResponse::from_property_tree(node2, bal_res); + auto bal_res = std::make_shared(); + monero_get_balance_response::from_property_tree(node2, bal_res); for (const auto &subaddress : bal_res->m_per_subaddress) { // merge info auto account = &accounts[subaddress->m_account_index.get()]; @@ -635,8 +730,10 @@ std::vector PyMoneroWalletRpc::get_accounts(bool include_subaddr return accounts; } -monero_account PyMoneroWalletRpc::create_account(const std::string& label) { - auto params = std::make_shared(label); +monero_account monero_wallet_rpc::create_account(const std::string& label) { + MTRACE("monero_wallet_rpc::create_account(" << label << ")"); + + auto params = std::make_shared(label); auto node = m_rpc->send_json_request("create_account", params); monero_account res; res.m_balance = 0; @@ -662,9 +759,12 @@ monero_account PyMoneroWalletRpc::create_account(const std::string& label) { return res; } -std::vector PyMoneroWalletRpc::get_subaddresses(const uint32_t account_idx, const std::vector& subaddress_indices, bool skip_balances) const { +std::vector monero_wallet_rpc::get_subaddresses(const uint32_t account_idx, const std::vector& subaddress_indices, bool skip_balances) const { + MTRACE("monero_wallet_rpc::get_subaddresses(" << account_idx << ", ...)"); + MTRACE("monero_wallet_rpc::get_subaddresses(): Subaddress indices size: " << subaddress_indices.size()); + // fetch subaddresses - auto params = std::make_shared(account_idx, subaddress_indices); + auto params = std::make_shared(account_idx, subaddress_indices); auto node = m_rpc->send_json_request("get_address", params); std::vector subaddresses; std::vector> subaddresses_ptr; @@ -717,16 +817,17 @@ std::vector PyMoneroWalletRpc::get_subaddresses(const uint32_ return subaddresses; } -std::vector PyMoneroWalletRpc::get_subaddresses(uint32_t account_idx, const std::vector& subaddress_indices) const { +std::vector monero_wallet_rpc::get_subaddresses(uint32_t account_idx, const std::vector& subaddress_indices) const { return get_subaddresses(account_idx, subaddress_indices, false); } -std::vector PyMoneroWalletRpc::get_subaddresses(const uint32_t account_idx) const { +std::vector monero_wallet_rpc::get_subaddresses(const uint32_t account_idx) const { std::vector empty_indices; return get_subaddresses(account_idx, empty_indices); } -monero_subaddress PyMoneroWalletRpc::get_subaddress(const uint32_t account_idx, const uint32_t subaddress_idx) const { +monero_subaddress monero_wallet_rpc::get_subaddress(const uint32_t account_idx, const uint32_t subaddress_idx) const { + std::vector subaddress_indices; subaddress_indices.push_back(subaddress_idx); auto subaddresses = get_subaddresses(account_idx, subaddress_indices); @@ -735,8 +836,10 @@ monero_subaddress PyMoneroWalletRpc::get_subaddress(const uint32_t account_idx, return subaddresses[0]; } -monero_subaddress PyMoneroWalletRpc::create_subaddress(uint32_t account_idx, const std::string& label) { - auto params = std::make_shared(account_idx, label); +monero_subaddress monero_wallet_rpc::create_subaddress(uint32_t account_idx, const std::string& label) { + MTRACE("monero_wallet_rpc::create_subaddress(" << account_idx << ", " << label << ")"); + + auto params = std::make_shared(account_idx, label); auto node = m_rpc->send_json_request("create_address", params); monero_subaddress sub; sub.m_account_index = account_idx; @@ -756,13 +859,15 @@ monero_subaddress PyMoneroWalletRpc::create_subaddress(uint32_t account_idx, con return sub; } -void PyMoneroWalletRpc::set_subaddress_label(uint32_t account_idx, uint32_t subaddress_idx, const std::string& label) { - auto params = std::make_shared(account_idx, subaddress_idx, label); +void monero_wallet_rpc::set_subaddress_label(uint32_t account_idx, uint32_t subaddress_idx, const std::string& label) { + MTRACE("monero_wallet_rpc::set_subaddress_label(" << account_idx << ", " << subaddress_idx << ", " << label << ")"); + + auto params = std::make_shared(account_idx, subaddress_idx, label); m_rpc->send_json_request("label_address", params); } -std::string PyMoneroWalletRpc::export_outputs(bool all) const { - auto params = std::make_shared(all); +std::string monero_wallet_rpc::export_outputs(bool all) const { + auto params = std::make_shared(all); auto node = m_rpc->send_json_request("export_outputs", params); for (auto it = node.begin(); it != node.end(); ++it) { @@ -774,8 +879,8 @@ std::string PyMoneroWalletRpc::export_outputs(bool all) const { throw std::runtime_error("Could not get outputs hex"); } -int PyMoneroWalletRpc::import_outputs(const std::string& outputs_hex) { - auto params = std::make_shared(outputs_hex); +int monero_wallet_rpc::import_outputs(const std::string& outputs_hex) { + auto params = std::make_shared(outputs_hex); auto node = m_rpc->send_json_request("import_outputs", params); int num_imported = 0; @@ -791,34 +896,38 @@ int PyMoneroWalletRpc::import_outputs(const std::string& outputs_hex) { return num_imported; } -std::vector> PyMoneroWalletRpc::export_key_images(bool all) const { - auto params = std::make_shared(all); +std::vector> monero_wallet_rpc::export_key_images(bool all) const { + MTRACE("monero_wallet_rpc::export_key_images()"); + + auto params = std::make_shared(all); auto node = m_rpc->send_json_request("export_key_images", params); std::vector> key_images; PyMoneroKeyImage::from_property_tree(node, key_images); return key_images; } -std::shared_ptr PyMoneroWalletRpc::import_key_images(const std::vector>& key_images) { - auto params = std::make_shared(key_images); +std::shared_ptr monero_wallet_rpc::import_key_images(const std::vector>& key_images) { + MTRACE("monero_wallet_rpc::import_key_images()"); + + auto params = std::make_shared(key_images); auto node = m_rpc->send_json_request("import_key_images", params); auto import_result = std::make_shared(); PyMoneroKeyImageImportResult::from_property_tree(node, import_result); return import_result; } -void PyMoneroWalletRpc::freeze_output(const std::string& key_image) { - auto params = std::make_shared(key_image); +void monero_wallet_rpc::freeze_output(const std::string& key_image) { + auto params = std::make_shared(key_image); m_rpc->send_json_request("freeze", params); } -void PyMoneroWalletRpc::thaw_output(const std::string& key_image) { - auto params = std::make_shared(key_image); +void monero_wallet_rpc::thaw_output(const std::string& key_image) { + auto params = std::make_shared(key_image); m_rpc->send_json_request("thaw", params); } -bool PyMoneroWalletRpc::is_output_frozen(const std::string& key_image) { - auto params = std::make_shared(key_image); +bool monero_wallet_rpc::is_output_frozen(const std::string& key_image) { + auto params = std::make_shared(key_image); auto node = m_rpc->send_json_request("frozen", params); for(auto it = node.begin(); it != node.end(); ++it) { @@ -830,7 +939,7 @@ bool PyMoneroWalletRpc::is_output_frozen(const std::string& key_image) { throw std::runtime_error("Could not get output"); } -monero_tx_priority PyMoneroWalletRpc::get_default_fee_priority() const { +monero_tx_priority monero_wallet_rpc::get_default_fee_priority() const { auto node = m_rpc->send_json_request("get_default_fee_priority"); for(auto it = node.begin(); it != node.end(); ++it) { @@ -849,7 +958,9 @@ monero_tx_priority PyMoneroWalletRpc::get_default_fee_priority() const { throw std::runtime_error("Could not get default fee priority"); } -std::vector> PyMoneroWalletRpc::create_txs(const monero_tx_config& conf) { +std::vector> monero_wallet_rpc::create_txs(const monero_tx_config& conf) { + MTRACE("monero_wallet_rpc::create_txs"); + // validate, copy, and normalize request monero_tx_config config = conf; if (config.m_address == boost::none && config.m_destinations.empty()) throw std::runtime_error("Destinations cannot be empty"); @@ -872,16 +983,16 @@ std::vector> PyMoneroWalletRpc::create_txs(con } // build request parameters - auto params = std::make_shared(config); + auto params = std::make_shared(config); std::string request_path = "transfer"; if (bool_equals_2(true, config.m_can_split)) request_path = "transfer_split"; boost::property_tree::ptree node; try { node = m_rpc->send_json_request(request_path, params); - } catch (const PyMoneroRpcError& ex) { + } catch (const monero_rpc_error& ex) { std::string message = ex.what(); - if (message.find("WALLET_RPC_ERROR_CODE_WRONG_ADDRESS") != std::string::npos) throw PyMoneroError("Invalid destination address"); + if (message.find("WALLET_RPC_ERROR_CODE_WRONG_ADDRESS") != std::string::npos) throw monero_error("Invalid destination address"); throw; } @@ -935,7 +1046,7 @@ std::vector> PyMoneroWalletRpc::create_txs(con return tx_set->m_txs; } -std::vector> PyMoneroWalletRpc::sweep_unlocked(const monero_tx_config& config) { +std::vector> monero_wallet_rpc::sweep_unlocked(const monero_tx_config& config) { // validate config std::vector> destinations = config.get_normalized_destinations(); if (destinations.size() != 1) throw std::runtime_error("Must specify exactly one destination to sweep to"); @@ -1002,7 +1113,9 @@ std::vector> PyMoneroWalletRpc::sweep_unlocked return txs; } -std::shared_ptr PyMoneroWalletRpc::sweep_output(const monero_tx_config& config) { +std::shared_ptr monero_wallet_rpc::sweep_output(const monero_tx_config& config) { + MTRACE("monero_wallet_rpc::sweep_output()"); + // validate request std::vector> destinations = config.get_normalized_destinations(); if (config.m_sweep_each_subaddress != boost::none) throw std::runtime_error("Cannot sweep each subaddress when sweeping single output"); @@ -1012,7 +1125,7 @@ std::shared_ptr PyMoneroWalletRpc::sweep_output(const monero_t if (destinations[0]->m_address == boost::none) throw std::runtime_error("Must specify destination address to sweep to"); if (destinations[0]->m_amount != boost::none) throw std::runtime_error("Cannot specify amount to sweep"); - auto params = std::make_shared(config); + auto params = std::make_shared(config); auto node = m_rpc->send_json_request("sweep_single", params); if (bool_equals_2(true, config.m_relay)) poll(); auto set = std::make_shared(); @@ -1022,8 +1135,10 @@ std::shared_ptr PyMoneroWalletRpc::sweep_output(const monero_t return tx; } -std::vector> PyMoneroWalletRpc::sweep_dust(bool relay) { - auto params = std::make_shared(relay); +std::vector> monero_wallet_rpc::sweep_dust(bool relay) { + MTRACE("monero_wallet_rpc::sweep_dust()"); + + auto params = std::make_shared(relay); auto node = m_rpc->send_json_request("sweep_dust", params); if (relay) poll(); auto set = std::make_shared(); @@ -1031,13 +1146,15 @@ std::vector> PyMoneroWalletRpc::sweep_dust(boo return set->m_txs; } -std::vector PyMoneroWalletRpc::relay_txs(const std::vector& tx_metadatas) { +std::vector monero_wallet_rpc::relay_txs(const std::vector& tx_metadatas) { + MTRACE("monero_wallet_rpc::relay_txs()"); + if (tx_metadatas.empty()) throw std::runtime_error("Must provide an array of tx metadata to relay"); std::vector tx_hashes; for (const auto &tx_metadata : tx_metadatas) { - auto params = std::make_shared(tx_metadata); + auto params = std::make_shared(tx_metadata); auto node = m_rpc->send_json_request("relay_tx", params); for (auto it = node.begin(); it != node.end(); ++it) { @@ -1049,8 +1166,8 @@ std::vector PyMoneroWalletRpc::relay_txs(const std::vector(); +monero_tx_set monero_wallet_rpc::describe_tx_set(const monero_tx_set& tx_set) { + auto params = std::make_shared(); params->m_multisig_txset = tx_set.m_multisig_tx_hex; params->m_unsigned_txset = tx_set.m_unsigned_tx_hex; auto node = m_rpc->send_json_request("describe_transfer", params); @@ -1059,16 +1176,16 @@ monero_tx_set PyMoneroWalletRpc::describe_tx_set(const monero_tx_set& tx_set) { return *set; } -monero_tx_set PyMoneroWalletRpc::sign_txs(const std::string& unsigned_tx_hex) { - auto params = std::make_shared(unsigned_tx_hex); +monero_tx_set monero_wallet_rpc::sign_txs(const std::string& unsigned_tx_hex) { + auto params = std::make_shared(unsigned_tx_hex); auto node = m_rpc->send_json_request("sign_transfer", params); auto set = std::make_shared(); PyMoneroTxSet::from_sent_txs(node, set); return *set; } -std::vector PyMoneroWalletRpc::submit_txs(const std::string& signed_tx_hex) { - auto params = std::make_shared(signed_tx_hex); +std::vector monero_wallet_rpc::submit_txs(const std::string& signed_tx_hex) { + auto params = std::make_shared(signed_tx_hex); auto node = m_rpc->send_json_request("submit_transfer", params); poll(); std::vector hashes; @@ -1088,39 +1205,41 @@ std::vector PyMoneroWalletRpc::submit_txs(const std::string& signed return hashes; } -std::string PyMoneroWalletRpc::sign_message(const std::string& msg, monero_message_signature_type signature_type, uint32_t account_idx, uint32_t subaddress_idx) const { - auto params = std::make_shared(msg, signature_type, account_idx, subaddress_idx); +std::string monero_wallet_rpc::sign_message(const std::string& msg, monero_message_signature_type signature_type, uint32_t account_idx, uint32_t subaddress_idx) const { + auto params = std::make_shared(msg, signature_type, account_idx, subaddress_idx); auto node = m_rpc->send_json_request("sign", params); - return PyMoneroReserveProofSignature::from_property_tree(node); + return monero_signature::from_property_tree(node); } -monero_message_signature_result PyMoneroWalletRpc::verify_message(const std::string& msg, const std::string& address, const std::string& signature) const { - auto params = std::make_shared(msg, address, signature); +monero_message_signature_result monero_wallet_rpc::verify_message(const std::string& msg, const std::string& address, const std::string& signature) const { + auto params = std::make_shared(msg, address, signature); auto sig_result = std::make_shared(); sig_result->m_is_good = false; try { auto node = m_rpc->send_json_request("verify", params); PyMoneroMessageSignatureResult::from_property_tree(node, sig_result); - } catch (const PyMoneroRpcError& ex) { + } catch (const monero_rpc_error& ex) { if (ex.code != -2) throw; } return *sig_result; } -void normalize_wallet_error(const PyMoneroRpcError& ex) { +void normalize_wallet_error(const monero_rpc_error& ex) { // normalize error message if (ex.code == -1 && std::string(ex.what()).find("basic_string") != std::string::npos) { - throw PyMoneroRpcError(-1, "Must provide signature to check tx proof"); + throw monero_rpc_error(-1, "Must provide signature to check tx proof"); } else if (ex.code == -8 && ex.what() == std::string("TX ID has invalid format")) { - throw PyMoneroRpcError(-8, "TX hash has invalid format"); + throw monero_rpc_error(-8, "TX hash has invalid format"); } throw; } -std::string PyMoneroWalletRpc::get_tx_key(const std::string& tx_hash) const { +std::string monero_wallet_rpc::get_tx_key(const std::string& tx_hash) const { + MTRACE("monero_wallet_rpc::get_tx_key()"); + try { - auto params = std::make_shared(tx_hash); + auto params = std::make_shared(tx_hash); auto node = m_rpc->send_json_request("get_tx_key", params); for (auto it = node.begin(); it != node.end(); ++it) { @@ -1130,102 +1249,114 @@ std::string PyMoneroWalletRpc::get_tx_key(const std::string& tx_hash) const { return it->second.data(); } } - } catch (const PyMoneroRpcError& ex) { + } catch (const monero_rpc_error& ex) { normalize_wallet_error(ex); } throw std::runtime_error("Could not get tx key"); } -std::shared_ptr PyMoneroWalletRpc::check_tx_key(const std::string& tx_hash, const std::string& tx_key, const std::string& address) const { +std::shared_ptr monero_wallet_rpc::check_tx_key(const std::string& tx_hash, const std::string& tx_key, const std::string& address) const { + MTRACE("monero_wallet_rpc::check_tx_key()"); + auto check = std::make_shared(); try { - auto params = std::make_shared(tx_hash, tx_key, address); + auto params = std::make_shared(tx_hash, tx_key, address); auto node = m_rpc->send_json_request("check_tx_key", params); check->m_is_good = true; PyMoneroCheckTxProof::from_property_tree(node, check); - } catch (const PyMoneroRpcError& ex) { + } catch (const monero_rpc_error& ex) { normalize_wallet_error(ex); } return check; } -std::string PyMoneroWalletRpc::get_tx_proof(const std::string& tx_hash, const std::string& address, const std::string& message) const { +std::string monero_wallet_rpc::get_tx_proof(const std::string& tx_hash, const std::string& address, const std::string& message) const { std::string tx_proof; try { - auto params = std::make_shared(tx_hash, message); + auto params = std::make_shared(tx_hash, message); params->m_address = address; auto node = m_rpc->send_json_request("get_tx_proof", params); - tx_proof = PyMoneroReserveProofSignature::from_property_tree(node); - } catch (const PyMoneroRpcError& ex) { + tx_proof = monero_signature::from_property_tree(node); + } catch (const monero_rpc_error& ex) { normalize_wallet_error(ex); } return tx_proof; } -std::shared_ptr PyMoneroWalletRpc::check_tx_proof(const std::string& tx_hash, const std::string& address, const std::string& message, const std::string& signature) const { +std::shared_ptr monero_wallet_rpc::check_tx_proof(const std::string& tx_hash, const std::string& address, const std::string& message, const std::string& signature) const { + MTRACE("monero_wallet_rpc::check_tx_proof()"); + auto check = std::make_shared(); try { - auto params = std::make_shared(tx_hash, address, message, signature); + auto params = std::make_shared(tx_hash, address, message, signature); auto node = m_rpc->send_json_request("check_tx_proof", params); PyMoneroCheckTxProof::from_property_tree(node, check); - } catch (const PyMoneroRpcError& ex) { + } catch (const monero_rpc_error& ex) { normalize_wallet_error(ex); } return check; } -std::string PyMoneroWalletRpc::get_spend_proof(const std::string& tx_hash, const std::string& message) const { +std::string monero_wallet_rpc::get_spend_proof(const std::string& tx_hash, const std::string& message) const { + MTRACE("monero_wallet_rpc::get_spend_proof()"); + std::string spend_proof; try { - auto params = std::make_shared(tx_hash, message); + auto params = std::make_shared(tx_hash, message); auto node = m_rpc->send_json_request("get_spend_proof", params); - spend_proof = PyMoneroReserveProofSignature::from_property_tree(node); - } catch (const PyMoneroRpcError& ex) { + spend_proof = monero_signature::from_property_tree(node); + } catch (const monero_rpc_error& ex) { normalize_wallet_error(ex); } return spend_proof; } -bool PyMoneroWalletRpc::check_spend_proof(const std::string& tx_hash, const std::string& message, const std::string& signature) const { +bool monero_wallet_rpc::check_spend_proof(const std::string& tx_hash, const std::string& message, const std::string& signature) const { + MTRACE("monero_wallet_rpc::check_spend_proof()"); + auto proof = std::make_shared(); try { - auto params = std::make_shared(tx_hash, message); + auto params = std::make_shared(tx_hash, message); params->m_signature = signature; auto node = m_rpc->send_json_request("check_spend_proof", params); PyMoneroCheckReserve::from_property_tree(node, proof); - } catch (const PyMoneroRpcError& ex) { + } catch (const monero_rpc_error& ex) { normalize_wallet_error(ex); } return proof->m_is_good; } -std::string PyMoneroWalletRpc::get_reserve_proof_wallet(const std::string& message) const { - auto params = std::make_shared(message); +std::string monero_wallet_rpc::get_reserve_proof_wallet(const std::string& message) const { + MTRACE("monero_wallet_rpc::get_reserve_proof_wallet()"); + auto params = std::make_shared(message); auto node = m_rpc->send_json_request("get_reserve_proof", params); - return PyMoneroReserveProofSignature::from_property_tree(node); + return monero_signature::from_property_tree(node); } -std::string PyMoneroWalletRpc::get_reserve_proof_account(uint32_t account_idx, uint64_t amount, const std::string& message) const { - auto params = std::make_shared(account_idx, amount, message); +std::string monero_wallet_rpc::get_reserve_proof_account(uint32_t account_idx, uint64_t amount, const std::string& message) const { + MTRACE("monero_wallet_rpc::get_reserve_proof_account()"); + auto params = std::make_shared(account_idx, amount, message); auto node = m_rpc->send_json_request("get_reserve_proof", params); - return PyMoneroReserveProofSignature::from_property_tree(node); + return monero_signature::from_property_tree(node); } -std::shared_ptr PyMoneroWalletRpc::check_reserve_proof(const std::string& address, const std::string& message, const std::string& signature) const { - auto params = std::make_shared(address, message, signature); +std::shared_ptr monero_wallet_rpc::check_reserve_proof(const std::string& address, const std::string& message, const std::string& signature) const { + MTRACE("monero_wallet_rpc::check_reserve_proof()"); + auto params = std::make_shared(address, message, signature); auto node = m_rpc->send_json_request("check_reserve_proof", params); auto proof = std::make_shared(); PyMoneroCheckReserve::from_property_tree(node, proof); return proof; } -std::string PyMoneroWalletRpc::get_tx_note(const std::string& tx_hash) const { +std::string monero_wallet_rpc::get_tx_note(const std::string& tx_hash) const { + MTRACE("monero_wallet_rpc::get_tx_note()"); std::vector tx_hashes; tx_hashes.push_back(tx_hash); auto notes = get_tx_notes(tx_hashes); @@ -1233,8 +1364,9 @@ std::string PyMoneroWalletRpc::get_tx_note(const std::string& tx_hash) const { return notes[0]; } -std::vector PyMoneroWalletRpc::get_tx_notes(const std::vector& tx_hashes) const { - auto params = std::make_shared(tx_hashes); +std::vector monero_wallet_rpc::get_tx_notes(const std::vector& tx_hashes) const { + MTRACE("monero_wallet_rpc::get_tx_notes()"); + auto params = std::make_shared(tx_hashes); auto node = m_rpc->send_json_request("get_tx_notes", params); std::vector notes; @@ -1253,7 +1385,8 @@ std::vector PyMoneroWalletRpc::get_tx_notes(const std::vector tx_hashes; std::vector notes; tx_hashes.push_back(tx_hash); @@ -1262,13 +1395,15 @@ void PyMoneroWalletRpc::set_tx_note(const std::string& tx_hash, const std::strin set_tx_notes(tx_hashes, notes); } -void PyMoneroWalletRpc::set_tx_notes(const std::vector& tx_hashes, const std::vector& notes) { - auto params = std::make_shared(tx_hashes, notes); +void monero_wallet_rpc::set_tx_notes(const std::vector& tx_hashes, const std::vector& notes) { + MTRACE("monero_wallet_rpc::set_tx_notes()"); + auto params = std::make_shared(tx_hashes, notes); m_rpc->send_json_request("set_tx_notes", params); } -std::vector PyMoneroWalletRpc::get_address_book_entries(const std::vector& indices) const { - auto params = std::make_shared(indices); +std::vector monero_wallet_rpc::get_address_book_entries(const std::vector& indices) const { + MTRACE("monero_wallet_rpc::get_address_book_entries()"); + auto params = std::make_shared(indices); auto node = m_rpc->send_json_request("get_address_book", params); std::vector> entries_ptr; PyMoneroAddressBookEntry::from_property_tree(node, entries_ptr); @@ -1281,8 +1416,9 @@ std::vector PyMoneroWalletRpc::get_address_book_entri return entries; } -uint64_t PyMoneroWalletRpc::add_address_book_entry(const std::string& address, const std::string& description) { - auto params = std::make_shared(address, description); +uint64_t monero_wallet_rpc::add_address_book_entry(const std::string& address, const std::string& description) { + MTRACE("monero_wallet_rpc::add_address_book_entry()"); + auto params = std::make_shared(address, description); auto node = m_rpc->send_json_request("add_address_book", params); for (auto it = node.begin(); it != node.end(); ++it) { @@ -1296,67 +1432,70 @@ uint64_t PyMoneroWalletRpc::add_address_book_entry(const std::string& address, c throw std::runtime_error("Invalid response from wallet rpc"); } -void PyMoneroWalletRpc::edit_address_book_entry(uint64_t index, bool set_address, const std::string& address, bool set_description, const std::string& description) { - auto params = std::make_shared(index, set_address, address, set_description, description); +void monero_wallet_rpc::edit_address_book_entry(uint64_t index, bool set_address, const std::string& address, bool set_description, const std::string& description) { + MTRACE("monero_wallet_rpc::edit_address_book_entry()"); + auto params = std::make_shared(index, set_address, address, set_description, description); m_rpc->send_json_request("edit_address_book", params); } -void PyMoneroWalletRpc::delete_address_book_entry(uint64_t index) { - auto params = std::make_shared(index); +void monero_wallet_rpc::delete_address_book_entry(uint64_t index) { + auto params = std::make_shared(index); m_rpc->send_json_request("delete_address_book", params); } -void PyMoneroWalletRpc::tag_accounts(const std::string& tag, const std::vector& account_indices) { - auto params = std::make_shared(tag, account_indices); +void monero_wallet_rpc::tag_accounts(const std::string& tag, const std::vector& account_indices) { + auto params = std::make_shared(tag, account_indices); m_rpc->send_json_request("tag_accounts", params); } -void PyMoneroWalletRpc::untag_accounts(const std::vector& account_indices) { - auto params = std::make_shared(account_indices); +void monero_wallet_rpc::untag_accounts(const std::vector& account_indices) { + auto params = std::make_shared(account_indices); m_rpc->send_json_request("untag_accounts", params); } -std::vector> PyMoneroWalletRpc::get_account_tags() { +std::vector> monero_wallet_rpc::get_account_tags() { auto res = m_rpc->send_json_request("get_account_tags"); - std::vector> account_tags; - PyMoneroAccountTag::from_property_tree(res, account_tags); + std::vector> account_tags; + monero_account_tag::from_property_tree(res, account_tags); return account_tags; } -void PyMoneroWalletRpc::set_account_tag_label(const std::string& tag, const std::string& label) { - auto params = std::make_shared(tag, label); +void monero_wallet_rpc::set_account_tag_label(const std::string& tag, const std::string& label) { + auto params = std::make_shared(tag, label); m_rpc->send_json_request("set_account_tag_description", params); } -std::string PyMoneroWalletRpc::get_payment_uri(const monero_tx_config& config) const { - auto params = std::make_shared(config); +std::string monero_wallet_rpc::get_payment_uri(const monero_tx_config& config) const { + MTRACE("monero_wallet_rpc::get_payment_uri()"); + auto params = std::make_shared(config); auto res = m_rpc->send_json_request("make_uri", params); - return PyMoneroGetParsePaymentUri::from_property_tree(res); + return monero_get_payment_uri::from_property_tree(res); } -std::shared_ptr PyMoneroWalletRpc::parse_payment_uri(const std::string& uri) const { - auto params = std::make_shared(uri); +std::shared_ptr monero_wallet_rpc::parse_payment_uri(const std::string& uri) const { + MTRACE("monero_wallet_rpc::parse_payment_uri(" << uri << ")"); + auto params = std::make_shared(uri); auto res = m_rpc->send_json_request("parse_uri", params); - auto uri_response = std::make_shared(); - PyMoneroGetParsePaymentUri::from_property_tree(res, uri_response); + auto uri_response = std::make_shared(); + monero_get_payment_uri::from_property_tree(res, uri_response); return uri_response->to_tx_config(); } -void PyMoneroWalletRpc::set_attribute(const std::string& key, const std::string& val) { - auto params = std::make_shared(key, val); +void monero_wallet_rpc::set_attribute(const std::string& key, const std::string& val) { + auto params = std::make_shared(key, val); m_rpc->send_json_request("set_attribute", params); } -bool PyMoneroWalletRpc::get_attribute(const std::string& key, std::string& value) const { +bool monero_wallet_rpc::get_attribute(const std::string& key, std::string& value) const { try { - auto params = std::make_shared(key); + auto params = std::make_shared(key); auto res = m_rpc->send_json_request("get_attribute", params); - PyMoneroKeyValue::from_property_tree(res, params); + monero_key_value::from_property_tree(res, params); if (params->m_value == boost::none) return false; value = params->m_value.get(); return true; } - catch (const PyMoneroRpcError& ex) { + catch (const monero_rpc_error& ex) { if (ex.code == -45) { // attribute not found value = std::string(""); return true; @@ -1366,45 +1505,47 @@ bool PyMoneroWalletRpc::get_attribute(const std::string& key, std::string& value return false; } -void PyMoneroWalletRpc::start_mining(boost::optional num_threads, boost::optional background_mining, boost::optional ignore_battery) { - auto params = std::make_shared(num_threads.value_or(0), background_mining.value_or(false), ignore_battery.value_or(false)); +void monero_wallet_rpc::start_mining(boost::optional num_threads, boost::optional background_mining, boost::optional ignore_battery) { + MTRACE("monero_wallet_rpc::start_mining()"); + auto params = std::make_shared(num_threads.value_or(0), background_mining.value_or(false), ignore_battery.value_or(false)); m_rpc->send_json_request("start_mining", params); } -void PyMoneroWalletRpc::stop_mining() { +void monero_wallet_rpc::stop_mining() { + MTRACE("monero_wallet_rpc::stop_mining()"); m_rpc->send_json_request("stop_mining"); } -bool PyMoneroWalletRpc::is_multisig_import_needed() const { +bool monero_wallet_rpc::is_multisig_import_needed() const { auto res = m_rpc->send_json_request("get_balance"); - auto balance = std::make_shared(); - PyMoneroGetBalanceResponse::from_property_tree(res, balance); + auto balance = std::make_shared(); + monero_get_balance_response::from_property_tree(res, balance); return bool_equals_2(true, balance->m_multisig_import_needed); } -monero_multisig_info PyMoneroWalletRpc::get_multisig_info() const { +monero_multisig_info monero_wallet_rpc::get_multisig_info() const { auto res = m_rpc->send_json_request("is_multisig"); auto info = std::make_shared(); PyMoneroMultisigInfo::from_property_tree(res, info); return *info; } -std::string PyMoneroWalletRpc::prepare_multisig() { - auto params = std::make_shared(); +std::string monero_wallet_rpc::prepare_multisig() { + auto params = std::make_shared(); auto res = m_rpc->send_json_request("prepare_multisig", params); clear_address_cache(); - return PyMoneroPrepareMakeMultisigResponse::from_property_tree(res); + return monero_prepare_make_multisig_response::from_property_tree(res); } -std::string PyMoneroWalletRpc::make_multisig(const std::vector& multisig_hexes, int threshold, const std::string& password) { - auto params = std::make_shared(multisig_hexes, threshold, password); +std::string monero_wallet_rpc::make_multisig(const std::vector& multisig_hexes, int threshold, const std::string& password) { + auto params = std::make_shared(multisig_hexes, threshold, password); auto res = m_rpc->send_json_request("make_multisig", params); clear_address_cache(); - return PyMoneroPrepareMakeMultisigResponse::from_property_tree(res); + return monero_prepare_make_multisig_response::from_property_tree(res); } -monero_multisig_init_result PyMoneroWalletRpc::exchange_multisig_keys(const std::vector& multisig_hexes, const std::string& password) { - auto params = std::make_shared(multisig_hexes, password); +monero_multisig_init_result monero_wallet_rpc::exchange_multisig_keys(const std::vector& multisig_hexes, const std::string& password) { + auto params = std::make_shared(multisig_hexes, password); auto res = m_rpc->send_json_request("exchange_multisig_keys", params); clear_address_cache(); auto multisig_init = std::make_shared(); @@ -1412,59 +1553,61 @@ monero_multisig_init_result PyMoneroWalletRpc::exchange_multisig_keys(const std: return *multisig_init; } -std::string PyMoneroWalletRpc::export_multisig_hex() { +std::string monero_wallet_rpc::export_multisig_hex() { auto res = m_rpc->send_json_request("export_multisig_info"); - return PyMoneroExportMultisigHexResponse::from_property_tree(res); + return monero_export_multisig_hex_response::from_property_tree(res); } -int PyMoneroWalletRpc::import_multisig_hex(const std::vector& multisig_hexes) { - auto params = std::make_shared(multisig_hexes); +int monero_wallet_rpc::import_multisig_hex(const std::vector& multisig_hexes) { + auto params = std::make_shared(multisig_hexes); auto res = m_rpc->send_json_request("import_multisig_info", params); - return PyMoneroImportMultisigHexResponse::from_property_tree(res); + return monero_import_multisig_hex_response::from_property_tree(res); } -monero_multisig_sign_result PyMoneroWalletRpc::sign_multisig_tx_hex(const std::string& multisig_tx_hex) { - auto params = std::make_shared(multisig_tx_hex); +monero_multisig_sign_result monero_wallet_rpc::sign_multisig_tx_hex(const std::string& multisig_tx_hex) { + auto params = std::make_shared(multisig_tx_hex); auto res = m_rpc->send_json_request("sign_multisig", params); auto multisig_result = std::make_shared(); PyMoneroMultisigSignResult::from_property_tree(res, multisig_result); return *multisig_result; } -std::vector PyMoneroWalletRpc::submit_multisig_tx_hex(const std::string& signed_multisig_tx_hex) { - auto params = std::make_shared(signed_multisig_tx_hex); +std::vector monero_wallet_rpc::submit_multisig_tx_hex(const std::string& signed_multisig_tx_hex) { + auto params = std::make_shared(signed_multisig_tx_hex); auto res = m_rpc->send_json_request("submit_multisig", params); - return PyMoneroSubmitMultisigTxHexResponse::from_property_tree(res); + return monero_submit_multisig_tx_hex_response::from_property_tree(res); } -void PyMoneroWalletRpc::change_password(const std::string& old_password, const std::string& new_password) { - auto params = std::make_shared(old_password, new_password); +void monero_wallet_rpc::change_password(const std::string& old_password, const std::string& new_password) { + MTRACE("monero_wallet_rpc::change_password(" << "***" << ", ***)"); + auto params = std::make_shared(old_password, new_password); m_rpc->send_json_request("change_wallet_password", params); } -void PyMoneroWalletRpc::save() { +void monero_wallet_rpc::save() { + MTRACE("monero_wallet_rpc::save()"); m_rpc->send_json_request("store"); } -bool PyMoneroWalletRpc::is_closed() const { +bool monero_wallet_rpc::is_closed() const { try { get_primary_address(); - } catch (const PyMoneroRpcError& ex) { + } catch (const monero_rpc_error& ex) { return ex.code == -8 && ex.what() == std::string("No wallet file"); } return false; } -void PyMoneroWalletRpc::close(bool save) { - MTRACE("PyMoneroWalletRpc::close()"); +void monero_wallet_rpc::close(bool save) { + MTRACE("monero_wallet_rpc::close()"); clear(); - auto params = std::make_shared(save); + auto params = std::make_shared(save); m_rpc->send_json_request("close_wallet", params); } -std::shared_ptr PyMoneroWalletRpc::get_balances(boost::optional account_idx, boost::optional subaddress_idx) const { - auto balance = std::make_shared(); +std::shared_ptr monero_wallet_rpc::get_balances(boost::optional account_idx, boost::optional subaddress_idx) const { + auto balance = std::make_shared(); if (account_idx == boost::none) { if (subaddress_idx != boost::none) throw std::runtime_error("Must provide account index with subaddress index"); @@ -1479,10 +1622,10 @@ std::shared_ptr PyMoneroWalletRpc::get_balances(boost::op return balance; } else { - auto params = std::make_shared(account_idx.get(), subaddress_idx); + auto params = std::make_shared(account_idx.get(), subaddress_idx); auto res = m_rpc->send_json_request("get_balance", params); - auto bal_res = std::make_shared(); - PyMoneroGetBalanceResponse::from_property_tree(res, bal_res); + auto bal_res = std::make_shared(); + monero_get_balance_response::from_property_tree(res, bal_res); if (subaddress_idx == boost::none) { balance->m_balance = bal_res->m_balance.get(); @@ -1499,7 +1642,7 @@ std::shared_ptr PyMoneroWalletRpc::get_balances(boost::op return balance; } -PyMoneroWalletRpc* PyMoneroWalletRpc::create_wallet_random(const std::shared_ptr &conf) { +monero_wallet_rpc* monero_wallet_rpc::create_wallet_random(const std::shared_ptr &conf) { // validate and normalize config auto config = conf->copy(); if (config.m_seed_offset != boost::none) throw std::runtime_error("Cannot specify seed offset when creating random wallet"); @@ -1513,15 +1656,15 @@ PyMoneroWalletRpc* PyMoneroWalletRpc::create_wallet_random(const std::shared_ptr std::string password = config.m_password.get(); std::string language = config.m_language.get(); - auto params = std::make_shared(filename, password, language); + auto params = std::make_shared(filename, password, language); try { m_rpc->send_json_request("create_wallet", params); } - catch (const PyMoneroRpcError& ex) { handle_create_wallet_error(ex, filename); } + catch (const monero_rpc_error& ex) { handle_create_wallet_error(ex, filename); } clear(); m_path = config.m_path.get(); return this; } -PyMoneroWalletRpc* PyMoneroWalletRpc::create_wallet_from_seed(const std::shared_ptr &conf) { +monero_wallet_rpc* monero_wallet_rpc::create_wallet_from_seed(const std::shared_ptr &conf) { auto config = conf->copy(); if (config.m_language == boost::none || config.m_language->empty()) config.m_language = "English"; auto filename = config.m_path.get(); @@ -1534,15 +1677,15 @@ PyMoneroWalletRpc* PyMoneroWalletRpc::create_wallet_from_seed(const std::shared_ bool enable_multisig_experimental = false; if (config.m_save_current != boost::none) autosave_current = config.m_save_current.get(); if (config.m_is_multisig != boost::none) enable_multisig_experimental = config.m_is_multisig.get(); - auto params = std::make_shared(filename, password, seed, seed_offset, restore_height, language, autosave_current, enable_multisig_experimental); + auto params = std::make_shared(filename, password, seed, seed_offset, restore_height, language, autosave_current, enable_multisig_experimental); try { m_rpc->send_json_request("restore_deterministic_wallet", params); } - catch (const PyMoneroRpcError& ex) { handle_create_wallet_error(ex, filename); } + catch (const monero_rpc_error& ex) { handle_create_wallet_error(ex, filename); } clear(); m_path = config.m_path.get(); return this; } -PyMoneroWalletRpc* PyMoneroWalletRpc::create_wallet_from_keys(const std::shared_ptr &config) { +monero_wallet_rpc* monero_wallet_rpc::create_wallet_from_keys(const std::shared_ptr &config) { if (config->m_seed_offset != boost::none) throw std::runtime_error("Cannot specify seed offset when creating wallet from keys"); if (config->m_restore_height == boost::none) config->m_restore_height = 0; std::string filename = config->m_path.get(); @@ -1555,24 +1698,24 @@ PyMoneroWalletRpc* PyMoneroWalletRpc::create_wallet_from_keys(const std::shared_ uint64_t restore_height = config->m_restore_height.get(); bool autosave_current = false; if (config->m_save_current != boost::none) autosave_current = config->m_save_current.get(); - auto params = std::make_shared(filename, password, address, view_key, spend_key, restore_height, autosave_current); + auto params = std::make_shared(filename, password, address, view_key, spend_key, restore_height, autosave_current); try { m_rpc->send_json_request("generate_from_keys", params); } - catch (const PyMoneroRpcError& ex) { handle_create_wallet_error(ex, filename); } + catch (const monero_rpc_error& ex) { handle_create_wallet_error(ex, filename); } clear(); m_path = config->m_path.get(); return this; } -std::string PyMoneroWalletRpc::query_key(const std::string& key_type) const { - auto params = std::make_shared(key_type); +std::string monero_wallet_rpc::query_key(const std::string& key_type) const { + auto params = std::make_shared(key_type); auto node = m_rpc->send_json_request("query_key", params); - auto kv = std::make_shared(); - PyMoneroKeyValue::from_property_tree(node, kv); + auto kv = std::make_shared(); + monero_key_value::from_property_tree(node, kv); if (kv->m_key == boost::none) throw std::runtime_error(std::string("Cloud not query key: ") + key_type); return *kv->m_key; } -std::vector> PyMoneroWalletRpc::sweep_account(const monero_tx_config &conf) { +std::vector> monero_wallet_rpc::sweep_account(const monero_tx_config &conf) { auto config = conf.copy(); // validate config if (config.m_account_index == boost::none) throw std::runtime_error("Must specify an account index to sweep from"); @@ -1594,7 +1737,7 @@ std::vector> PyMoneroWalletRpc::sweep_account( } if (config.m_subaddress_indices.size() == 0) throw std::runtime_error("No subaddresses to sweep from"); bool relay = config.m_relay == true; - auto params = std::make_shared(config); + auto params = std::make_shared(config); params->m_get_tx_key = boost::none; params->m_get_tx_keys = true; auto node = m_rpc->send_json_request("sweep_all", params); @@ -1638,13 +1781,13 @@ std::vector> PyMoneroWalletRpc::sweep_account( return set->m_txs; } -void PyMoneroWalletRpc::clear_address_cache() { +void monero_wallet_rpc::clear_address_cache() { m_address_cache.clear(); } -void PyMoneroWalletRpc::refresh_listening() { +void monero_wallet_rpc::refresh_listening() { if (m_poller == nullptr && !m_listeners.empty()) { - m_poller = std::make_unique(this); + m_poller = std::make_unique(this); if (m_sync_period_in_ms != boost::none) m_poller->set_period_in_ms(m_sync_period_in_ms.get()); } if (m_poller != nullptr) { @@ -1652,25 +1795,25 @@ void PyMoneroWalletRpc::refresh_listening() { } } -void PyMoneroWalletRpc::poll() { +void monero_wallet_rpc::poll() { if (m_poller != nullptr && m_poller->is_polling()) { m_poller->poll(); } } -void PyMoneroWalletRpc::clear() { +void monero_wallet_rpc::clear() { m_listeners.clear(); refresh_listening(); clear_address_cache(); m_path = ""; } -std::vector> PyMoneroWalletRpc::get_txs() const { +std::vector> monero_wallet_rpc::get_txs() const { return get_txs(monero_tx_query()); } -std::vector> PyMoneroWalletRpc::get_txs(const monero_tx_query& query) const { - MTRACE("get_txs(query)"); +std::vector> monero_wallet_rpc::get_txs(const monero_tx_query& query) const { + MTRACE("monero_wallet_rpc::get_txs(query)"); // copy query std::shared_ptr query_sp = std::make_shared(query); // convert to shared pointer @@ -1752,7 +1895,7 @@ std::vector> PyMoneroWalletRpc::get_txs(const // TODO monero-project: offer wallet.get_txs(...) for (const std::shared_ptr& tx : txs) { if ((*tx->m_is_confirmed && tx->m_block == boost::none) || (!*tx->m_is_confirmed && tx->m_block != boost::none)) { - std::cout << "WARNING: Inconsistency detected building txs from multiple wallet2 calls, re-fetching" << std::endl; + MWARNING("Inconsistency detected building txs from multiple wallet2 calls, re-fetching"); monero_utils::free(txs); txs.clear(); txs = get_txs(*_query); @@ -1775,7 +1918,7 @@ std::vector> PyMoneroWalletRpc::get_txs(const return txs; } -std::vector> PyMoneroWalletRpc::get_transfers(const monero_transfer_query& query) const { +std::vector> monero_wallet_rpc::get_transfers(const monero_transfer_query& query) const { // get transfers directly if query does not require tx context (e.g. other transfers, outputs) if (!PyMoneroTransferQuery::is_contextual(query)) return get_transfers_aux(query); @@ -1789,7 +1932,7 @@ std::vector> PyMoneroWalletRpc::get_transfers(c return transfers; } -std::vector> PyMoneroWalletRpc::get_outputs(const monero_output_query& query) const { +std::vector> monero_wallet_rpc::get_outputs(const monero_output_query& query) const { // get outputs directly if query does not require tx context (e.g. other outputs, transfers) if (!PyMoneroOutputQuery::is_contextual(query)) return get_outputs_aux(query); @@ -1803,7 +1946,7 @@ std::vector> PyMoneroWalletRpc::get_output return outputs; } -std::map> PyMoneroWalletRpc::get_account_indices(bool get_subaddr_indices) const { +std::map> monero_wallet_rpc::get_account_indices(bool get_subaddr_indices) const { std::map> indices; for (const auto& account : monero::monero_wallet::get_accounts()) { uint32_t account_idx = account.m_index.get(); @@ -1815,9 +1958,9 @@ std::map> PyMoneroWalletRpc::get_account_indices return indices; } -std::vector PyMoneroWalletRpc::get_subaddress_indices(uint32_t account_idx) const { +std::vector monero_wallet_rpc::get_subaddress_indices(uint32_t account_idx) const { // fetch subaddresses - auto params = std::make_shared(account_idx); + auto params = std::make_shared(account_idx); auto node = m_rpc->send_json_request("get_address", params); std::vector subadress_indices; std::vector> subaddresses; @@ -1828,13 +1971,8 @@ std::vector PyMoneroWalletRpc::get_subaddress_indices(uint32_t account return subadress_indices; } -std::vector> PyMoneroWalletRpc::get_transfers_aux(const monero_transfer_query& query) const { - MTRACE("PyMoneroWalletRpc::get_transfers(query)"); -// // log query -// if (query.m_tx_query != boost::none) { -// if ((*query.m_tx_query)->m_block == boost::none) std::cout << "Transfer query's tx query rooted at [tx]:" << (*query.m_tx_query)->serialize() << std::endl; -// else std::cout << "Transfer query's tx query rooted at [block]: " << (*(*query.m_tx_query)->m_block)->serialize() << std::endl; -// } else std::cout << "Transfer query: " << query.serialize() << std::endl; +std::vector> monero_wallet_rpc::get_transfers_aux(const monero_transfer_query& query) const { + MTRACE("monero_wallet_rpc::get_transfers_aux(query)"); // copy and normalize query std::shared_ptr _query; @@ -1877,7 +2015,7 @@ std::vector> PyMoneroWalletRpc::get_transfers_a std::unordered_map> tx_map; std::unordered_map> block_map; - auto params = std::make_shared(); + auto params = std::make_shared(); params->m_in = is_in; params->m_out = is_out; params->m_pool = is_pool; @@ -1912,7 +2050,7 @@ std::vector> PyMoneroWalletRpc::get_transfers_a // sort txs by block height std::vector> txs; - for (std::unordered_map>::const_iterator tx_iter = tx_map.begin(); tx_iter != tx_map.end(); tx_iter++) { + for (std::unordered_map>::const_iterator tx_iter = tx_map.begin(); tx_iter != tx_map.end(); ++tx_iter) { txs.push_back(tx_iter->second); } sort(txs.begin(), txs.end(), tx_height_less_than); @@ -1936,21 +2074,15 @@ std::vector> PyMoneroWalletRpc::get_transfers_a tx->m_block.get()->m_txs.erase(std::remove(tx->m_block.get()->m_txs.begin(), tx->m_block.get()->m_txs.end(), tx), tx->m_block.get()->m_txs.end()); // TODO, no way to use const_iterator? } } - MTRACE("PyMoneroWalletRpc::get_transfers() returning " << transfers.size() << " transfers"); + MTRACE("monero_wallet_rpc::get_transfers() returning " << transfers.size() << " transfers"); // free query and return transfers monero_utils::free(tx_query); return transfers; } -std::vector> PyMoneroWalletRpc::get_outputs_aux(const monero_output_query& query) const { - MTRACE("PyMoneroWalletRpc::get_outputs_aux(query)"); - -// // log query -// if (query.m_tx_query != boost::none) { -// if ((*query.m_tx_query)->m_block == boost::none) std::cout << "Output query's tx query rooted at [tx]:" << (*query.m_tx_query)->serialize() << std::endl; -// else std::cout << "Output query's tx query rooted at [block]: " << (*(*query.m_tx_query)->m_block)->serialize() << std::endl; -// } else std::cout << "Output query: " << query.serialize() << std::endl; +std::vector> monero_wallet_rpc::get_outputs_aux(const monero_output_query& query) const { + MTRACE("monero_wallet_rpc::get_outputs_aux(query)"); // copy and normalize query std::shared_ptr _query; @@ -2002,7 +2134,7 @@ std::vector> PyMoneroWalletRpc::get_output else transfer_type = "available"; } - auto params = std::make_shared(transfer_type); + auto params = std::make_shared(transfer_type); for(const auto& kv : indices) { uint32_t account_idx = kv.first; @@ -2017,7 +2149,7 @@ std::vector> PyMoneroWalletRpc::get_output // sort txs by block height std::vector> txs ; - for (std::unordered_map>::const_iterator tx_iter = tx_map.begin(); tx_iter != tx_map.end(); tx_iter++) { + for (std::unordered_map>::const_iterator tx_iter = tx_map.begin(); tx_iter != tx_map.end(); ++tx_iter) { txs.push_back(tx_iter->second); } sort(txs.begin(), txs.end(), tx_height_less_than); diff --git a/src/cpp/wallet/py_monero_wallet_rpc.h b/src/cpp/wallet/py_monero_wallet_rpc.h index 63489fa..f2cfe0b 100644 --- a/src/cpp/wallet/py_monero_wallet_rpc.h +++ b/src/cpp/wallet/py_monero_wallet_rpc.h @@ -1,48 +1,128 @@ +/** + * Copyright (c) everoddandeven + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Parts of this file are originally copyright (c) 2025-2026 woodser + * + * Parts of this file are originally copyright (c) 2014-2019, The Monero Project + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * All rights reserved. + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be + * used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + */ #pragma once #include "py_monero_wallet.h" +// forward declaration of internal wallet poller +class monero_wallet_poller; -class PyMoneroWalletPoller: public PyThreadPoller { +/** + * Implements a Monero wallet using monero-wallet-rpc. + */ +class monero_wallet_rpc : public PyMoneroWallet { public: - PyMoneroWalletPoller(PyMoneroWallet *wallet); - void poll() override; + /** + * Destruct the wallet. + */ + ~monero_wallet_rpc(); + monero_wallet_rpc(const std::shared_ptr& rpc_connection); + monero_wallet_rpc(const std::string& uri = "", const std::string& username = "", const std::string& password = "", const std::string& proxy_uri = "", const std::string& zmq_uri = "", uint64_t timeout = 20000); -private: - PyMoneroWallet *m_wallet; - std::atomic m_num_polling; - - std::vector m_prev_unconfirmed_notifications; - std::vector m_prev_confirmed_notifications; - boost::optional> m_prev_balances; - boost::optional m_prev_height; - std::vector> m_prev_locked_txs; - - std::shared_ptr get_tx(const std::vector>& txs, const std::string& tx_hash); - void on_new_block(uint64_t height); - void notify_outputs(const std::shared_ptr &tx); - bool check_for_changed_balances(); -}; + /** + * Open an existing wallet from rpc server. + * + * @param config is the wallet configuration + * @return a pointer to the wallet instance + */ + monero_wallet_rpc* open_wallet(const std::shared_ptr &config); -class PyMoneroWalletRpc : public PyMoneroWallet { -public: + /** + * Open an existing wallet from rpc server. + * + * @param name is the wallet's name to open + * @param password is the password of the wallet file to open + * @return a pointer to the wallet instance + */ + monero_wallet_rpc* open_wallet(const std::string& name, const std::string& password); - ~PyMoneroWalletRpc(); - PyMoneroWalletRpc(const std::shared_ptr& rpc_connection); - PyMoneroWalletRpc(const std::string& uri = "", const std::string& username = "", const std::string& password = "", const std::string& proxy_uri = "", const std::string& zmq_uri = "", uint64_t timeout = 20000); + /** + * Create a new wallet with the given configuration. + * + * @param config is the wallet configuration + * @return a pointer to the wallet instance + */ + monero_wallet_rpc* create_wallet(const std::shared_ptr &config); - PyMoneroWalletRpc* open_wallet(const std::shared_ptr &config); - PyMoneroWalletRpc* open_wallet(const std::string& name, const std::string& password); - PyMoneroWalletRpc* create_wallet(const std::shared_ptr &config); + /** + * Get the wallet's RPC connection. + * + * @return the wallet's rpc connection + */ std::shared_ptr get_rpc_connection() const { return m_rpc; } + + /** + * Get a list of available languages for the wallet's seed. + * + * @return the available languages for the wallet's seed + */ std::vector get_seed_languages() const; + + /** + * Save and close the current wallet and stop the RPC server. + */ void stop(); + + /** + * Supported wallet methods. + */ void add_listener(monero_wallet_listener& listener) override; void remove_listener(monero_wallet_listener& listener) override; bool is_view_only() const override; boost::optional get_daemon_connection() const override; - void set_daemon_connection(const boost::optional& connection, bool is_trusted, const boost::optional& ssl_options); + void set_daemon_connection(const boost::optional& connection, bool is_trusted, const boost::optional& ssl_options); void set_daemon_connection(const boost::optional& connection) override; void set_daemon_connection(const std::string& uri, const std::string& username = "", const std::string& password = "", const std::string& proxy_uri = "") override; bool is_connected_to_daemon() const override; @@ -128,7 +208,7 @@ class PyMoneroWalletRpc : public PyMoneroWallet { void delete_address_book_entry(uint64_t index) override; void tag_accounts(const std::string& tag, const std::vector& account_indices) override; void untag_accounts(const std::vector& account_indices) override; - std::vector> get_account_tags() override; + std::vector> get_account_tags() override; void set_account_tag_label(const std::string& tag, const std::string& label) override; std::string get_payment_uri(const monero_tx_config& config) const override; std::shared_ptr parse_payment_uri(const std::string& uri) const override; @@ -149,24 +229,27 @@ class PyMoneroWalletRpc : public PyMoneroWallet { void save() override; bool is_closed() const override; void close(bool save = false) override; - std::shared_ptr get_balances(boost::optional account_idx, boost::optional subaddress_idx) const override; + std::shared_ptr get_balances(boost::optional account_idx, boost::optional subaddress_idx) const; -protected: +// --------------------------------- PRIVATE -------------------------------- + +private: + friend class monero_wallet_poller; inline static const uint64_t DEFAULT_SYNC_PERIOD_IN_MS = 20000; boost::optional m_sync_period_in_ms; std::string m_path = ""; std::shared_ptr m_rpc; std::shared_ptr m_daemon_connection; - std::unique_ptr m_poller; + std::unique_ptr m_poller; mutable boost::recursive_mutex m_sync_mutex; mutable std::unordered_map> m_address_cache; - PyMoneroWalletRpc* create_wallet_random(const std::shared_ptr &conf); - PyMoneroWalletRpc* create_wallet_from_seed(const std::shared_ptr &conf); - PyMoneroWalletRpc* create_wallet_from_keys(const std::shared_ptr &config); + monero_wallet_rpc* create_wallet_random(const std::shared_ptr &config); + monero_wallet_rpc* create_wallet_from_seed(const std::shared_ptr &config); + monero_wallet_rpc* create_wallet_from_keys(const std::shared_ptr &config); - monero_sync_result refresh(const std::shared_ptr& params); + monero_sync_result refresh(const std::shared_ptr& params); std::map> get_account_indices(bool get_subaddress_indices) const; std::vector get_subaddress_indices(uint32_t account_idx) const; diff --git a/src/python/__init__.pyi b/src/python/__init__.pyi index f23a44c..608eb36 100644 --- a/src/python/__init__.pyi +++ b/src/python/__init__.pyi @@ -55,6 +55,7 @@ Parts of this file are originally copyright (c) 2012-2013 The Cryptonote develop """ from .serializable_struct import SerializableStruct +from .monero_rpc_payment_info import MoneroRpcPaymentInfo from .monero_account import MoneroAccount from .monero_account_tag import MoneroAccountTag from .monero_address_book_entry import MoneroAddressBookEntry @@ -67,7 +68,6 @@ from .monero_block_template import MoneroBlockTemplate from .monero_check import MoneroCheck from .monero_check_reserve import MoneroCheckReserve from .monero_check_tx import MoneroCheckTx -from .monero_connection_priority_comparator import MoneroConnectionPriorityComparator from .monero_connection_span import MoneroConnectionSpan from .monero_connection_type import MoneroConnectionType from .monero_daemon import MoneroDaemon @@ -112,7 +112,7 @@ from .monero_prune_result import MoneroPruneResult from .monero_request import MoneroRequest from .monero_rpc_connection import MoneroRpcConnection from .monero_rpc_error import MoneroRpcError -from .monero_ssl_options import MoneroSslOptions +from .ssl_options import SslOptions from .monero_subaddress import MoneroSubaddress from .monero_submit_tx_result import MoneroSubmitTxResult from .monero_sync_result import MoneroSyncResult @@ -136,6 +136,7 @@ from .monero_wallet_rpc import MoneroWalletRpc __all__ = [ + 'MoneroRpcPaymentInfo', 'MoneroAccount', 'MoneroAccountTag', 'MoneroAddressBookEntry', @@ -148,7 +149,6 @@ __all__ = [ 'MoneroCheck', 'MoneroCheckReserve', 'MoneroCheckTx', - 'MoneroConnectionPriorityComparator', 'MoneroConnectionSpan', 'MoneroConnectionType', 'MoneroDaemon', @@ -212,6 +212,6 @@ __all__ = [ 'MoneroWalletKeys', 'MoneroWalletListener', 'MoneroWalletRpc', - 'MoneroSslOptions', + 'SslOptions', 'SerializableStruct' ] diff --git a/src/python/monero_connection_priority_comparator.pyi b/src/python/monero_connection_priority_comparator.pyi deleted file mode 100644 index fc0dac7..0000000 --- a/src/python/monero_connection_priority_comparator.pyi +++ /dev/null @@ -1,14 +0,0 @@ -class MoneroConnectionPriorityComparator: - """Connection priority compare utils.""" - - @staticmethod - def compare(p1: int, p2: int) -> bool: - """ - Compare connection priorities. - - :param int p1: first priority to check. - :param int p2: second priority to check. - - :returns bool: `True` if `p1` comes before `p2`, `False` otherwise. - """ - ... diff --git a/src/python/monero_daemon.pyi b/src/python/monero_daemon.pyi index 55cb71d..9b8385f 100644 --- a/src/python/monero_daemon.pyi +++ b/src/python/monero_daemon.pyi @@ -165,23 +165,12 @@ class MoneroDaemon: """ ... - @typing.overload - def get_block_template(self, wallet_address: str) -> MoneroBlockTemplate: - """ - Get a block template for mining a new block. - - :param str wallet_address: is the address of the wallet to receive miner transactions if block is successfully mined. - :returns MoneroBlockTemplate: a block template for mining a new block. - """ - ... - - @typing.overload - def get_block_template(self, wallet_address: str, reserve_size: int) -> MoneroBlockTemplate: + def get_block_template(self, wallet_address: str, reserve_size: int | None = None) -> MoneroBlockTemplate: """ Get a block template for mining a new block. :param str wallet_address: is the address of the wallet to receive miner transactions if block is successfully mined. - :param int reserve_size: is the reserve size (optional). + :param int | None reserve_size: is the reserve size (optional). :returns MoneroBlockTemplate: a block template for mining a new block. """ ... diff --git a/src/python/monero_daemon_info.pyi b/src/python/monero_daemon_info.pyi index af8d7d2..af7a20c 100644 --- a/src/python/monero_daemon_info.pyi +++ b/src/python/monero_daemon_info.pyi @@ -1,7 +1,8 @@ from .monero_network_type import MoneroNetworkType +from .monero_rpc_payment_info import MoneroRpcPaymentInfo -class MoneroDaemonInfo: +class MoneroDaemonInfo(MoneroRpcPaymentInfo): """Models information got from a Monero daemon.""" adjusted_timestamp: int | None @@ -16,8 +17,6 @@ class MoneroDaemonInfo: """Median adjusted block size of latest `100000` blocks.""" bootstrap_daemon_address: str | None """Bootstrap-node to give immediate usability to wallets while syncing by proxying RPC to it.""" - credits: int | None - """If payment for RPC is enabled, the number of credits available to the requesting client.""" cumulative_difficulty: int | None """Cumulative difficulty.""" database_size: int | None @@ -62,8 +61,6 @@ class MoneroDaemonInfo: """Current target for next proof of work.""" target_height: int | None """The height of the next block in the chain.""" - top_block_hash: str | None - """Hash of the highest block in the chain.""" update_available: bool | None """States if a newer Monero software version is available.""" version: str | None diff --git a/src/python/monero_daemon_sync_info.pyi b/src/python/monero_daemon_sync_info.pyi index bd64cd8..1f512ca 100644 --- a/src/python/monero_daemon_sync_info.pyi +++ b/src/python/monero_daemon_sync_info.pyi @@ -1,12 +1,11 @@ from .monero_peer import MoneroPeer +from .monero_rpc_payment_info import MoneroRpcPaymentInfo from .monero_connection_span import MoneroConnectionSpan -class MoneroDaemonSyncInfo: +class MoneroDaemonSyncInfo(MoneroRpcPaymentInfo): """Models daemon synchronization information.""" - credits: int | None - """If payment for RPC is enabled, the number of credits available to the requesting client.""" height: int | None """Daemon blockchain height.""" next_needed_pruning_seed: int | None @@ -22,8 +21,6 @@ class MoneroDaemonSyncInfo: """Currently connected peers to download blocks from.""" target_height: int | None """Target height the node is syncing from (will be 0 if node is fully synced).""" - top_block_hash: str | None - """Hash of the highest block in the chain.""" def __init__(self) -> None: """Initialize a Monero daemon sync info.""" diff --git a/src/python/monero_hard_fork_info.pyi b/src/python/monero_hard_fork_info.pyi index 1373bc6..86301f1 100644 --- a/src/python/monero_hard_fork_info.pyi +++ b/src/python/monero_hard_fork_info.pyi @@ -1,8 +1,9 @@ -class MoneroHardForkInfo: +from .monero_rpc_payment_info import MoneroRpcPaymentInfo + + +class MoneroHardForkInfo(MoneroRpcPaymentInfo): """Models a Monero look up information regarding hard fork voting and readiness.""" - credits: int | None - """If payment for RPC is enabled, the number of credits available to the requesting client.""" earliest_height: int | None """Block height at which hard fork would be enabled if voted in.""" is_enabled: bool | None @@ -16,8 +17,6 @@ class MoneroHardForkInfo: """ threshold: int | None """Minimum percent of votes to trigger hard fork. Default is 80.""" - top_block_hash: str | None - """If payment for RPC is enabled, the hash of the highest block in the chain. Otherwise, empty.""" version: int | None """The major block version for the fork.""" voting: int | None diff --git a/src/python/monero_rpc_connection.pyi b/src/python/monero_rpc_connection.pyi index 4b39ae0..fde9ea0 100644 --- a/src/python/monero_rpc_connection.pyi +++ b/src/python/monero_rpc_connection.pyi @@ -44,6 +44,18 @@ class MoneroRpcConnection(SerializableStruct): """ ... + @staticmethod + def compare(p1: int, p2: int) -> bool: + """ + Compare connection priorities. + + :param int p1: first priority to check. + :param int p2: second priority to check. + + :returns bool: `True` if `p1` comes before `p2`, `False` otherwise. + """ + ... + @typing.overload def __init__(self, uri: str = '', username: str = '', password: str = '', proxy_uri: str = '', zmq_uri: str = '', priority: int = 0, timeout: int = 20000) -> None: """ @@ -154,13 +166,13 @@ class MoneroRpcConnection(SerializableStruct): """ ... - def send_binary_request(self, method: str, parameters: object | None = None) -> str | None: + def send_binary_request(self, method: str, parameters: object | None = None) -> bytes | None: """ Send a binary RPC request. :param str method: is the path of the binary RPC method to invoke. :param Optional[object] parameters: are the request parameters (default `None`). - :returns str | None: the request's deserialized binary response. + :returns bytes | None: the request's deserialized binary response. """ ... diff --git a/src/python/monero_rpc_payment_info.pyi b/src/python/monero_rpc_payment_info.pyi new file mode 100644 index 0000000..4376682 --- /dev/null +++ b/src/python/monero_rpc_payment_info.pyi @@ -0,0 +1,11 @@ +from .serializable_struct import SerializableStruct + + +class MoneroRpcPaymentInfo(SerializableStruct): + """Models a Monero RPC payment information.""" + + credits: int | None + """If payment for RPC is enabled, the number of credits available to the requesting client. Otherwise, 0.""" + + top_block_hash: str | None + """If payment for RPC is enabled, the hash of the highest block in the chain. Otherwise, `None`.""" diff --git a/src/python/monero_submit_tx_result.pyi b/src/python/monero_submit_tx_result.pyi index ed453e8..0e93c6b 100644 --- a/src/python/monero_submit_tx_result.pyi +++ b/src/python/monero_submit_tx_result.pyi @@ -1,8 +1,8 @@ -class MoneroSubmitTxResult: +from .monero_rpc_payment_info import MoneroRpcPaymentInfo + +class MoneroSubmitTxResult(MoneroRpcPaymentInfo): """Models the result from submitting a tx to a daemon.""" - credits: int | None - """If payment for RPC is enabled, the number of credits available to the requesting client.""" has_invalid_input: bool | None """Indicates if the transaction has an invalid input.""" has_invalid_output: bool | None @@ -30,8 +30,6 @@ class MoneroSubmitTxResult: """Additional information. Currently empty or `Not relayed` if transaction was accepted but not relayed.""" sanity_check_failed: bool | None """Indicates if the transaction sanity check has failed.""" - top_block_hash: str | None - """Hash of the highest block in the chain.""" def __init__(self) -> None: """Initialize a new submit transaction result.""" diff --git a/src/python/monero_wallet_rpc.pyi b/src/python/monero_wallet_rpc.pyi index 8162a83..8b6a137 100644 --- a/src/python/monero_wallet_rpc.pyi +++ b/src/python/monero_wallet_rpc.pyi @@ -3,7 +3,7 @@ import typing from .monero_wallet import MoneroWallet from .monero_wallet_config import MoneroWalletConfig from .monero_rpc_connection import MoneroRpcConnection -from .monero_ssl_options import MoneroSslOptions +from .ssl_options import SslOptions class MoneroWalletRpc(MoneroWallet): @@ -84,12 +84,12 @@ class MoneroWalletRpc(MoneroWallet): ... @typing.overload - def set_daemon_connection(self, connection: MoneroRpcConnection | None, is_trusted: bool, ssl_options: MoneroSslOptions | None) -> None: # type: ignore + def set_daemon_connection(self, connection: MoneroRpcConnection | None, is_trusted: bool, ssl_options: SslOptions | None) -> None: # type: ignore """ Set daemon connection. :param MoneroRpcConnection | None connection: rpc connection to set. :param bool is_trusted: set trusted daemon connection. - :param MoneroSslOptions | None ssl_options: rpc connection SSL options. + :param SslOptions | None ssl_options: rpc connection SSL options. """ ... diff --git a/src/python/serializable_struct.pyi b/src/python/serializable_struct.pyi index df14d25..f59044d 100644 --- a/src/python/serializable_struct.pyi +++ b/src/python/serializable_struct.pyi @@ -1,9 +1,8 @@ -class SerializableStruct: - """Base struct which can be serialized.""" +from abc import ABC - def __init__(self) -> None: - """Initialize a new base struct.""" - ... + +class SerializableStruct(ABC): + """Base struct which can be serialized.""" def serialize(self) -> str: """ diff --git a/src/python/monero_ssl_options.pyi b/src/python/ssl_options.pyi similarity index 84% rename from src/python/monero_ssl_options.pyi rename to src/python/ssl_options.pyi index 071a53a..9539f43 100644 --- a/src/python/monero_ssl_options.pyi +++ b/src/python/ssl_options.pyi @@ -1,4 +1,7 @@ -class MoneroSslOptions: +from .serializable_struct import SerializableStruct + + +class SslOptions(SerializableStruct): """Models SSL options for a Monero rpc connection.""" ssl_private_key_path: str | None diff --git a/tests/test_monero_common.py b/tests/test_monero_common.py index c1ef174..a736bca 100644 --- a/tests/test_monero_common.py +++ b/tests/test_monero_common.py @@ -30,7 +30,7 @@ def test_monero_error(self) -> None: assert monero_rpc_err.code == -1 # test serializable struct - @pytest.mark.not_implemented + @pytest.mark.xfail(raises=TypeError, reason="Serializable struct is an abstract class") def test_serializable_struct(self) -> None: ser_struct: SerializableStruct = SerializableStruct() ser_struct.serialize() diff --git a/tests/test_monero_daemon_rpc.py b/tests/test_monero_daemon_rpc.py index 2a81f4e..d0c0c77 100644 --- a/tests/test_monero_daemon_rpc.py +++ b/tests/test_monero_daemon_rpc.py @@ -22,7 +22,8 @@ BlockUtils, GenUtils, DaemonUtils, WalletType, IntegrationTestUtils, - SubmitThenRelayTxTester, BaseTestClass + SubmitThenRelayTxTester, BaseTestClass, + TxWalletUtils, WalletTxsUtils ) logger: logging.Logger = logging.getLogger("TestMoneroDaemonRpc") @@ -314,7 +315,7 @@ def test_get_block_ids_binary(self) -> None: @pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") def test_get_tx_by_hash(self, daemon: MoneroDaemonRpc) -> None: # fetch tx hashses to test - tx_hashes: list[str] = TxUtils.get_confirmed_tx_hashes(daemon) + tx_hashes: list[str] = DaemonUtils.get_confirmed_tx_hashes(daemon) # context for creating txs ctx = TestContext() @@ -346,7 +347,7 @@ def test_get_tx_by_hash(self, daemon: MoneroDaemonRpc) -> None: @pytest.mark.flaky(reruns=5, reruns_delay=5) def test_get_txs_by_hashes(self, daemon: MoneroDaemonRpc, wallet: MoneroWalletRpc) -> None: # fetch tx hashses to test - tx_hashes: list[str] = TxUtils.get_confirmed_tx_hashes(daemon) + tx_hashes: list[str] = DaemonUtils.get_confirmed_tx_hashes(daemon) assert len(tx_hashes) > 0, "No tx hashes found" # context for creating txs @@ -371,7 +372,7 @@ def test_get_txs_by_hashes(self, daemon: MoneroDaemonRpc, wallet: MoneroWalletRp # fetch missing hash dest = MoneroDestination() dest.address = wallet.get_primary_address() - dest.amount = TxUtils.MAX_FEE + dest.amount = TxWalletUtils.MAX_FEE config = MoneroTxConfig() config.account_index = 0 config.destinations.append(dest) @@ -401,7 +402,7 @@ def test_get_txs_by_hashes_in_pool(self, daemon: MoneroDaemonRpc, wallet: Monero # submit txs to the pool but don't relay tx_hashes: list[str] = [] for i in range(1, 3): - tx: MoneroTx = TxUtils.get_unrelayed_tx(wallet, i) + tx: MoneroTx = WalletTxsUtils.get_unrelayed_tx(wallet, i) assert tx.hash is not None assert tx.full_hex is not None assert len(tx.full_hex) > 0 @@ -435,7 +436,7 @@ def test_get_txs_by_hashes_in_pool(self, daemon: MoneroDaemonRpc, wallet: Monero @pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") def test_get_tx_hex_by_hash(self, daemon: MoneroDaemonRpc) -> None: # fetch transaction hashes to test - tx_hashes: list[str] = TxUtils.get_confirmed_tx_hashes(daemon) + tx_hashes: list[str] = DaemonUtils.get_confirmed_tx_hashes(daemon) # fetch each tx hex by hash with and without pruning hexes: list[str | None] = [] @@ -468,7 +469,7 @@ def test_get_tx_hex_by_hash(self, daemon: MoneroDaemonRpc) -> None: @pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") def test_get_tx_hexes_by_hashes(self, daemon: MoneroDaemonRpc) -> None: # fetch transaction hashes to test - tx_hashes: list[str] = TxUtils.get_confirmed_tx_hashes(daemon) + tx_hashes: list[str] = DaemonUtils.get_confirmed_tx_hashes(daemon) # fetch tx hexes by hash with and without pruning hexes: list[str] = daemon.get_tx_hexes(tx_hashes) @@ -516,7 +517,7 @@ def test_get_txs_in_pool(self, daemon: MoneroDaemonRpc, wallet: MoneroWalletRpc) Utils.WALLET_TX_TRACKER.wait_for_txs_to_clear_pool(wallet) # submit tx to pool but don't relay - tx: MoneroTx = TxUtils.get_unrelayed_tx(wallet, 1) + tx: MoneroTx = WalletTxsUtils.get_unrelayed_tx(wallet, 1) assert tx.hash is not None assert tx.full_hex is not None result: MoneroSubmitTxResult = daemon.submit_tx_hex(tx.full_hex, True) @@ -558,7 +559,7 @@ def test_get_tx_pool_statistics(self, daemon: MoneroDaemonRpc, wallet: MoneroWal for i in range(1, 3): # submit tx hex logger.debug(f"test_get_tx_pool_statistics: account {i}") - tx: MoneroTx = TxUtils.get_unrelayed_tx(wallet, i) + tx: MoneroTx = WalletTxsUtils.get_unrelayed_tx(wallet, i) assert tx.full_hex is not None result: MoneroSubmitTxResult = daemon.submit_tx_hex(tx.full_hex, True) assert result.is_good, f"Expected True, got {result.is_good}" @@ -584,7 +585,7 @@ def test_flush_txs_from_pool(self, daemon: MoneroDaemonRpc, wallet: MoneroWallet # submit txs to the pool but don't relay for i in range(1, 3): - tx: MoneroTx = TxUtils.get_unrelayed_tx(wallet, i) + tx: MoneroTx = WalletTxsUtils.get_unrelayed_tx(wallet, i) assert tx.full_hex is not None result: MoneroSubmitTxResult = daemon.submit_tx_hex(tx.full_hex, True) DaemonUtils.test_submit_tx_result_good(result) @@ -618,7 +619,7 @@ def test_flush_tx_from_pool_by_hash(self, daemon: MoneroDaemonRpc, wallet: Moner # submit txs to the pool but don't relay txs: list[MoneroTx] = [] for i in range(1, 3): - tx: MoneroTx = TxUtils.get_unrelayed_tx(wallet, i) + tx: MoneroTx = WalletTxsUtils.get_unrelayed_tx(wallet, i) assert tx.full_hex is not None result: MoneroSubmitTxResult = daemon.submit_tx_hex(tx.full_hex, True) DaemonUtils.test_submit_tx_result_good(result) @@ -652,7 +653,7 @@ def test_flush_txs_from_pool_by_hashes(self, daemon: MoneroDaemonRpc, wallet: Mo # submit txs to the pool but don't relay tx_hashes: list[str] = [] for i in range(1, 3): - tx: MoneroTx = TxUtils.get_unrelayed_tx(wallet, i) + tx: MoneroTx = WalletTxsUtils.get_unrelayed_tx(wallet, i) assert tx.hash is not None assert tx.full_hex is not None result: MoneroSubmitTxResult = daemon.submit_tx_hex(tx.full_hex, True) @@ -676,7 +677,7 @@ def test_get_spent_status_of_key_images(self, daemon: MoneroDaemonRpc, wallet: M txs: list[MoneroTx] = [] for i in range(1, 3): - tx: MoneroTx = TxUtils.get_unrelayed_tx(wallet, i) + tx: MoneroTx = WalletTxsUtils.get_unrelayed_tx(wallet, i) assert tx.full_hex is not None daemon.submit_tx_hex(tx.full_hex, True) txs.append(tx) @@ -1081,8 +1082,8 @@ def test_submit_and_relay_tx_hex(self, daemon: MoneroDaemonRpc, wallet: MoneroWa # create 2 txs, the second will double spend outputs of first # TODO: this test requires tx to be from/to different accounts else # the occlusion issue (#4500) causes the tx to not be recognized by the wallet at all - tx1: MoneroTx = TxUtils.get_unrelayed_tx(wallet, 2) - tx2: MoneroTx = TxUtils.get_unrelayed_tx(wallet, 2) + tx1: MoneroTx = WalletTxsUtils.get_unrelayed_tx(wallet, 2) + tx2: MoneroTx = WalletTxsUtils.get_unrelayed_tx(wallet, 2) # submit and relay tx1 assert tx1.hash is not None @@ -1129,7 +1130,7 @@ def test_submit_and_relay_tx_hex(self, daemon: MoneroDaemonRpc, wallet: MoneroWa @pytest.mark.skipif(Utils.LITE_MODE, reason="LITE_MODE enabled") def test_submit_then_relay_tx_hex(self, daemon: MoneroDaemonRpc, wallet: MoneroWalletRpc) -> None: Utils.WALLET_TX_TRACKER.wait_for_txs_to_clear_pool(wallet) - tx: MoneroTx = TxUtils.get_unrelayed_tx(wallet, 1) + tx: MoneroTx = WalletTxsUtils.get_unrelayed_tx(wallet, 1) tester: SubmitThenRelayTxTester = SubmitThenRelayTxTester(daemon, [tx]) tester.test() @@ -1139,9 +1140,9 @@ def test_submit_then_relay_tx_hex(self, daemon: MoneroDaemonRpc, wallet: MoneroW def test_submit_then_relay_tx_hexes(self, daemon: MoneroDaemonRpc, wallet: MoneroWalletRpc) -> None: Utils.WALLET_TX_TRACKER.wait_for_txs_to_clear_pool(wallet) txs: list[MoneroTx] = [] - txs.append(TxUtils.get_unrelayed_tx(wallet, 1)) + txs.append(WalletTxsUtils.get_unrelayed_tx(wallet, 1)) # TODO: accounts cannot be re-used across send tests else isRelayed is true; wallet needs to update? - txs.append(TxUtils.get_unrelayed_tx(wallet, 2)) + txs.append(WalletTxsUtils.get_unrelayed_tx(wallet, 2)) tester: SubmitThenRelayTxTester = SubmitThenRelayTxTester(daemon, txs) tester.test() diff --git a/tests/test_monero_rpc_connection.py b/tests/test_monero_rpc_connection.py index daa7316..263a5ed 100644 --- a/tests/test_monero_rpc_connection.py +++ b/tests/test_monero_rpc_connection.py @@ -2,10 +2,9 @@ import logging from monero import ( - MoneroRpcConnection, MoneroConnectionType, MoneroRpcError, - MoneroUtils, MoneroConnectionPriorityComparator + MoneroRpcConnection, MoneroConnectionType, MoneroRpcError ) -from utils import TestUtils as Utils, DaemonUtils, StringUtils, BaseTestClass +from utils import TestUtils as Utils, RpcConnectionUtils, StringUtils, BaseTestClass logger: logging.Logger = logging.getLogger("TestMoneroRpcConnection") @@ -58,7 +57,7 @@ def test_rpc_connection_serialization(self, node_connection: MoneroRpcConnection # Can copy a rpc connection @pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") - @pytest.mark.xfail(raises=AssertionError, reason="TODO monero-cpp PyMoneroWalletConfig has custom fields serialization") + @pytest.mark.xfail(reason="TODO move PyMoneroRpcConnection to monero-cpp") def test_connection_copy(self, node_connection: MoneroRpcConnection) -> None: # test copy copy: MoneroRpcConnection = MoneroRpcConnection(node_connection) @@ -67,18 +66,18 @@ def test_connection_copy(self, node_connection: MoneroRpcConnection) -> None: # Test monerod rpc connection @pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") def test_node_rpc_connection(self, node_connection: MoneroRpcConnection) -> None: - DaemonUtils.test_rpc_connection(node_connection, Utils.DAEMON_RPC_URI, True, MoneroConnectionType.IPV4) + RpcConnectionUtils.test_rpc_connection(node_connection, Utils.DAEMON_RPC_URI, True, MoneroConnectionType.IPV4) # Test wallet rpc connection @pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") def test_wallet_rpc_connection(self, wallet_connection: MoneroRpcConnection) -> None: - DaemonUtils.test_rpc_connection(wallet_connection, Utils.WALLET_RPC_URI, True, MoneroConnectionType.IPV4) + RpcConnectionUtils.test_rpc_connection(wallet_connection, Utils.WALLET_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: connection = MoneroRpcConnection(Utils.OFFLINE_SERVER_URI) - DaemonUtils.test_rpc_connection(connection, Utils.OFFLINE_SERVER_URI, False, MoneroConnectionType.INVALID) + RpcConnectionUtils.test_rpc_connection(connection, Utils.OFFLINE_SERVER_URI, False, MoneroConnectionType.INVALID) # Can set credentials @pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") @@ -195,11 +194,12 @@ def test_priority(self) -> None: for i in range(100): for j in range(100): expected: bool = (i == 0 and j != 0) or (i != 0 and j != 0 and i > j) - assert MoneroConnectionPriorityComparator.compare(i, j) is expected + assert MoneroRpcConnection.compare(i, j) is expected # Can send json request @pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") def test_send_json_request(self, node_connection: MoneroRpcConnection) -> None: + RpcConnectionUtils.setup_rpc_connection(node_connection) result: object = node_connection.send_json_request("get_version") assert result is not None logger.debug(f"JSON-RPC response {result}") @@ -208,18 +208,18 @@ def test_send_json_request(self, node_connection: MoneroRpcConnection) -> None: try: node_connection.send_json_request("invalid_method") except MoneroRpcError as e: - if str(e) != "Method not found": - raise + e_msg: str = str(e) + assert e_msg == "Method not found", e_msg assert e.code == -32601 # Can send binary request @pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") def test_send_binary_request(self, node_connection: MoneroRpcConnection) -> None: + RpcConnectionUtils.setup_rpc_connection(node_connection) parameters: dict[str, list[int]] = { "heights": list(range(100)) } - bin_result: str | None = node_connection.send_binary_request("get_blocks_by_height.bin", parameters) + bin_result: bytes | None = node_connection.send_binary_request("get_blocks_by_height.bin", parameters) assert bin_result is not None - result: str = MoneroUtils.binary_to_json(bin_result) - logger.debug(f"Binary response: {result}") + logger.debug(f"Binary response: {bin_result}") # test invalid binary method try: @@ -230,6 +230,7 @@ def test_send_binary_request(self, node_connection: MoneroRpcConnection) -> None # Can send path request @pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") def test_send_path_request(self, node_connection: MoneroRpcConnection) -> None: + RpcConnectionUtils.setup_rpc_connection(node_connection) result: object = node_connection.send_path_request("get_height") assert result is not None logger.debug(f"Path response {result}") diff --git a/tests/test_monero_wallet_common.py b/tests/test_monero_wallet_common.py index c8e63a5..0ba986d 100644 --- a/tests/test_monero_wallet_common.py +++ b/tests/test_monero_wallet_common.py @@ -23,13 +23,15 @@ ) from utils import ( TestUtils, WalletEqualityUtils, - StringUtils, AssertUtils, TxUtils, + StringUtils, AssertUtils, TxContext, GenUtils, WalletUtils, WalletType, IntegrationTestUtils, ViewOnlyAndOfflineWalletTester, WalletNotificationCollector, - MiningUtils, SendAndUpdateTxsTester, - SyncWithPoolSubmitTester, BaseTestClass + MiningUtils, BaseTestClass, + OutputUtils, TxWalletUtils, TransferUtils, + WalletTxsUtils, WalletTransfersUtils, + WalletErrorUtils, WalletSendUtils, WalletTestUtils ) logger: logging.Logger = logging.getLogger("TestMoneroWalletCommon") @@ -39,8 +41,8 @@ class BaseTestMoneroWallet(BaseTestClass): """Common wallet tests that every Monero wallet implementation should support.""" - CREATED_WALLET_KEYS_ERROR: str = "Wallet created from keys is not connected to authenticated daemon" _test_wallet: Optional[MoneroWallet] = None + """Private reference to test wallet instance.""" @classmethod def get_wallet_type(cls) -> WalletType: @@ -237,7 +239,7 @@ def test_validate_inputs_sending_funds(self, wallet: MoneroWallet) -> None: tx_config = MoneroTxConfig() tx_config.address = "my invalid address" tx_config.account_index = 0 - tx_config.amount = TxUtils.MAX_FEE + tx_config.amount = TxWalletUtils.MAX_FEE wallet.create_tx(tx_config) raise Exception("Should have thrown") except Exception as e: @@ -251,9 +253,9 @@ def test_sync_with_pool_same_accounts(self, daemon: MoneroDaemonRpc, wallet: Mon config: MoneroTxConfig = MoneroTxConfig() config.account_index = 0 config.address = wallet.get_primary_address() - config.amount = TxUtils.MAX_FEE * 5 + config.amount = TxWalletUtils.MAX_FEE * 5 config.relay = True - self._test_sync_with_pool_submit(daemon, wallet, config) + WalletSendUtils.test_sync_with_pool_submit(daemon, wallet, config) # Can sync with txs submitted and flushed from the pool # This test takes at least 500 seconds to catchup failed txs @@ -264,9 +266,9 @@ def test_sync_with_pool_submit_and_flush(self, daemon: MoneroDaemonRpc, wallet: config: MoneroTxConfig = MoneroTxConfig() config.account_index = 2 config.address = wallet.get_primary_address() - config.amount = TxUtils.MAX_FEE * 5 + config.amount = TxWalletUtils.MAX_FEE * 5 config.relay = False - self._test_sync_with_pool_submit(daemon, wallet, config) + WalletSendUtils.test_sync_with_pool_submit(daemon, wallet, config) # Can sync with txs submitted and relayed from the pool @pytest.mark.skipif(TestUtils.TEST_RELAYS is False, reason="TEST_RELAYS disabled") @@ -275,9 +277,9 @@ def test_sync_with_pool_submit_and_relay(self, daemon: MoneroDaemonRpc, wallet: config: MoneroTxConfig = MoneroTxConfig() config.account_index = 2 config.address = wallet.get_primary_address() - config.amount = TxUtils.MAX_FEE * 5 + config.amount = TxWalletUtils.MAX_FEE * 5 config.relay = True - self._test_sync_with_pool_submit(daemon, wallet, config) + WalletSendUtils.test_sync_with_pool_submit(daemon, wallet, config) # can sync with txs relayed to the pool @pytest.mark.skipif(TestUtils.TEST_RELAYS is False, reason="TEST_RELAYS disabled") @@ -294,7 +296,7 @@ def test_sync_with_pool_relay(self, daemon: MoneroDaemonRpc, wallet: MoneroWalle config: MoneroTxConfig = MoneroTxConfig() config.account_index = 2 config.address = wallet.get_primary_address() - config.amount = TxUtils.MAX_FEE * 5 + config.amount = TxWalletUtils.MAX_FEE * 5 # create tx to relay tx: MoneroTxWallet = wallet.create_tx(config) @@ -379,7 +381,7 @@ def test_sync_with_pool_relay(self, daemon: MoneroDaemonRpc, wallet: MoneroWalle def test_send_to_self(self, wallet: MoneroWallet) -> None: # wait for txs to confirm and for sufficient unlocked balance TestUtils.WALLET_TX_TRACKER.wait_for_txs_to_clear_pool(wallet) - amount: int = TxUtils.MAX_FEE * 3 + amount: int = TxWalletUtils.MAX_FEE * 3 TestUtils.WALLET_TX_TRACKER.wait_for_unlocked_balance(wallet, 0, None, amount) # collect sender balances before @@ -431,7 +433,7 @@ def test_send_to_external(self, wallet: MoneroWallet) -> None: try: # wait for txs to confirm and for sufficient unlocked balance TestUtils.WALLET_TX_TRACKER.wait_for_txs_to_clear_pool(wallet) - amount: int = TxUtils.MAX_FEE * 3 + amount: int = TxWalletUtils.MAX_FEE * 3 TestUtils.WALLET_TX_TRACKER.wait_for_unlocked_balance(wallet, 0, None, amount) # create recipient wallet @@ -474,12 +476,12 @@ def test_send_to_external(self, wallet: MoneroWallet) -> None: # Can send to multiple subaddresses in a single transaction @pytest.mark.skipif(TestUtils.TEST_RELAYS is False, reason="TEST_RELAYS disabled") def test_send_to_multiple(self, wallet: MoneroWallet) -> None: - WalletUtils.test_send_to_multiple(wallet, 5, 3, False) + WalletSendUtils.test_send_to_multiple(wallet, 5, 3, False) # Can send to multiple addresses in split transactions @pytest.mark.skipif(TestUtils.TEST_RELAYS is False, reason="TEST_RELAYS disabled") def test_send_to_multiple_split(self, wallet: MoneroWallet) -> None: - WalletUtils.test_send_to_multiple(wallet, 3, 15, True) + WalletSendUtils.test_send_to_multiple(wallet, 3, 15, True) # Can send dust to multiple addresses in split transactions @pytest.mark.skipif(TestUtils.TEST_RELAYS is False, reason="TEST_RELAYS disabled") @@ -487,32 +489,32 @@ def test_send_dust_to_multiple_split(self, daemon: MoneroDaemonRpc, wallet: Mone estimate: MoneroFeeEstimate = daemon.get_fee_estimate() assert estimate.fee is not None dust_amount: int = int(estimate.fee / 2) - WalletUtils.test_send_to_multiple(wallet, 5, 3, True, dust_amount) + WalletSendUtils.test_send_to_multiple(wallet, 5, 3, True, dust_amount) # Can subtract fees from destinations @pytest.mark.skipif(TestUtils.TEST_RELAYS is False, reason="TEST_RELAYS disabled") def test_subtract_fee_from(self, wallet: MoneroWallet) -> None: - WalletUtils.test_send_to_multiple(wallet, 5, 3, False, None, True) + WalletSendUtils.test_send_to_multiple(wallet, 5, 3, False, None, True) # Cannot subtract fees from destinations in split transactions @pytest.mark.skipif(TestUtils.TEST_RELAYS is False, reason="TEST_RELAYS disabled") def test_subtract_fee_from_split(self, wallet: MoneroWallet) -> None: - WalletUtils.test_send_to_multiple(wallet, 3, 15, True, None, True) + WalletSendUtils.test_send_to_multiple(wallet, 3, 15, True, None, True) # Can send from multiple subaddresses in a single transaction @pytest.mark.skipif(TestUtils.TEST_RELAYS is False, reason="TEST_RELAYS disabled") def test_send_from_subaddresses(self, wallet: MoneroWallet) -> None: - WalletUtils.test_send_from_multiple(wallet, None) + WalletSendUtils.test_send_from_multiple(wallet, None) # Can send from multiple subaddresses in split transactions @pytest.mark.skipif(TestUtils.TEST_RELAYS is False, reason="TEST_RELAYS disabled") def test_send_from_subaddresses_split(self, wallet: MoneroWallet) -> None: - WalletUtils.test_send_from_multiple(wallet, True) + WalletSendUtils.test_send_from_multiple(wallet, True) # Can send to an address in a single transaction @pytest.mark.skipif(TestUtils.TEST_RELAYS is False, reason="TEST_RELAYS disabled") def test_send(self, wallet: MoneroWallet) -> None: - WalletUtils.test_send_to_single(wallet, False) + WalletSendUtils.test_send_to_single(wallet, False) # Can send to an address in a single transaction with a payment id # NOTE this test will be invalid when payment hashes are fully removed @@ -522,7 +524,7 @@ def test_send_with_payment_id(self, wallet: MoneroWallet) -> None: assert integrated_address.payment_id is not None payment_id: str = integrated_address.payment_id try: - WalletUtils.test_send_to_single(wallet, False, None, f"{payment_id}{payment_id}{payment_id}") + WalletSendUtils.test_send_to_single(wallet, False, None, f"{payment_id}{payment_id}{payment_id}") raise Exception("Should have thrown") except Exception as e: msg = "Standalone payment IDs are obsolete. Use subaddresses or integrated addresses instead" @@ -531,17 +533,17 @@ def test_send_with_payment_id(self, wallet: MoneroWallet) -> None: # Can send to an address with split transactions @pytest.mark.skipif(TestUtils.TEST_RELAYS is False, reason="TEST_RELAYS disabled") def test_send_split(self, wallet: MoneroWallet) -> None: - WalletUtils.test_send_to_single(wallet, True) + WalletSendUtils.test_send_to_single(wallet, True) # Can create then relay a transaction to send to a single address @pytest.mark.skipif(TestUtils.TEST_RELAYS is False, reason="TEST_RELAYS disabled") def test_create_then_relay(self, wallet: MoneroWallet) -> None: - WalletUtils.test_send_to_single(wallet, False, False) + WalletSendUtils.test_send_to_single(wallet, False, False) # Can create then relay split transactions to send to a single address @pytest.mark.skipif(TestUtils.TEST_RELAYS is False, reason="TEST_RELAYS disabled") def test_create_then_relay_split(self, wallet: MoneroWallet) -> None: - WalletUtils.test_send_to_single(wallet, True, False) + WalletSendUtils.test_send_to_single(wallet, True, False) # Can sweep individual outputs identified by their key images @pytest.mark.skipif(TestUtils.TEST_RELAYS is False, reason="TEST_RELAYS disabled") @@ -563,21 +565,21 @@ def test_sweep_outputs(self, wallet: MoneroWallet) -> None: if len(outputs_to_sweep) >= num_outputs: break assert spendable_output.amount is not None - if spendable_output.amount > TxUtils.MAX_FEE: + if spendable_output.amount > TxWalletUtils.MAX_FEE: outputs_to_sweep.append(spendable_output) assert len(outputs_to_sweep) >= num_outputs, "Wallet does not have enough sweepable outputs; run send tests" # sweep each output by key image for output in outputs_to_sweep: - TxUtils.test_output_wallet(output) + OutputUtils.test_output_wallet(output) assert output.is_spent is False assert output.tx is not None assert isinstance(output.tx, MoneroTxWallet) assert output.tx.is_locked is False assert output.amount is not None - if output.amount <= TxUtils.MAX_FEE: + if output.amount <= TxWalletUtils.MAX_FEE: continue # sweep output to address @@ -600,7 +602,7 @@ def test_sweep_outputs(self, wallet: MoneroWallet) -> None: ctx.is_send_response = True ctx.is_sweep_response = True ctx.is_sweep_output_response = True - TxUtils.test_tx_wallet(tx, ctx) + TxWalletUtils.test_tx_wallet(tx, ctx) # get outputs after sweeping after_outputs: list[MoneroOutputWallet] = wallet.get_outputs() @@ -631,7 +633,7 @@ def test_sweep_dust_no_relay(self, wallet: MoneroWallet) -> None: ctx.config.relay = False ctx.is_sweep_response = True for tx in txs: - TxUtils.test_tx_wallet(tx, ctx) + TxWalletUtils.test_tx_wallet(tx, ctx) # relay txs metadatas: list[str] = [] @@ -650,7 +652,7 @@ def test_sweep_dust_no_relay(self, wallet: MoneroWallet) -> None: txs = wallet.get_txs(tx_query) ctx.config.relay = True for tx in txs: - TxUtils.test_tx_wallet(tx, ctx) + TxWalletUtils.test_tx_wallet(tx, ctx) # Can sweep dust @pytest.mark.skipif(TestUtils.TEST_RELAYS is False, reason="TEST_RELAYS disabled") @@ -667,7 +669,7 @@ def test_sweep_dust(self, wallet: MoneroWallet) -> None: ctx.is_send_response = True ctx.is_sweep_response = True for tx in txs: - TxUtils.test_tx_wallet(tx, ctx) + TxWalletUtils.test_tx_wallet(tx, ctx) # TODO: test sending to multiple accounts # Can update a locked tx sent from/to the same account as blocks are added to the chain @@ -676,11 +678,11 @@ def test_sweep_dust(self, wallet: MoneroWallet) -> None: def test_update_locked_same_account(self, daemon: MoneroDaemonRpc, wallet: MoneroWallet) -> None: config: MoneroTxConfig = MoneroTxConfig() config.address = wallet.get_primary_address() - config.amount = TxUtils.MAX_FEE + config.amount = TxWalletUtils.MAX_FEE config.account_index = 0 config.can_split = False config.relay = True - self._test_send_and_update_txs(daemon, wallet, config) + WalletSendUtils.test_send_and_update_txs(daemon, wallet, config) # Can update split locked txs sent from/to the same account as blocks are added to the chain @pytest.mark.skipif(TestUtils.TEST_RELAYS is False, reason="TEST_RELAYS disabled") @@ -689,11 +691,11 @@ def test_update_locked_same_account(self, daemon: MoneroDaemonRpc, wallet: Moner def test_update_locked_same_account_split(self, daemon: MoneroDaemonRpc, wallet: MoneroWallet) -> None: config: MoneroTxConfig = MoneroTxConfig() config.address = wallet.get_primary_address() - config.amount = TxUtils.MAX_FEE + config.amount = TxWalletUtils.MAX_FEE config.account_index = 0 config.can_split = True config.relay = True - self._test_send_and_update_txs(daemon, wallet, config) + WalletSendUtils.test_send_and_update_txs(daemon, wallet, config) # TODO on wallet full is flaky due to `Cannot reconcile integrals: 0 vs 1. tx wallet m_is_incoming` error # Can update a locked tx sent from/to different accounts as blocks are added to the chain @@ -704,11 +706,11 @@ def test_update_locked_same_account_split(self, daemon: MoneroDaemonRpc, wallet: def test_update_locked_different_accounts(self, daemon: MoneroDaemonRpc, wallet: MoneroWallet) -> None: config: MoneroTxConfig = MoneroTxConfig() config.address = wallet.get_subaddress(1, 0).address - config.amount = TxUtils.MAX_FEE + config.amount = TxWalletUtils.MAX_FEE config.account_index = 0 config.can_split = False config.relay = True - self._test_send_and_update_txs(daemon, wallet, config) + WalletSendUtils.test_send_and_update_txs(daemon, wallet, config) # Can update locked, split txs sent from/to different accounts as blocks are added to the chain @pytest.mark.skipif(TestUtils.TEST_RELAYS is False, reason="TEST_RELAYS disabled") @@ -717,11 +719,11 @@ def test_update_locked_different_accounts(self, daemon: MoneroDaemonRpc, wallet: def test_update_locked_different_accounts_split(self, daemon: MoneroDaemonRpc, wallet: MoneroWallet) -> None: config: MoneroTxConfig = MoneroTxConfig() config.address = wallet.get_subaddress(1, 0).address - config.amount = TxUtils.MAX_FEE + config.amount = TxWalletUtils.MAX_FEE config.account_index = 0 config.can_split = True config.relay = True - self._test_send_and_update_txs(daemon, wallet, config) + WalletSendUtils.test_send_and_update_txs(daemon, wallet, config) #endregion @@ -869,8 +871,8 @@ def test_create_wallet_from_keys(self, daemon: MoneroDaemonRpc, wallet: MoneroWa assert private_spend_key == w.get_private_spend_key() if not w.is_connected_to_daemon(): # TODO monero-project: keys wallets not connected - logger.warning(f"WARNING: {self.CREATED_WALLET_KEYS_ERROR}") - assert w.is_connected_to_daemon(), self.CREATED_WALLET_KEYS_ERROR + logger.warning(f"WARNING: {WalletErrorUtils.CREATED_WALLET_KEYS_ERROR}") + assert w.is_connected_to_daemon(), WalletErrorUtils.CREATED_WALLET_KEYS_ERROR if not isinstance(w, MoneroWalletRpc): # TODO monero-wallet-rpc: cannot get seed from wallet created from keys? MoneroUtils.validate_mnemonic(w.get_seed()) @@ -891,8 +893,8 @@ def test_create_wallet_from_keys(self, daemon: MoneroDaemonRpc, wallet: MoneroWa assert private_spend_key == w.get_private_spend_key() if not w.is_connected_to_daemon(): # TODO monero-project: keys wallets not connected - logger.warning(f"{self.CREATED_WALLET_KEYS_ERROR}") - assert w.is_connected_to_daemon(), self.CREATED_WALLET_KEYS_ERROR + logger.warning(f"{WalletErrorUtils.CREATED_WALLET_KEYS_ERROR}") + assert w.is_connected_to_daemon(), WalletErrorUtils.CREATED_WALLET_KEYS_ERROR if not isinstance(w, MoneroWalletRpc): # TODO monero-wallet-rpc: cannot get seed from wallet created from keys? MoneroUtils.validate_mnemonic(w.get_seed()) @@ -926,7 +928,7 @@ def test_subaddress_lookahead(self, wallet: MoneroWallet) -> None: tx_config.account_index = 0 dest = MoneroDestination() dest.address = receiver.get_subaddress(0, 85000).address - dest.amount = TxUtils.MAX_FEE + dest.amount = TxWalletUtils.MAX_FEE tx_config.destinations.append(dest) tx_config.relay = True @@ -1134,7 +1136,7 @@ def test_get_address_indices(self, wallet: MoneroWallet) -> None: assert subaddress_idx == subaddress.index # test valid but unfound address - non_wallet_address: str = WalletUtils.get_external_wallet_address() + non_wallet_address: str = WalletTestUtils.get_external_wallet_address() try: wallet.get_address_index(non_wallet_address) raise Exception("Should have thrown exception") @@ -1147,7 +1149,7 @@ def test_get_address_indices(self, wallet: MoneroWallet) -> None: wallet.get_address_index("this is definitely not an address") raise Exception("Should have thrown exception") except Exception as e: - TxUtils.test_invalid_address_error(e) + WalletErrorUtils.test_invalid_address_error(e) # Can decode an integrated address @pytest.mark.skipif(TestUtils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") @@ -1161,7 +1163,7 @@ def test_decode_integrated_address(self, wallet: MoneroWallet) -> None: wallet.decode_integrated_address("bad address") raise Exception("Should have failed decoding bad address") except Exception as e: - TxUtils.test_invalid_address_error(e) + WalletErrorUtils.test_invalid_address_error(e) # Can sync (without progress) # TODO test syncing from start height @@ -1377,9 +1379,7 @@ def test_get_subaddresses_by_indices(self, wallet: MoneroWallet) -> None: # remove a subaddress for query if possible if len(subaddresses) > 1: - # TODO implement remove (needs operator == overload) - #subaddresses.remove(subaddresses[0]) - pass + subaddresses = list(filter(lambda sub: sub != subaddresses[0], subaddresses)) # get subaddress indices subaddress_indices: list[int] = [] @@ -1464,7 +1464,7 @@ def test_set_subaddress_label(self, wallet: MoneroWallet) -> None: @pytest.mark.skipif(TestUtils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") def test_get_txs_wallet(self, wallet: MoneroWallet) -> None: #non_default_incoming: bool = False - txs = TxUtils.get_and_test_txs(wallet, None, None, True, TestUtils.REGTEST) + txs = WalletTxsUtils.get_and_test_txs(wallet, None, None, True, TestUtils.REGTEST) assert len(txs) > 0, "Wallet has no txs to test" # TODO make consistent with test funded wallet # assert TestUtils.FIRST_RECEIVE_HEIGHT == txs[0].get_height(), "First tx's restore height must match the restore height in TestUtils" @@ -1476,7 +1476,7 @@ def test_get_txs_wallet(self, wallet: MoneroWallet) -> None: # test each transaction block_per_height: dict[int, MoneroBlock] = {} for i, tx in enumerate(txs): - TxUtils.test_tx_wallet(tx, ctx) + TxWalletUtils.test_tx_wallet(tx, ctx) # test merging equivalent txs it = txs[i] # is the same as tx @@ -1494,7 +1494,7 @@ def test_get_txs_wallet(self, wallet: MoneroWallet) -> None: copy2.block.txs = [copy2] copy1.merge(copy2) - TxUtils.test_tx_wallet(copy1, ctx) + TxWalletUtils.test_tx_wallet(copy1, ctx) # find non-default incoming for transfer in it.incoming_transfers: @@ -1540,7 +1540,7 @@ def test_get_txs_by_hash(self, wallet: MoneroWallet) -> None: fetched_tx = wallet.get_tx(tx_hash) assert fetched_tx is not None assert tx_hash == fetched_tx.hash - TxUtils.test_tx_wallet(fetched_tx) + TxWalletUtils.test_tx_wallet(fetched_tx) # test fetching by hashes tx_id1 = txs[0].hash @@ -1562,7 +1562,7 @@ def test_get_txs_by_hash(self, wallet: MoneroWallet) -> None: for i, tx in enumerate(txs): fetched_tx = fetched_txs[i] assert tx.hash == fetched_tx.hash - TxUtils.test_tx_wallet(fetched_tx) + TxWalletUtils.test_tx_wallet(fetched_tx) # test fetching with missing tx hashes missing_hash: str = "d01ede9cde813b2a693069b640c4b99c5adbdb49fbbd8da2c16c8087d0c3e320" @@ -1572,15 +1572,15 @@ def test_get_txs_by_hash(self, wallet: MoneroWallet) -> None: for i, tx in enumerate(txs): fetched_tx = fetched_txs[i] assert tx.hash == fetched_tx.hash - TxUtils.test_tx_wallet(fetched_tx) + TxWalletUtils.test_tx_wallet(fetched_tx) # Can get transactions with additional configuration @pytest.mark.skipif(TestUtils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") def test_get_txs_with_query(self, wallet: MoneroWallet) -> None: # get random transactions for testing - random_txs = TxUtils.get_random_transactions(wallet, None, 3, 5) + random_txs = WalletTxsUtils.get_random_transactions(wallet, None, 3, 5) for random_tx in random_txs: - TxUtils.test_tx_wallet(random_tx, None) + TxWalletUtils.test_tx_wallet(random_tx, None) # get transactions by hash tx_hashes: list[str] = [] @@ -1589,17 +1589,17 @@ def test_get_txs_with_query(self, wallet: MoneroWallet) -> None: tx_hashes.append(random_tx.hash) query = MoneroTxQuery() query.hash = random_tx.hash - txs = TxUtils.get_and_test_txs(wallet, query, None, True, TestUtils.REGTEST) + txs = WalletTxsUtils.get_and_test_txs(wallet, query, None, True, TestUtils.REGTEST) assert len(txs) == 1 # txs change with chain so check mergeability merged = txs[0] merged.merge(random_tx.copy()) - TxUtils.test_tx_wallet(merged) + TxWalletUtils.test_tx_wallet(merged) # get transactions by hashes query = MoneroTxQuery() query.hashes = tx_hashes - txs = TxUtils.get_and_test_txs(wallet, query, None, None, TestUtils.REGTEST) + txs = WalletTxsUtils.get_and_test_txs(wallet, query, None, None, TestUtils.REGTEST) assert len(txs) == len(random_txs) for tx in txs: assert tx.hash in tx_hashes @@ -1609,17 +1609,17 @@ def test_get_txs_with_query(self, wallet: MoneroWallet) -> None: ctx.has_outgoing_transfer = True query = MoneroTxQuery() query.is_outgoing = True - txs = TxUtils.get_and_test_txs(wallet, query, ctx, True, TestUtils.REGTEST) + txs = WalletTxsUtils.get_and_test_txs(wallet, query, ctx, True, TestUtils.REGTEST) for tx in txs: assert tx.is_outgoing is True assert tx.outgoing_transfer is not None - TxUtils.test_transfer(tx.outgoing_transfer, None) + TransferUtils.test_transfer(tx.outgoing_transfer, None) # get transactions without an outgoing transfer ctx.has_outgoing_transfer = False query = MoneroTxQuery() query.is_outgoing = False - txs = TxUtils.get_and_test_txs(wallet, query, ctx, True, TestUtils.REGTEST) + txs = WalletTxsUtils.get_and_test_txs(wallet, query, ctx, True, TestUtils.REGTEST) for tx in txs: assert tx.outgoing_transfer is None @@ -1628,18 +1628,18 @@ def test_get_txs_with_query(self, wallet: MoneroWallet) -> None: ctx.has_incoming_transfers = True query = MoneroTxQuery() query.is_incoming = True - txs = TxUtils.get_and_test_txs(wallet, query, ctx, True, TestUtils.REGTEST) + txs = WalletTxsUtils.get_and_test_txs(wallet, query, ctx, True, TestUtils.REGTEST) for tx in txs: assert tx.is_incoming is True assert len(tx.incoming_transfers) > 0 for transfer in tx.incoming_transfers: - TxUtils.test_transfer(transfer, None) + TransferUtils.test_transfer(transfer, None) # get transactions without incoming transfers ctx.has_incoming_transfers = False query = MoneroTxQuery() query.is_incoming = False - txs = TxUtils.get_and_test_txs(wallet, query, ctx, True, TestUtils.REGTEST) + txs = WalletTxsUtils.get_and_test_txs(wallet, query, ctx, True, TestUtils.REGTEST) for tx in txs: assert tx.is_incoming is False assert len(tx.incoming_transfers) == 0 @@ -1671,7 +1671,7 @@ def test_get_txs_with_query(self, wallet: MoneroWallet) -> None: tx_query.transfer_query = MoneroTransferQuery() tx_query.transfer_query.account_index = 0 tx_query.transfer_query.outgoing = True - txs = TxUtils.get_and_test_txs(wallet, tx_query, ctx, True, TestUtils.REGTEST) + txs = WalletTxsUtils.get_and_test_txs(wallet, tx_query, ctx, True, TestUtils.REGTEST) for tx in txs: if tx.is_confirmed is not True: logger.warning(f"{tx.serialize()}") @@ -1687,7 +1687,7 @@ def test_get_txs_with_query(self, wallet: MoneroWallet) -> None: tx_query.transfer_query.account_index = 0 tx_query.transfer_query.has_destinations = True - txs = TxUtils.get_and_test_txs(wallet, tx_query, None, None, TestUtils.REGTEST) + txs = WalletTxsUtils.get_and_test_txs(wallet, tx_query, None, None, TestUtils.REGTEST) for tx in txs: assert tx.is_outgoing is True assert tx.outgoing_transfer is not None @@ -1698,7 +1698,7 @@ def test_get_txs_with_query(self, wallet: MoneroWallet) -> None: ctx.include_outputs = True tx_query = MoneroTxQuery() tx_query.include_outputs = True - txs = TxUtils.get_and_test_txs(wallet, tx_query, ctx, True, TestUtils.REGTEST) + txs = WalletTxsUtils.get_and_test_txs(wallet, tx_query, ctx, True, TestUtils.REGTEST) found: bool = False for tx in txs: if len(tx.outputs) > 0: @@ -1796,7 +1796,7 @@ def test_get_txs_by_height(self, wallet: MoneroWallet) -> None: logger.debug(f"Getting mode txs by range; mode height {mode_height}") mode_txs_by_range: list[MoneroTxWallet] = wallet.get_txs(query) # TODO test txs order is failing - TxUtils.assert_list_txs_equals(mode_txs, mode_txs_by_range) + TxWalletUtils.assert_list_txs_equals(mode_txs, mode_txs_by_range) # fetch all txs by range query = MoneroTxQuery() @@ -1804,7 +1804,7 @@ def test_get_txs_by_height(self, wallet: MoneroWallet) -> None: query.max_height = txs[-1].get_height() all_txs: list[MoneroTxWallet] = wallet.get_txs(query) # TODO test txs order is failing - TxUtils.assert_list_txs_equals(txs, all_txs) + TxWalletUtils.assert_list_txs_equals(txs, all_txs) # test some filtered by range try: @@ -1837,7 +1837,7 @@ def test_get_txs_by_height(self, wallet: MoneroWallet) -> None: query = MoneroTxQuery() query.min_height = min_height query.max_height = max_height - txs = TxUtils.get_and_test_txs(wallet, query, None, True, TestUtils.REGTEST) + txs = WalletTxsUtils.get_and_test_txs(wallet, query, None, True, TestUtils.REGTEST) assert len(txs) < unfiltered_count for tx in txs: assert tx.block is not None @@ -1854,7 +1854,7 @@ def test_get_txs_with_payment_ids(self, wallet: MoneroWallet) -> None: # get random transactions with payment ids for testing tx_query: MoneroTxQuery = MoneroTxQuery() tx_query.has_payment_id = True - random_txs: list[MoneroTxWallet] = TxUtils.get_random_transactions(wallet, tx_query, 2, 5) + random_txs: list[MoneroTxWallet] = WalletTxsUtils.get_random_transactions(wallet, tx_query, 2, 5) assert len(random_txs) > 0, "No txs with payment ids to test" for random_tx in random_txs: assert random_tx.payment_id is not None and len(random_tx.payment_id) > 0 @@ -1869,7 +1869,7 @@ def test_get_txs_with_payment_ids(self, wallet: MoneroWallet) -> None: for payment_id in payment_ids: tx_query = MoneroTxQuery() tx_query.payment_id = payment_id - txs: list[MoneroTxWallet] = TxUtils.get_and_test_txs(wallet, tx_query, None, None, TestUtils.REGTEST) + txs: list[MoneroTxWallet] = WalletTxsUtils.get_and_test_txs(wallet, tx_query, None, None, TestUtils.REGTEST) assert len(txs) > 0 first_payment_id: Optional[str] = txs[0].payment_id assert first_payment_id is not None @@ -1878,7 +1878,7 @@ def test_get_txs_with_payment_ids(self, wallet: MoneroWallet) -> None: # get transactions by payment hashes tx_query = MoneroTxQuery() tx_query.payment_ids = payment_ids - txs: list[MoneroTxWallet] = TxUtils.get_and_test_txs(wallet, tx_query, None, None, TestUtils.REGTEST) + txs: list[MoneroTxWallet] = WalletTxsUtils.get_and_test_txs(wallet, tx_query, None, None, TestUtils.REGTEST) for tx in txs: assert tx.payment_id is not None assert tx.payment_id in payment_ids @@ -1919,7 +1919,7 @@ def test_get_txs_fields_with_filtering(self, wallet: MoneroWallet) -> None: @pytest.mark.skipif(TestUtils.TEST_NON_RELAYS is False or TestUtils.LITE_MODE, reason="TEST_NON_RELAYS disabled or LITE_MODE enabled") def test_validate_inputs_get_txs(self, wallet: MoneroWallet) -> None: # fetch random txs for testing - random_txs: list[MoneroTxWallet] = TxUtils.get_random_transactions(wallet, None, 3, 5) + random_txs: list[MoneroTxWallet] = WalletTxsUtils.get_random_transactions(wallet, None, 3, 5) # valid, invalid, and unknown tx hashes for tests tx_hash = random_txs[0].hash @@ -1970,20 +1970,20 @@ def test_validate_inputs_get_txs(self, wallet: MoneroWallet) -> None: # test txs for tx in txs: - TxUtils.test_tx_wallet(tx) + TxWalletUtils.test_tx_wallet(tx) # Can get transfers in the wallet, accounts, and subaddresses @pytest.mark.skipif(TestUtils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") def test_get_transfers(self, wallet: MoneroWallet) -> None: # get all transfers - TxUtils.get_and_test_transfers(wallet, None, None, True) + WalletTransfersUtils.get_and_test_transfers(wallet, None, None, True) # get transfers by account index non_default_incoming: bool = False for account in wallet.get_accounts(True): transfer_query = MoneroTransferQuery() transfer_query.account_index = account.index - account_transfers = TxUtils.get_and_test_transfers(wallet, transfer_query, None, None) + account_transfers = WalletTransfersUtils.get_and_test_transfers(wallet, transfer_query, None, None) for transfer in account_transfers: assert transfer.account_index == account.index @@ -1993,7 +1993,7 @@ def test_get_transfers(self, wallet: MoneroWallet) -> None: subaddress_query = MoneroTransferQuery() subaddress_query.account_index = subaddress.account_index subaddress_query.subaddress_index = subaddress.index - transfers = TxUtils.get_and_test_transfers(wallet, subaddress_query, None, None) + transfers = WalletTransfersUtils.get_and_test_transfers(wallet, subaddress_query, None, None) for transfer in transfers: # test account and subaddress indices @@ -2046,7 +2046,7 @@ def test_get_transfers(self, wallet: MoneroWallet) -> None: for idx in subaddress_indices: transfer_query.subaddress_indices.append(idx) - transfers = TxUtils.get_and_test_transfers(wallet, transfer_query, None, None) + transfers = WalletTransfersUtils.get_and_test_transfers(wallet, transfer_query, None, None) # TODO monero-wallet-rpc: these may not be equal because outgoing transfers are always from subaddress 0 (#5171) # and/or incoming transfers from/to same account are occluded (#4500) assert len(subaddress_transfers) == len(transfers) @@ -2070,14 +2070,14 @@ def test_get_transfers_with_query(self, wallet: MoneroWallet) -> None: # get incoming transfers query: MoneroTransferQuery = MoneroTransferQuery() query.incoming = True - transfers: list[MoneroTransfer] = TxUtils.get_and_test_transfers(wallet, query, None, True) + transfers: list[MoneroTransfer] = WalletTransfersUtils.get_and_test_transfers(wallet, query, None, True) for transfer in transfers: assert transfer.is_incoming() # get outgoing transfers query = MoneroTransferQuery() query.outgoing = True - transfers = TxUtils.get_and_test_transfers(wallet, query, None, True) + transfers = WalletTransfersUtils.get_and_test_transfers(wallet, query, None, True) for transfer in transfers: assert transfer.is_outgoing() @@ -2086,7 +2086,7 @@ def test_get_transfers_with_query(self, wallet: MoneroWallet) -> None: query.account_index = 0 query.tx_query = MoneroTxQuery() query.tx_query.is_confirmed = True - transfers = TxUtils.get_and_test_transfers(wallet, query, None, True) + transfers = WalletTransfersUtils.get_and_test_transfers(wallet, query, None, True) for transfer in transfers: assert transfer.account_index == 0 assert transfer.tx is not None @@ -2098,7 +2098,7 @@ def test_get_transfers_with_query(self, wallet: MoneroWallet) -> None: query.subaddress_index = 2 query.tx_query = MoneroTxQuery() query.tx_query.is_confirmed = True - transfers = TxUtils.get_and_test_transfers(wallet, query, None, True) + transfers = WalletTransfersUtils.get_and_test_transfers(wallet, query, None, True) for transfer in transfers: assert transfer.account_index == 1 if transfer.is_incoming(): @@ -2115,13 +2115,13 @@ def test_get_transfers_with_query(self, wallet: MoneroWallet) -> None: query = MoneroTransferQuery() query.tx_query = MoneroTxQuery() query.tx_query.in_tx_pool = True - transfers = TxUtils.get_and_test_transfers(wallet, query, None, None) + transfers = WalletTransfersUtils.get_and_test_transfers(wallet, query, None, None) for transfer in transfers: assert transfer.tx is not None assert transfer.tx.in_tx_pool is True # get random transactions - txs: list[MoneroTxWallet] = TxUtils.get_random_transactions(wallet, None, 3, 5) + txs: list[MoneroTxWallet] = WalletTxsUtils.get_random_transactions(wallet, None, 3, 5) # get transfers with a tx hash tx_hashes: list[str] = [] @@ -2131,7 +2131,7 @@ def test_get_transfers_with_query(self, wallet: MoneroWallet) -> None: query = MoneroTransferQuery() query.tx_query = MoneroTxQuery() query.tx_query.hash = tx.hash - transfers = TxUtils.get_and_test_transfers(wallet, query, None, True) + transfers = WalletTransfersUtils.get_and_test_transfers(wallet, query, None, True) for transfer in transfers: assert transfer.tx is not None assert tx.hash == transfer.tx.hash @@ -2140,7 +2140,7 @@ def test_get_transfers_with_query(self, wallet: MoneroWallet) -> None: query = MoneroTransferQuery() query.tx_query = MoneroTxQuery() query.tx_query.hashes = tx_hashes - transfers = TxUtils.get_and_test_transfers(wallet, query, None, True) + transfers = WalletTransfersUtils.get_and_test_transfers(wallet, query, None, True) for transfer in transfers: assert transfer.tx is not None assert transfer.tx.hash is not None @@ -2155,7 +2155,7 @@ def test_get_transfers_with_query(self, wallet: MoneroWallet) -> None: transfer_query.has_destinations = True transfer_query.tx_query = MoneroTxQuery() transfer_query.tx_query.is_confirmed = True - transfers = TxUtils.get_and_test_transfers(wallet, transfer_query, None, None) + transfers = WalletTransfersUtils.get_and_test_transfers(wallet, transfer_query, None, None) for transfer in transfers: assert transfer.is_outgoing() is True assert isinstance(transfer, MoneroOutgoingTransfer) @@ -2203,7 +2203,7 @@ def test_validate_inputs_get_transfers(self, wallet: MoneroWallet) -> None: assert len(transfers) == 0 # test invalid hash in list - random_txs = TxUtils.get_random_transactions(wallet, None, 3, 5) + random_txs = WalletTxsUtils.get_random_transactions(wallet, None, 3, 5) transfer_query.tx_query = MoneroTxQuery() random_hash = random_txs[0].hash assert random_hash is not None @@ -2239,7 +2239,7 @@ def test_validate_inputs_get_transfers(self, wallet: MoneroWallet) -> None: @pytest.mark.skipif(TestUtils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") def test_get_outputs(self, wallet: MoneroWallet) -> None: # get all outputs - TxUtils.get_and_test_outputs(wallet, None, True) + OutputUtils.get_and_test_outputs(wallet, None, True) # get outputs for each account non_default_incoming: bool = False @@ -2255,7 +2255,7 @@ def test_get_outputs(self, wallet: MoneroWallet) -> None: # get outputs by account index output_query: MoneroOutputQuery = MoneroOutputQuery() output_query.account_index = account.index - account_outputs = TxUtils.get_and_test_outputs(wallet, output_query, is_used) + account_outputs = OutputUtils.get_and_test_outputs(wallet, output_query, is_used) for ouput in account_outputs: assert ouput.account_index == account.index @@ -2265,7 +2265,7 @@ def test_get_outputs(self, wallet: MoneroWallet) -> None: subaddr_query: MoneroOutputQuery = MoneroOutputQuery() subaddr_query.account_index = account.index subaddr_query.subaddress_index = subaddress.index - outputs = TxUtils.get_and_test_outputs(wallet, subaddr_query, subaddress.is_used) + outputs = OutputUtils.get_and_test_outputs(wallet, subaddr_query, subaddress.is_used) for output in outputs: assert subaddress.account_index == output.account_index assert subaddress.index == output.subaddress_index @@ -2286,7 +2286,7 @@ def test_get_outputs(self, wallet: MoneroWallet) -> None: for sub_idx in subaddress_indices: output_query.subaddress_indices.append(sub_idx) - outputs = TxUtils.get_and_test_outputs(wallet, output_query, is_used) + outputs = OutputUtils.get_and_test_outputs(wallet, output_query, is_used) assert len(outputs) == len(subaddress_outputs) for output in outputs: @@ -2304,7 +2304,7 @@ def test_get_outputs_with_query(self, wallet: MoneroWallet) -> None: output_query: MoneroOutputQuery = MoneroOutputQuery() output_query.account_index = 0 output_query.is_spent = False - outputs: list[MoneroOutputWallet] = TxUtils.get_and_test_outputs(wallet, output_query, None) + outputs: list[MoneroOutputWallet] = OutputUtils.get_and_test_outputs(wallet, output_query, None) for output in outputs: assert output.account_index == 0 @@ -2314,7 +2314,7 @@ def test_get_outputs_with_query(self, wallet: MoneroWallet) -> None: output_query = MoneroOutputQuery() output_query.account_index = 1 output_query.is_spent = True - outputs = TxUtils.get_and_test_outputs(wallet, output_query, True) + outputs = OutputUtils.get_and_test_outputs(wallet, output_query, True) for output in outputs: assert output.account_index == 1 @@ -2323,7 +2323,7 @@ def test_get_outputs_with_query(self, wallet: MoneroWallet) -> None: # get random transactions tx_query: MoneroTxQuery = MoneroTxQuery() tx_query.is_confirmed = True - txs: list[MoneroTxWallet] = TxUtils.get_random_transactions(wallet, tx_query, 3, 5) + txs: list[MoneroTxWallet] = WalletTxsUtils.get_random_transactions(wallet, tx_query, 3, 5) # get outputs with a tx hash tx_hashes: list[str] = [] @@ -2334,7 +2334,7 @@ def test_get_outputs_with_query(self, wallet: MoneroWallet) -> None: output_query.set_tx_query(MoneroTxQuery(), True) assert output_query.tx_query is not None output_query.tx_query.hash = tx.hash - outputs = TxUtils.get_and_test_outputs(wallet, output_query, True) + outputs = OutputUtils.get_and_test_outputs(wallet, output_query, True) for output in outputs: assert output.tx is not None @@ -2346,7 +2346,7 @@ def test_get_outputs_with_query(self, wallet: MoneroWallet) -> None: tx_query.hashes = tx_hashes output_query = MoneroOutputQuery() output_query.set_tx_query(tx_query, True) - outputs = TxUtils.get_and_test_outputs(wallet, output_query, True) + outputs = OutputUtils.get_and_test_outputs(wallet, output_query, True) for output in outputs: assert output.tx is not None @@ -2362,8 +2362,8 @@ def test_get_outputs_with_query(self, wallet: MoneroWallet) -> None: tx_query = MoneroTxQuery() tx_query.is_confirmed = True output_query.set_tx_query(tx_query, True) - output_query.min_amount = TxUtils.MAX_FEE - outputs = TxUtils.get_and_test_outputs(wallet, output_query, True) + output_query.min_amount = TxWalletUtils.MAX_FEE + outputs = OutputUtils.get_and_test_outputs(wallet, output_query, True) for output in outputs: assert output.account_index == account_idx @@ -2371,7 +2371,7 @@ def test_get_outputs_with_query(self, wallet: MoneroWallet) -> None: assert output.tx is not None assert output.tx.is_confirmed is True assert output.amount is not None - assert output.amount >= TxUtils.MAX_FEE + assert output.amount >= TxWalletUtils.MAX_FEE # get output by key image output: MoneroOutputWallet = outputs[0] @@ -2423,7 +2423,7 @@ def test_validate_inputs_get_outputs(self, wallet: MoneroWallet) -> None: tx_query: MoneroTxQuery = MoneroTxQuery() tx_query.is_confirmed = True tx_query.include_outputs = True - random_txs: list[MoneroTxWallet] = TxUtils.get_random_transactions(wallet, tx_query, 3, 5) + random_txs: list[MoneroTxWallet] = WalletTxsUtils.get_random_transactions(wallet, tx_query, 3, 5) for random_tx in random_txs: assert len(random_tx.outputs) > 0 @@ -2565,7 +2565,7 @@ def test_check_tx_key(self, wallet: MoneroWallet) -> None: query.is_confirmed = True query.transfer_query = MoneroTransferQuery() query.transfer_query.has_destinations = True - txs = TxUtils.get_random_transactions(wallet, query, 1, WalletUtils.MAX_TX_PROOFS) + txs = WalletTxsUtils.get_random_transactions(wallet, query, 1, WalletUtils.MAX_TX_PROOFS) except AssertionError as e: if "found with" in str(e): raise Exception("No txs with outgoing destinations found; run send tests") @@ -2591,14 +2591,14 @@ def test_check_tx_key(self, wallet: MoneroWallet) -> None: logger.warning(msg) else: assert check.received_amount == 0 - TxUtils.test_check_tx(tx, check) + TxWalletUtils.test_check_tx(tx, check) # test get tx key with invalid hash try: wallet.get_tx_key("invalid_tx_id") raise Exception("Should throw exception for invalid key") except Exception as e: - TxUtils.test_invalid_tx_hash_error(e) + WalletErrorUtils.test_invalid_tx_hash_error(e) # test check with invalid tx hash tx: MoneroTxWallet = txs[0] @@ -2611,21 +2611,21 @@ def test_check_tx_key(self, wallet: MoneroWallet) -> None: wallet.check_tx_key("invalid_tx_id", key, destination.address) raise Exception("Should have thrown exception") except Exception as e: - TxUtils.test_invalid_tx_hash_error(e) + WalletErrorUtils.test_invalid_tx_hash_error(e) # test check with invalid key try: wallet.check_tx_key(tx.hash, "invalid_tx_key", destination.address) raise Exception("Should have thrown exception") except Exception as e: - TxUtils.test_invalid_tx_key_error(e) + WalletErrorUtils.test_invalid_tx_key_error(e) # test check with invalid address try: wallet.check_tx_key(tx.hash, key, "invalid_tx_address") raise Exception("Should have thrown exception") except Exception as e: - TxUtils.test_invalid_address_error(e) + WalletErrorUtils.test_invalid_address_error(e) # test check with different address different_address: Optional[str] = None @@ -2643,7 +2643,7 @@ def test_check_tx_key(self, wallet: MoneroWallet) -> None: assert check.is_good is True assert check.received_amount is not None assert check.received_amount >= 0 - TxUtils.test_check_tx(tx, check) + TxWalletUtils.test_check_tx(tx, check) # Can prove a transaction by getting its signature @pytest.mark.skipif(TestUtils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") @@ -2654,7 +2654,7 @@ def test_check_tx_proof(self, wallet: MoneroWallet) -> None: query: MoneroTxQuery = MoneroTxQuery() query.transfer_query = MoneroTransferQuery() query.transfer_query.has_destinations = True - txs = TxUtils.get_random_transactions(wallet, query, 2, WalletUtils.MAX_TX_PROOFS) + txs = WalletTxsUtils.get_random_transactions(wallet, query, 2, WalletUtils.MAX_TX_PROOFS) except Exception as e: if "found with" in str(e): raise Exception("No txs with outgoing destinations found; run send tests") @@ -2668,7 +2668,7 @@ def test_check_tx_proof(self, wallet: MoneroWallet) -> None: assert destination.address is not None signature: str = wallet.get_tx_proof(tx.hash, destination.address, "This transaction definitely happened.") check: MoneroCheckTx = wallet.check_tx_proof(tx.hash, destination.address, "This transaction definitely happened.", signature) - TxUtils.test_check_tx(tx, check) + TxWalletUtils.test_check_tx(tx, check) # test good check without message tx: MoneroTx = txs[0] @@ -2679,34 +2679,34 @@ def test_check_tx_proof(self, wallet: MoneroWallet) -> None: assert destination.address is not None signature: str = wallet.get_tx_proof(tx.hash, destination.address) check: MoneroCheckTx = wallet.check_tx_proof(tx.hash, destination.address, '', signature) - TxUtils.test_check_tx(tx, check) + TxWalletUtils.test_check_tx(tx, check) # test get proof with invalid hash try: wallet.get_tx_proof("invalid_tx_id", destination.address) raise Exception("Should throw exception for invalid key") except Exception as e: - TxUtils.test_invalid_tx_hash_error(e) + WalletErrorUtils.test_invalid_tx_hash_error(e) # test check tx proof with invalid tx hash try: wallet.check_tx_proof("invalid_tx_id", destination.address, '', signature) raise Exception("Should have thrown exception") except Exception as e: - TxUtils.test_invalid_tx_hash_error(e) + WalletErrorUtils.test_invalid_tx_hash_error(e) # test check with invalid address try: wallet.check_tx_proof(tx.hash, "invalid_tx_address", '', signature) raise Exception("Should have throw exception") except Exception as e: - TxUtils.test_invalid_address_error(e) + WalletErrorUtils.test_invalid_address_error(e) # test check with wrong message signature = wallet.get_tx_proof(tx.hash, destination.address, "This is the right message") check = wallet.check_tx_proof(tx.hash, destination.address, "This is the wrong message", signature) assert check.is_good is False - TxUtils.test_check_tx(tx, check) + TxWalletUtils.test_check_tx(tx, check) # test check with wrong signature other_tx: MoneroTxWallet = txs[1] @@ -2720,7 +2720,7 @@ def test_check_tx_proof(self, wallet: MoneroWallet) -> None: check = wallet.check_tx_proof(tx.hash, destination.address, "This is the right message", wrong_signature) assert check.is_good is False except Exception as e: - TxUtils.test_invalid_signature_error(e) + WalletErrorUtils.test_invalid_signature_error(e) # test check with empty signature try: @@ -2737,7 +2737,7 @@ def test_check_spend_proof(self, wallet: MoneroWallet) -> None: query.is_incoming = False query.in_tx_pool = False query.is_failed = False - txs: list[MoneroTxWallet] = TxUtils.get_random_transactions(wallet, query, 2, WalletUtils.MAX_TX_PROOFS) + txs: list[MoneroTxWallet] = WalletTxsUtils.get_random_transactions(wallet, query, 2, WalletUtils.MAX_TX_PROOFS) for tx in txs: assert tx.is_confirmed is True assert len(tx.incoming_transfers) == 0 @@ -2764,14 +2764,14 @@ def test_check_spend_proof(self, wallet: MoneroWallet) -> None: wallet.get_spend_proof("invalid_tx_id") raise Exception("Should throw exception for invalid key") except Exception as e: - TxUtils.test_invalid_tx_hash_error(e) + WalletErrorUtils.test_invalid_tx_hash_error(e) # test check with invalid tx hash try: wallet.check_spend_proof("invalid_tx_id", '', signature) raise Exception("Should have thrown exception") except Exception as e: - TxUtils.test_invalid_tx_hash_error(e) + WalletErrorUtils.test_invalid_tx_hash_error(e) # test check with invalid message signature = wallet.get_spend_proof(tx.hash, "This is the right message") @@ -2794,7 +2794,7 @@ def test_get_reserve_proof_wallet(self, wallet: MoneroWallet) -> None: # check proof of entire wallet check: MoneroCheckReserve = wallet.check_reserve_proof(wallet.get_primary_address(), "Test message", signature) assert check.is_good is True - TxUtils.test_check_reserve(check) + TxWalletUtils.test_check_reserve(check) balance: int = wallet.get_balance() if balance != check.total_amount: # TODO monero-wallet-rpc: this check fails with unconfirmed txs @@ -2804,12 +2804,12 @@ def test_get_reserve_proof_wallet(self, wallet: MoneroWallet) -> None: assert len(unconfirmed_txs) > 0, "Reserve amount must equal balance unless wallet has unconfirmed txs" # test different wallet address - different_address: str = WalletUtils.get_external_wallet_address() + different_address: str = WalletTestUtils.get_external_wallet_address() try: wallet.check_reserve_proof(different_address, "Test message", signature) raise Exception("Should have thrown exception") except Exception as e: - TxUtils.test_no_subaddress_error(e) + WalletErrorUtils.test_no_subaddress_error(e) # test subaddress try: @@ -2818,20 +2818,20 @@ def test_get_reserve_proof_wallet(self, wallet: MoneroWallet) -> None: wallet.check_reserve_proof(address, "Test message", signature) raise Exception("Should have thrown exception") except Exception as e: - TxUtils.test_no_subaddress_error(e) + WalletErrorUtils.test_no_subaddress_error(e) # test wrong message check = wallet.check_reserve_proof(wallet.get_primary_address(), "Wrong message", signature) # TODO: specifically test reserve checks, probably separate objects assert check.is_good is False - TxUtils.test_check_reserve(check) + TxWalletUtils.test_check_reserve(check) # test wrong signature try: wallet.check_reserve_proof(wallet.get_primary_address(), "Test message", "wrong signature") raise Exception("Should have thrown exception") except Exception as e: - TxUtils.test_signature_header_error(e) + WalletErrorUtils.test_signature_header_error(e) # Can prove reserves in an account @pytest.mark.skipif(TestUtils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disablde") @@ -2849,7 +2849,7 @@ def test_get_reserve_proof_account(self, wallet: MoneroWallet) -> None: signature = wallet.get_reserve_proof_account(account.index, check_amount, msg) check: MoneroCheckReserve = wallet.check_reserve_proof(wallet.get_primary_address(), msg, signature) assert check.is_good is True - TxUtils.test_check_reserve(check) + TxWalletUtils.test_check_reserve(check) assert check.total_amount is not None assert check.total_amount >= 0 num_non_zero_tests += 1 @@ -2863,7 +2863,7 @@ def test_get_reserve_proof_account(self, wallet: MoneroWallet) -> None: assert "Should have thrown exception" != err_msg, err_msg try: - wallet.get_reserve_proof_account(account.index, TxUtils.MAX_FEE, msg) + wallet.get_reserve_proof_account(account.index, TxWalletUtils.MAX_FEE, msg) raise Exception("Should have thrown exception") except Exception as e: err_msg: str = str(e) @@ -2876,7 +2876,7 @@ def test_get_reserve_proof_account(self, wallet: MoneroWallet) -> None: try: account: MoneroAccount = accounts[0] assert account.balance is not None - amount: int = account.balance + TxUtils.MAX_FEE + amount: int = account.balance + TxWalletUtils.MAX_FEE proof: str = wallet.get_reserve_proof_account(0, amount, "Test message") logger.info(f"Account balance: {wallet.get_balance(0)}") logger.info(f"First account balance {account.balance}") @@ -2896,7 +2896,7 @@ def test_get_reserve_proof_account(self, wallet: MoneroWallet) -> None: #assert "Should have thrown exception" not in err_msg, err_msg # test different wallet address - different_address: str = WalletUtils.get_external_wallet_address() + different_address: str = WalletTestUtils.get_external_wallet_address() try: wallet.check_reserve_proof(different_address, "Test message", signature) raise Exception("Should have thrown exception") @@ -2920,7 +2920,7 @@ def test_get_reserve_proof_account(self, wallet: MoneroWallet) -> None: check: MoneroCheckReserve = wallet.check_reserve_proof(wallet.get_primary_address(), "Wrong message", signature) # TODO: specifically test reserve checks, probably separate objects assert check.is_good is False - TxUtils.test_check_reserve(check) + TxWalletUtils.test_check_reserve(check) # test wrong signature try: @@ -2934,7 +2934,7 @@ def test_get_reserve_proof_account(self, wallet: MoneroWallet) -> None: # Can get and set a transaction note @pytest.mark.skipif(TestUtils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") def test_set_tx_note(self, wallet: MoneroWallet) -> None: - txs = TxUtils.get_random_transactions(wallet, None, 1, 5) + txs = WalletTxsUtils.get_random_transactions(wallet, None, 1, 5) # set notes uuid = StringUtils.get_random_string() @@ -3100,7 +3100,7 @@ def test_sign_and_verify_messages(self, wallet: MoneroWallet) -> None: WalletUtils.test_message_signature_result(result, False) # verify message with external address - result = wallet.verify_message(msg, WalletUtils.get_external_wallet_address(), signature) + result = wallet.verify_message(msg, WalletTestUtils.get_external_wallet_address(), signature) WalletUtils.test_message_signature_result(result, False) # sign and verify message with view key @@ -3118,7 +3118,7 @@ def test_sign_and_verify_messages(self, wallet: MoneroWallet) -> None: WalletUtils.test_message_signature_result(result, False) # verify message with external address - result = wallet.verify_message(msg, WalletUtils.get_external_wallet_address(), signature) + result = wallet.verify_message(msg, WalletTestUtils.get_external_wallet_address(), signature) WalletUtils.test_message_signature_result(result, False) # Has an address book @@ -3132,7 +3132,7 @@ def test_address_book(self, wallet: MoneroWallet) -> None: # test adding standard addresses NUM_ENTRIES: int = 5 - address: str = WalletUtils.get_external_wallet_address() + address: str = WalletTestUtils.get_external_wallet_address() indices: list[int] = [] for i in range(NUM_ENTRIES): logger.debug(f"Adding address book entry ({i + 1})") @@ -3470,7 +3470,7 @@ def test_freeze_outputs(self, wallet: MoneroWallet) -> None: @pytest.mark.skipif(TestUtils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") def test_input_key_images(self, wallet: MoneroWallet) -> None: # get subaddress to test input key images - subaddress: Optional[MoneroSubaddress] = WalletUtils.select_subaddress_with_min_balance(wallet, TxUtils.MAX_FEE) + subaddress: Optional[MoneroSubaddress] = WalletTestUtils.select_subaddress_with_min_balance(wallet, TxWalletUtils.MAX_FEE) assert subaddress is not None, "No subaddress with outputs found for test input key images; fund wallet" assert subaddress.account_index is not None assert subaddress.index is not None @@ -3480,20 +3480,20 @@ def test_input_key_images(self, wallet: MoneroWallet) -> None: # test unrelayed single transaction tx_config: MoneroTxConfig = MoneroTxConfig() tx_config.account_index = account_index - tx_config.destinations.append(MoneroDestination(wallet.get_primary_address(), TxUtils.MAX_FEE)) + tx_config.destinations.append(MoneroDestination(wallet.get_primary_address(), TxWalletUtils.MAX_FEE)) spend_tx: MoneroTxWallet = wallet.create_tx(tx_config) - TxUtils.test_spend_tx(spend_tx) + TxWalletUtils.test_spend_tx(spend_tx) # test unrelayed split transactions txs: list[MoneroTxWallet] = wallet.create_txs(tx_config) for tx in txs: - TxUtils.test_spend_tx(tx) + TxWalletUtils.test_spend_tx(tx) # test unrelayed sweep dust dust_key_images: list[str] = [] txs = wallet.sweep_dust(False) for tx in txs: - TxUtils.test_spend_tx(tx) + TxWalletUtils.test_spend_tx(tx) for tx_input in tx.inputs: assert tx_input.key_image is not None assert tx_input.key_image.hex is not None @@ -3505,7 +3505,7 @@ def test_input_key_images(self, wallet: MoneroWallet) -> None: output_query.subaddress_index = subaddress_index output_query.is_spent = False output_query.is_frozen = False - output_query.min_amount = TxUtils.MAX_FEE + output_query.min_amount = TxWalletUtils.MAX_FEE output_query.set_tx_query(MoneroTxQuery(), True) assert output_query.tx_query is not None output_query.tx_query.is_locked = False @@ -3525,9 +3525,7 @@ def test_input_key_images(self, wallet: MoneroWallet) -> None: logger.debug(f"Found {len(dust_outputs)} dust outputs") # remove dust outputs from outputs - for dust_output in dust_outputs: - if dust_output in outputs: - outputs.remove(dust_output) + outputs = list(filter(lambda output: output not in dust_outputs, outputs)) assert len(outputs) > 0, "No available outputs found" logger.debug(f"Using {len(outputs)} available outputs") @@ -3539,7 +3537,7 @@ def test_input_key_images(self, wallet: MoneroWallet) -> None: assert output_key_image is not None tx_config.key_image = output_key_image.hex spend_tx = wallet.sweep_output(tx_config) - TxUtils.test_spend_tx(spend_tx) + TxWalletUtils.test_spend_tx(spend_tx) # test unrelayed sweep wallet ensuring all non-dust outputs are spent available_key_images: set[str] = set() @@ -3555,7 +3553,7 @@ def test_input_key_images(self, wallet: MoneroWallet) -> None: txs = wallet.sweep_unlocked(tx_config) for tx in txs: - TxUtils.test_spend_tx(tx) + TxWalletUtils.test_spend_tx(tx) for input_wallet in tx.inputs: assert input_wallet.key_image is not None assert input_wallet.key_image.hex is not None @@ -3575,20 +3573,20 @@ def test_input_key_images(self, wallet: MoneroWallet) -> None: if max_skipped_output is not None: assert max_skipped_output.amount is not None - assert max_skipped_output.amount < TxUtils.MAX_FEE + assert max_skipped_output.amount < TxWalletUtils.MAX_FEE # Can prove unrelayed txs @pytest.mark.skipif(TestUtils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") def test_prove_unrelayed_txs(self, daemon: MoneroDaemonRpc, wallet: MoneroWallet) -> None: # create unrelayed tx to verify - address1: str = WalletUtils.get_external_wallet_address() + address1: str = WalletTestUtils.get_external_wallet_address() address2: str = wallet.get_address(0, 0) address3: str = wallet.get_address(1, 0) tx_config: MoneroTxConfig = MoneroTxConfig() tx_config.account_index = 0 - tx_config.destinations.append(MoneroDestination(address1, TxUtils.MAX_FEE)) - tx_config.destinations.append(MoneroDestination(address2, TxUtils.MAX_FEE * 2)) - tx_config.destinations.append(MoneroDestination(address3, TxUtils.MAX_FEE * 3)) + tx_config.destinations.append(MoneroDestination(address1, TxWalletUtils.MAX_FEE)) + tx_config.destinations.append(MoneroDestination(address2, TxWalletUtils.MAX_FEE * 2)) + tx_config.destinations.append(MoneroDestination(address3, TxWalletUtils.MAX_FEE * 3)) tx: MoneroTxWallet = wallet.create_tx(tx_config) assert tx.full_hex is not None assert tx.hash is not None @@ -3606,7 +3604,7 @@ def test_prove_unrelayed_txs(self, daemon: MoneroDaemonRpc, wallet: MoneroWallet assert check.is_good assert check.in_tx_pool is True assert check.num_confirmations == 0 - assert check.received_amount == TxUtils.MAX_FEE + assert check.received_amount == TxWalletUtils.MAX_FEE # verify transfer 2 check = verifying_wallet.check_tx_key(tx.hash, tx.key, address2) @@ -3615,14 +3613,14 @@ def test_prove_unrelayed_txs(self, daemon: MoneroDaemonRpc, wallet: MoneroWallet assert check.num_confirmations == 0 # + change amount assert check.received_amount is not None - assert check.received_amount >= TxUtils.MAX_FEE * 2 + assert check.received_amount >= TxWalletUtils.MAX_FEE * 2 # verify transfer 3 check = verifying_wallet.check_tx_key(tx.hash, tx.key, address3) assert check.is_good assert check.in_tx_pool is True assert check.num_confirmations == 0 - assert TxUtils.MAX_FEE * 3 == check.received_amount + assert TxWalletUtils.MAX_FEE * 3 == check.received_amount # cleanup daemon.flush_tx_pool(tx.hash) @@ -3673,6 +3671,7 @@ def test_account_tags(self, wallet: MoneroWallet) -> None: found = True break assert found, f"Could not find tag: {tag.serialize()}" + logger.debug(f"Found account tag: {tag.serialize()}") # re-tag an account tag2: MoneroAccountTag = MoneroAccountTag(f"my_tag_{StringUtils.get_random_string()}", "my tag label 2", [1]) @@ -3744,13 +3743,13 @@ def test_create_and_receive(self, daemon: MoneroDaemonRpc, wallet: MoneroWallet) # wait for txs to confirm and for sufficient unlocked balance TestUtils.WALLET_TX_TRACKER.wait_for_txs_to_clear_pool(wallet) - TestUtils.WALLET_TX_TRACKER.wait_for_unlocked_balance(wallet, 0, None, TxUtils.MAX_FEE) + TestUtils.WALLET_TX_TRACKER.wait_for_unlocked_balance(wallet, 0, None, TxWalletUtils.MAX_FEE) # send funds to the receiver tx_config: MoneroTxConfig = MoneroTxConfig() tx_config.account_index = 0 tx_config.address = receiver.get_primary_address() - tx_config.amount = TxUtils.MAX_FEE + tx_config.amount = TxWalletUtils.MAX_FEE tx_config.relay = True sent_tx: MoneroTxWallet = wallet.create_tx(tx_config) assert sent_tx.hash is not None @@ -3798,10 +3797,10 @@ def test_sweep_subaddresses(self, wallet: MoneroWallet) -> None: for subaddress in account.subaddresses: subaddresses.append(subaddress) assert subaddress.balance is not None - if subaddress.balance > TxUtils.MAX_FEE: + if subaddress.balance > TxWalletUtils.MAX_FEE: subaddresses_balance.append(subaddress) assert subaddress.unlocked_balance is not None - if subaddress.unlocked_balance > TxUtils.MAX_FEE: + if subaddress.unlocked_balance > TxWalletUtils.MAX_FEE: subaddresses_unlocked.append(subaddress) # test requires at least one more subaddresses than the number being swept to verify it does not change @@ -3832,12 +3831,12 @@ def test_sweep_subaddresses(self, wallet: MoneroWallet) -> None: ctx.config = config ctx.is_send_response = True ctx.is_sweep_response = True - TxUtils.test_tx_wallet(tx, ctx) + TxWalletUtils.test_tx_wallet(tx, ctx) # assert unlocked balance is less than max fee subaddress: MoneroSubaddress = wallet.get_subaddress(unlocked_subaddress.account_index, unlocked_subaddress.index) assert subaddress.unlocked_balance is not None - assert subaddress.unlocked_balance < TxUtils.MAX_FEE + assert subaddress.unlocked_balance < TxWalletUtils.MAX_FEE # test subaddresses after sweeping subaddresses_after: list[MoneroSubaddress] = [] @@ -3865,7 +3864,7 @@ def test_sweep_subaddresses(self, wallet: MoneroWallet) -> None: # assert unlocked balance is less than max fee if swept, unchanged otherwise if swept: - assert subaddress_after.unlocked_balance < TxUtils.MAX_FEE + assert subaddress_after.unlocked_balance < TxWalletUtils.MAX_FEE else: assert subaddress_before.unlocked_balance == subaddress_after.unlocked_balance @@ -3885,9 +3884,9 @@ def test_sweep_accounts(self, wallet: MoneroWallet) -> None: continue assert account.balance is not None assert account.unlocked_balance is not None - if account.balance > TxUtils.MAX_FEE: + if account.balance > TxWalletUtils.MAX_FEE: accounts_balance.append(account) - if account.unlocked_balance > TxUtils.MAX_FEE: + if account.unlocked_balance > TxWalletUtils.MAX_FEE: accounts_unlocked.append(account) # test requires at least one more accounts than the number being swept to verify it does not change @@ -3915,7 +3914,7 @@ def test_sweep_accounts(self, wallet: MoneroWallet) -> None: ctx.config = config ctx.is_send_response = True ctx.is_sweep_response = True - TxUtils.test_tx_wallet(tx, ctx) + TxWalletUtils.test_tx_wallet(tx, ctx) assert tx.tx_set is not None assert tx in tx.tx_set.txs @@ -3939,7 +3938,7 @@ def test_sweep_accounts(self, wallet: MoneroWallet) -> None: # assert unlocked balance is less than max fee if swept, unchanged otherwise if swept: - assert account_after.unlocked_balance < TxUtils.MAX_FEE + assert account_after.unlocked_balance < TxWalletUtils.MAX_FEE else: assert account_after.unlocked_balance == account_after.unlocked_balance @@ -3947,14 +3946,14 @@ def test_sweep_accounts(self, wallet: MoneroWallet) -> None: @pytest.mark.skipif(TestUtils.TEST_RESETS is False, reason="TEST_RESETS disabled") def test_sweep_wallet_by_accounts(self, wallet: MoneroWallet) -> None: IntegrationTestUtils.fund_wallet_and_wait_for_unlocked(wallet) - WalletUtils.test_sweep_wallet(wallet, None) + WalletSendUtils.test_sweep_wallet(wallet, None) # Can sweep the whole wallet by subaddresses @pytest.mark.skipif(TestUtils.TEST_RESETS is False, reason="TEST_RESETS disabled") @pytest.mark.xfail(reason="TODO wallet2 error: No unlocked balance in the specified subaddress(es)") def test_sweep_wallet_by_subaddresses(self, wallet: MoneroWallet) -> None: IntegrationTestUtils.fund_wallet_and_wait_for_unlocked(wallet) - WalletUtils.test_sweep_wallet(wallet, True) + WalletSendUtils.test_sweep_wallet(wallet, True) # Can scan transactions by id @pytest.mark.skipif(TestUtils.TEST_RESETS is False, reason="TEST_RESETS disabled") @@ -3964,7 +3963,7 @@ def test_scan_txs(self, wallet: MoneroWallet) -> None: config.restore_height = 0 scan_wallet: MoneroWallet = self._create_wallet(config) logger.debug("Created scan wallet") - TxUtils.test_scan_txs(wallet, scan_wallet) + WalletTxsUtils.test_scan_txs(wallet, scan_wallet) # Can rescan spent @pytest.mark.skipif(TestUtils.TEST_RESETS is False, reason="TEST_RESETS disabled") @@ -3977,21 +3976,7 @@ def test_rescan_spent(self, wallet: MoneroWallet) -> None: def test_rescan_blockchain(self, wallet: MoneroWallet) -> None: wallet.rescan_blockchain() for tx in wallet.get_txs(): - TxUtils.test_tx_wallet(tx) - - #endregion - - #region Utils - - @classmethod - def _test_send_and_update_txs(cls, daemon: MoneroDaemonRpc, wallet: MoneroWallet, config: MoneroTxConfig) -> None: - tester: SendAndUpdateTxsTester = SendAndUpdateTxsTester(daemon, wallet, config) - tester.test() - - @classmethod - def _test_sync_with_pool_submit(cls, daemon: MoneroDaemonRpc, wallet: MoneroWallet, config: MoneroTxConfig) -> None: - tester: SyncWithPoolSubmitTester = SyncWithPoolSubmitTester(daemon, wallet, config) - tester.test() + TxWalletUtils.test_tx_wallet(tx) #endregion diff --git a/tests/test_monero_wallet_full.py b/tests/test_monero_wallet_full.py index 5946fa6..1a8169a 100644 --- a/tests/test_monero_wallet_full.py +++ b/tests/test_monero_wallet_full.py @@ -16,7 +16,8 @@ TestUtils as Utils, StringUtils, AssertUtils, WalletUtils, WalletType, MultisigSampleCodeTester, SyncSeedTester, - SyncProgressTester, WalletEqualityUtils + SyncProgressTester, WalletEqualityUtils, + WalletErrorUtils ) from test_monero_wallet_common import BaseTestMoneroWallet @@ -630,28 +631,28 @@ def test_close(self) -> None: try: wallet.get_height() except Exception as e: - WalletUtils.test_wallet_is_closed_error(e) + WalletErrorUtils.test_wallet_is_closed_error(e) try: wallet.get_seed() except Exception as e: - WalletUtils.test_wallet_is_closed_error(e) + WalletErrorUtils.test_wallet_is_closed_error(e) # TODO calling monero_wallet_full::sync() on a closed wallet causes segmentation fault try: wallet.sync() except Exception as e: - WalletUtils.test_wallet_is_closed_error(e) + WalletErrorUtils.test_wallet_is_closed_error(e) try: wallet.start_syncing() except Exception as e: - WalletUtils.test_wallet_is_closed_error(e) + WalletErrorUtils.test_wallet_is_closed_error(e) try: wallet.stop_syncing() except Exception as e: - WalletUtils.test_wallet_is_closed_error(e) + WalletErrorUtils.test_wallet_is_closed_error(e) # re-open the wallet config = MoneroWalletConfig() diff --git a/tests/test_monero_wallet_rpc.py b/tests/test_monero_wallet_rpc.py index 068c012..1240eda 100644 --- a/tests/test_monero_wallet_rpc.py +++ b/tests/test_monero_wallet_rpc.py @@ -9,8 +9,8 @@ from typing_extensions import override from utils import ( - TestUtils as Utils, StringUtils, WalletUtils, WalletType, - WalletNotificationCollector + TestUtils as Utils, StringUtils, WalletType, + WalletNotificationCollector, WalletErrorUtils ) from test_monero_wallet_common import BaseTestMoneroWallet @@ -263,17 +263,17 @@ def test_close(self, daemon: MoneroDaemonRpc) -> None: try: wallet.get_height() except Exception as e: - WalletUtils.test_no_wallet_file_error(e) + WalletErrorUtils.test_no_wallet_file_error(e) try: wallet.get_seed() except Exception as e: - WalletUtils.test_no_wallet_file_error(e) + WalletErrorUtils.test_no_wallet_file_error(e) try: wallet.sync() except Exception as e: - WalletUtils.test_no_wallet_file_error(e) + WalletErrorUtils.test_no_wallet_file_error(e) # re-open the wallet wallet.open_wallet(path, Utils.WALLET_PASSWORD) diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py index ac2207b..cfb38a5 100644 --- a/tests/utils/__init__.py +++ b/tests/utils/__init__.py @@ -16,13 +16,14 @@ from .wallet_sync_printer import WalletSyncPrinter from .address_book import AddressBook from .keys_book import KeysBook -from .test_context import TestContext -from .tx_context import TxContext -from .binary_block_context import BinaryBlockContext +from .context import TestContext, BinaryBlockContext, TxContext from .string_utils import StringUtils from .wallet_equality_utils import WalletEqualityUtils from .wallet_tx_tracker import WalletTxTracker +from .output_utils import OutputUtils from .tx_utils import TxUtils +from .tx_wallet_utils import TxWalletUtils +from .transfer_utils import TransferUtils from .block_utils import BlockUtils from .daemon_utils import DaemonUtils from .wallet_utils import WalletUtils @@ -45,9 +46,16 @@ from .docker_wallet_rpc_manager import DockerWalletRpcManager from .rpc_connection_utils import RpcConnectionUtils from .base_test_class import BaseTestClass +from .wallet_transfers_utils import WalletTransfersUtils +from .wallet_txs_utils import WalletTxsUtils +from .wallet_send_utils import WalletSendUtils +from .wallet_test_utils import WalletTestUtils +from .wallet_error_utils import WalletErrorUtils __all__ = [ 'WalletUtils', + 'WalletTransfersUtils', + 'WalletTxsUtils', 'DaemonUtils', 'GenUtils', 'AssertUtils', @@ -62,7 +70,10 @@ 'StringUtils', 'WalletEqualityUtils', 'WalletTxTracker', + 'OutputUtils', 'TxUtils', + 'TxWalletUtils', + 'TransferUtils', 'BlockUtils', 'SingleTxSender', 'ToMultipleTxSender', @@ -82,5 +93,8 @@ 'SyncWithPoolSubmitTester', 'DockerWalletRpcManager', 'RpcConnectionUtils', - 'BaseTestClass' + 'BaseTestClass', + 'WalletErrorUtils', + 'WalletSendUtils', + 'WalletTestUtils' ] diff --git a/tests/utils/block_utils.py b/tests/utils/block_utils.py index 7372788..4e9a4ed 100644 --- a/tests/utils/block_utils.py +++ b/tests/utils/block_utils.py @@ -6,8 +6,7 @@ MoneroBlockHeader, MoneroBlock, MoneroDaemonRpc ) -from .binary_block_context import BinaryBlockContext -from .test_context import TestContext +from .context import TestContext, BinaryBlockContext from .tx_utils import TxUtils logger: logging.Logger = logging.getLogger("BlockUtils") @@ -155,3 +154,21 @@ def test_get_blocks_range( for i, block in enumerate(blocks): assert real_start_height + i == block.height cls.test_block(block, block_ctx) + + @classmethod + def is_tx_in_block(cls, tx_hash: str | None, block: MoneroBlock) -> bool: + """Check if transaction is included in block. + + :param str | None tx_hash: tx's hash to check if included in block. + :param MoneroBlock block: block to check if `tx` is included in. + """ + # validate tx hash + assert tx_hash is not None + assert len(tx_hash) > 0 + + # search tx hash in block txs + for block_tx in block.txs: + if block_tx.hash == tx_hash: + return True + + return False diff --git a/tests/utils/context/__init__.py b/tests/utils/context/__init__.py new file mode 100644 index 0000000..8171ae4 --- /dev/null +++ b/tests/utils/context/__init__.py @@ -0,0 +1,9 @@ +from .test_context import TestContext +from .binary_block_context import BinaryBlockContext +from .tx_context import TxContext + +__all__ = [ + 'TestContext', + 'BinaryBlockContext', + 'TxContext' +] diff --git a/tests/utils/binary_block_context.py b/tests/utils/context/binary_block_context.py similarity index 100% rename from tests/utils/binary_block_context.py rename to tests/utils/context/binary_block_context.py diff --git a/tests/utils/test_context.py b/tests/utils/context/test_context.py similarity index 99% rename from tests/utils/test_context.py rename to tests/utils/context/test_context.py index 1c29e44..53878c8 100644 --- a/tests/utils/test_context.py +++ b/tests/utils/context/test_context.py @@ -1,4 +1,5 @@ from __future__ import annotations + from typing import Optional diff --git a/tests/utils/tx_context.py b/tests/utils/context/tx_context.py similarity index 100% rename from tests/utils/tx_context.py rename to tests/utils/context/tx_context.py diff --git a/tests/utils/daemon_utils.py b/tests/utils/daemon_utils.py index 1b085b7..ca8fe0a 100644 --- a/tests/utils/daemon_utils.py +++ b/tests/utils/daemon_utils.py @@ -2,15 +2,14 @@ from abc import ABC from monero import ( - MoneroPeer, MoneroDaemonInfo, MoneroDaemonSyncInfo, + MoneroDaemon, MoneroPeer, MoneroDaemonInfo, MoneroDaemonSyncInfo, MoneroConnectionSpan, MoneroHardForkInfo, MoneroAltChain, MoneroBan, MoneroMinerTxSum, MoneroTxPoolStats, MoneroBlockTemplate, MoneroDaemonUpdateCheckResult, MoneroDaemonUpdateDownloadResult, - MoneroNetworkType, MoneroRpcConnection, MoneroSubmitTxResult, + MoneroNetworkType, MoneroSubmitTxResult, MoneroKeyImageSpentStatus, MoneroDaemonRpc, MoneroTx, - MoneroBlock, MoneroOutputHistogramEntry, MoneroOutputDistributionEntry, - MoneroConnectionType, SerializableStruct + MoneroBlock, MoneroOutputHistogramEntry, MoneroOutputDistributionEntry ) from .gen_utils import GenUtils @@ -326,59 +325,6 @@ def test_tx_pool_stats(cls, stats: MoneroTxPoolStats | None) -> None: # TODO test histo #assert stats.histo is None - @classmethod - def test_rpc_connection( - cls, - connection: MoneroRpcConnection | None, - uri: str | None, - connected: bool, - connection_type: MoneroConnectionType | None - ) -> None: - """Test a monero rpc connection. - - :param MoneroRpcConnection | None connection: rpc connection to test. - :param str | None uri: rpc uri of the connection to test. - :param bool connected: checks if rpc is connected or not. - :param MoneroConnectionType | None connection_type: type of rpc connection to test. - :raises AssertionError: raises an error if rpc connection is not as expected. - """ - # check expected values from rpc connection - assert connection is not None - assert isinstance(connection, SerializableStruct) - assert isinstance(connection, MoneroRpcConnection) - assert uri is not None - assert len(uri) > 0 - assert connection.uri == uri - # check connection - assert connection.check_connection() - assert not connection.check_connection() - assert connection.is_connected() == connected - assert connection.is_online() == connected - - if connected: - assert connection.response_time is not None - assert connection.response_time > 0 - logger.debug(f"Rpc connection response time: {connection.response_time} ms") - else: - assert connection.response_time is None - - # test setting to readonly property - try: - connection.response_time = 0 # type: ignore - raise Exception("Should have failed") - except Exception as e: - e_msg: str = str(e) - assert e_msg != "Should have failed", e_msg - - # test connection type - if connection_type == MoneroConnectionType.I2P: - assert connection.is_i2p() - elif connection_type == MoneroConnectionType.TOR: - assert connection.is_onion() - elif connection_type is not None: - assert not connection.is_i2p() - assert not connection.is_onion() - @classmethod def test_block_template(cls, template: MoneroBlockTemplate | None) -> None: """Test a mining block template. @@ -584,4 +530,20 @@ def get_confirmed_txs(cls, daemon: MoneroDaemonRpc, num_txs: int) -> list[Monero raise Exception(f"Could not get {num_txs} confirmed txs (found: {len(txs)})") + @classmethod + def get_confirmed_tx_hashes(cls, daemon: MoneroDaemon) -> list[str]: + """Get confirmed tx hashes from daemon from last 5 blocks. + + :param MoneroDaemon daemon: daemon instance to get confirmed txs from. + :returns list[str]: confirmed txs hashes. + """ + hashes: list[str] = [] + height: int = daemon.get_height() + while len(hashes) < 5 and height > 0: + height -= 1 + block = daemon.get_block_by_height(height) + for tx_hash in block.tx_hashes: + hashes.append(tx_hash) + return hashes + #endregion diff --git a/tests/utils/docker_wallet_rpc_manager.py b/tests/utils/docker_wallet_rpc_manager.py index f4c8d2b..6a7d94d 100644 --- a/tests/utils/docker_wallet_rpc_manager.py +++ b/tests/utils/docker_wallet_rpc_manager.py @@ -282,7 +282,7 @@ def clear(self, save: bool = False) -> None: except Exception as e: e_str: str = str(e) if "No wallet file" != e_str: - raise e + raise logger.debug("Free wallet rpc instance") diff --git a/tests/utils/from_multiple_tx_sender.py b/tests/utils/from_multiple_tx_sender.py index ee37071..a6b8496 100644 --- a/tests/utils/from_multiple_tx_sender.py +++ b/tests/utils/from_multiple_tx_sender.py @@ -7,8 +7,9 @@ from .assert_utils import AssertUtils from .test_utils import TestUtils -from .tx_utils import TxUtils -from .tx_context import TxContext +from .transfer_utils import TransferUtils +from .tx_wallet_utils import TxWalletUtils +from .context import TxContext logger: logging.Logger = logging.getLogger("FromMultipleTxSender") @@ -67,9 +68,9 @@ def _get_src_account(self) -> MoneroAccount: for subaddress in account.subaddresses: assert subaddress.balance is not None assert subaddress.unlocked_balance is not None - if subaddress.balance > TxUtils.MAX_FEE: + if subaddress.balance > TxWalletUtils.MAX_FEE: num_subaddress_balances += 1 - if subaddress.unlocked_balance > TxUtils.MAX_FEE: + if subaddress.unlocked_balance > TxWalletUtils.MAX_FEE: unlocked_subaddresses.append(subaddress) if num_subaddress_balances >= self.NUM_SUBADDRESSES + 1: @@ -173,12 +174,12 @@ def send(self) -> None: assert len(txs) > 0 outgoing_sum: int = 0 for tx in txs: - TxUtils.test_tx_wallet(tx, ctx) + TxWalletUtils.test_tx_wallet(tx, ctx) outgoing_sum += tx.get_outgoing_amount() if tx.outgoing_transfer is not None and len(tx.outgoing_transfer.destinations) > 0: destination_sum: int = 0 for destination in tx.outgoing_transfer.destinations: - TxUtils.test_destination(destination) + TransferUtils.test_destination(destination) assert destination.amount is not None assert address == destination.address destination_sum += destination.amount diff --git a/tests/utils/integration_test_utils.py b/tests/utils/integration_test_utils.py index aad9642..9c4ad0a 100644 --- a/tests/utils/integration_test_utils.py +++ b/tests/utils/integration_test_utils.py @@ -4,7 +4,7 @@ from time import sleep from monero import MoneroWallet, MoneroTxWallet, MoneroTxQuery -from .wallet_utils import WalletUtils +from .wallet_test_utils import WalletTestUtils from .blockchain_utils import BlockchainUtils from .wallet_type import WalletType from .test_utils import TestUtils @@ -68,7 +68,7 @@ def fund_wallet_and_wait_for_unlocked(cls, wallet: MoneroWallet) -> list[MoneroT :returns list[MoneroTxWallet]: list of transactions used to fund test wallet. """ # fund wallet - txs: list[MoneroTxWallet] = WalletUtils.fund_wallet(wallet) + txs: list[MoneroTxWallet] = WalletTestUtils.fund_wallet(wallet) if len(txs) > 0: # mine blocks to confirm txs block_height: int = BlockchainUtils.wait_for_blocks(11) diff --git a/tests/utils/mining_utils.py b/tests/utils/mining_utils.py index 499b97b..718752d 100644 --- a/tests/utils/mining_utils.py +++ b/tests/utils/mining_utils.py @@ -35,9 +35,9 @@ def is_mining(cls, d: MoneroDaemonRpc | None = None) -> bool: status = daemon.get_mining_status() return status.is_active is True - except Exception as e: + except Exception: if i == 2: - raise e + raise return False diff --git a/tests/utils/output_utils.py b/tests/utils/output_utils.py new file mode 100644 index 0000000..21a911d --- /dev/null +++ b/tests/utils/output_utils.py @@ -0,0 +1,146 @@ +import logging + +from abc import ABC +from typing import Optional + +from monero import ( + MoneroWallet, MoneroOutputQuery, + MoneroOutput, MoneroKeyImage, MoneroOutputWallet +) + +from .gen_utils import GenUtils +from .assert_utils import AssertUtils +from .context import TestContext + +logger: logging.Logger = logging.getLogger("OutputUtils") + + +class OutputUtils(ABC): + + @classmethod + def test_key_image(cls, image: Optional[MoneroKeyImage], context: Optional[TestContext] = None) -> None: + """Test monero key image. + + :param MoneroKeyImage | None image: key image to test. + :param TestContext context: test context (default `None`). + """ + assert image is not None + assert image.hex is not None + assert len(image.hex) > 0 + if image.signature is not None: + assert len(image.signature) > 0 + + @classmethod + def test_output(cls, output: Optional[MoneroOutput], context: Optional[TestContext] = None) -> None: + """Test monero output. + + :param MoneroOutput | None output: output to test. + :param TestContext | None: test context (default `None`). + """ + assert output is not None + GenUtils.test_unsigned_big_integer(output.amount) + if context is None: + return + assert output.tx is not None + ctx = TestContext(context) + if output.tx.in_tx_pool or ctx.has_output_indices is False: + assert output.index is None + else: + assert output.index is not None + assert output.index >= 0 + assert output.stealth_public_key is not None + assert len(output.stealth_public_key) > 0 + + @classmethod + def test_input(cls, xmr_input: Optional[MoneroOutput], ctx: Optional[TestContext]) -> None: + """Test monero input. + + :param MoneroOutput | None zmr_input: input to test. + :param TestContext | None ctx: test context (default `None`). + """ + assert xmr_input is not None + cls.test_output(xmr_input) + cls.test_key_image(xmr_input.key_image, ctx) + assert len(xmr_input.ring_output_indices) > 0 + + @classmethod + def test_input_wallet(cls, xmr_input: Optional[MoneroOutputWallet]) -> None: + """Test monero input wallet. + + :param MoneroOutputWallet xmr_input: wallet input to test. + """ + assert xmr_input is not None + assert xmr_input.key_image is not None + assert xmr_input.key_image.hex is not None + assert len(xmr_input.key_image.hex) > 0 + assert xmr_input.amount is None + + @classmethod + def test_output_wallet(cls, output: Optional[MoneroOutputWallet]) -> None: + """Test monero output wallet. + + :param MoneroOutputWallet | None output: wallet output to test. + """ + assert output is not None + assert output.account_index is not None + assert output.account_index >= 0 + assert output.subaddress_index is not None + assert output.subaddress_index >= 0 + assert output.index is not None + assert output.index >= 0 + assert output.is_spent is not None + # TODO implement is_locked + #assert output.is_locked is not None + assert output.is_frozen is not None + assert output.key_image is not None + assert output.key_image.hex is not None + assert len(output.key_image.hex) > 0 + GenUtils.test_unsigned_big_integer(output.amount, True) + + # output has circular reference to its transaction which has some initialized fields + tx = output.tx + assert tx is not None + assert output in tx.outputs + assert tx.hash is not None + # TODO implement is_locked + #assert tx.is_locked is not None + # TODO monero-wallet-rpc: possible to get unconfirmed outputs? + assert tx.is_confirmed is True + assert tx.is_relayed is True + assert tx.is_failed is False + tx_height = tx.get_height() + assert tx_height is not None + assert tx_height > 0 + + # test copying + copy = output.copy() + assert copy != output + AssertUtils.assert_equals(copy, output) + # TODO: should output copy do deep copy of tx so models are graph instead of tree? Would need to work out circular references + # monero-cpp gives non-null output tx + # assert copy.tx is None + assert copy.tx is not None + assert copy.tx == output.tx + + @classmethod + def get_and_test_outputs(cls, wallet: MoneroWallet, query: Optional[MoneroOutputQuery], is_expected: Optional[bool]) -> list[MoneroOutputWallet]: + """Fetches and tests wallet outputs (i.e. wallet tx outputs) according to the given query. + + :param MoneroWallet wallet: wallet to get outputs from. + :param MoneroOutputQuery | None query: output query. + :param bool | None is_expected: expected non-empty outputs. + """ + + copy = query.copy() if query is not None else None + outputs = wallet.get_outputs(query) if query is not None else wallet.get_outputs(MoneroOutputQuery()) + AssertUtils.assert_equals(copy, query) + + if is_expected is False: + assert len(outputs) == 0 + elif is_expected is True: + assert len(outputs) > 0, "Outputs were expected but not found; run send tests" + + for output in outputs: + OutputUtils.test_output_wallet(output) + + return outputs diff --git a/tests/utils/rpc_connection_utils.py b/tests/utils/rpc_connection_utils.py index a26181f..ce333f9 100644 --- a/tests/utils/rpc_connection_utils.py +++ b/tests/utils/rpc_connection_utils.py @@ -1,51 +1,85 @@ +import logging + from abc import ABC -from monero import MoneroRpcConnection +from monero import SerializableStruct, MoneroRpcConnection, MoneroConnectionType + +logger: logging.Logger = logging.getLogger("RpcConnectionUtils") class RpcConnectionUtils(ABC): """Test utils for rpc connections.""" @classmethod - def test_connections_and_order( - cls, - ordered_connections: list[MoneroRpcConnection], - connections: list[MoneroRpcConnection], - check_never_connected: bool - ) -> None: - """ - Test rpc connections and order. + def setup_rpc_connection(cls, connection: MoneroRpcConnection) -> None: + """Setup and check rpc connection. - :param list[MoneroRpcConnection] ordered_connections: list of ordered connections to test. - :param list[MoneroRpcConnection] connections: connections to test with ordered. - :param bool check_never_connected: check connection never connected. + :param MoneroRpcConnection connection: rpc connection to setup. """ - assert ordered_connections[0] == connections[4] - assert ordered_connections[1] == connections[2] - assert ordered_connections[2] == connections[3] - assert ordered_connections[3] == connections[0] - connection = connections[1] - assert connection is not None - assert ordered_connections[4].uri == connection.uri - - if not check_never_connected: + if connection.is_connected() is not None: return - - for connection in ordered_connections: - assert connection.is_online() is None + cls.test_check_rpc_connection(connection, True) @classmethod - def test_connections_order(cls, ordered_connections: list[MoneroRpcConnection], connections: list[MoneroRpcConnection]) -> None: + def test_check_rpc_connection(cls, connection: MoneroRpcConnection, connected: bool) -> None: + """Test rpc connection check. + + :param MoneroRpcConnection connection: rpc connection to check. + :param bool connected: expected rpc connection to be connected after check. """ - Test rpc connections order. + # check connection + assert connection.check_connection() + assert not connection.check_connection() + assert connection.is_connected() == connected + assert connection.is_online() == connected - :param list[MoneroRpcConnection] ordered_connections: list of ordered connections to test. - :param list[MoneroRpcConnection] connections: connections to test with ordered. + if connected: + assert connection.response_time is not None + assert connection.response_time > 0 + logger.debug(f"Rpc connection response time: {connection.response_time} ms") + else: + assert connection.response_time is None + + @classmethod + def test_rpc_connection( + cls, + connection: MoneroRpcConnection | None, + uri: str | None, + connected: bool, + connection_type: MoneroConnectionType | None + ) -> None: + """Test a monero rpc connection. + + :param MoneroRpcConnection | None connection: rpc connection to test. + :param str | None uri: rpc uri of the connection to test. + :param bool connected: checks if rpc is connected or not. + :param MoneroConnectionType | None connection_type: type of rpc connection to test. + :raises AssertionError: raises an error if rpc connection is not as expected. """ - assert ordered_connections[0] == connections[4] - assert ordered_connections[1] == connections[0] - connection = connections[1] + # check expected values from rpc connection assert connection is not None - assert ordered_connections[2].uri == connection.uri - assert ordered_connections[3] == connections[2] - assert ordered_connections[4] == connections[3] + assert isinstance(connection, SerializableStruct) + assert isinstance(connection, MoneroRpcConnection) + assert uri is not None + assert len(uri) > 0 + assert connection.uri == uri + + # test check connection + cls.test_check_rpc_connection(connection, connected) + + # test setting to readonly property + try: + connection.response_time = 0 # type: ignore + raise Exception("Should have failed") + except Exception as e: + e_msg: str = str(e) + assert e_msg != "Should have failed", e_msg + + # test connection type + if connection_type == MoneroConnectionType.I2P: + assert connection.is_i2p() + elif connection_type == MoneroConnectionType.TOR: + assert connection.is_onion() + elif connection_type is not None: + assert not connection.is_i2p() + assert not connection.is_onion() diff --git a/tests/utils/send_and_update_txs_tester.py b/tests/utils/send_and_update_txs_tester.py index ef77576..10699af 100644 --- a/tests/utils/send_and_update_txs_tester.py +++ b/tests/utils/send_and_update_txs_tester.py @@ -7,9 +7,10 @@ ) from .test_utils import TestUtils -from .tx_utils import TxUtils +from .tx_wallet_utils import TxWalletUtils +from .wallet_txs_utils import WalletTxsUtils from .mining_utils import MiningUtils -from .tx_context import TxContext +from .context import TxContext logger: logging.Logger = logging.getLogger("SendAndUpdateTxsTester") @@ -53,7 +54,7 @@ def setup(self) -> None: TestUtils.WALLET_TX_TRACKER.wait_for_txs_to_clear_pool(self.wallet) assert len(self.config.subaddress_indices) == 0 assert self.config.account_index is not None - fee: int = TxUtils.MAX_FEE * 2 + fee: int = TxWalletUtils.MAX_FEE * 2 TestUtils.WALLET_TX_TRACKER.wait_for_unlocked_balance(self.wallet, self.config.account_index, None, fee) def test_unlock_tx(self, tx: MoneroTxWallet, is_send_response: bool) -> None: @@ -67,7 +68,7 @@ def test_unlock_tx(self, tx: MoneroTxWallet, is_send_response: bool) -> None: ctx.config = self.config ctx.is_send_response = is_send_response try: - TxUtils.test_tx_wallet(tx, ctx) + TxWalletUtils.test_tx_wallet(tx, ctx) except Exception as e: logger.warning(e) raise @@ -109,6 +110,44 @@ def test_out_in_pairs(self, txs: list[MoneroTxWallet], is_send_response: bool) - else: self.test_out_in_pair(tx_out, tx_in) + @classmethod + def merge_fetched_into_updated_txs( + cls, + sent_txs: list[MoneroTxWallet], + fetched_txs: list[MoneroTxWallet] + ) -> list[MoneroTxWallet] | None: + """Merge fetched txs into updated txs and original sent txs. + + :param list[MoneroTxWallet] sent_txs: original sent txs. + :param list[MoneroTxWallet] fetched_txs: fetched txs to merge into updated txs. + :returns list[MoneroTxWallet] | None: updated txs to merged with fetched txs. + """ + updated_txs: list[MoneroTxWallet] | None = None + + for fetched_tx in fetched_txs: + # merge with updated txs + if updated_txs is None: + updated_txs = fetched_txs + else: + for updated_tx in updated_txs: + if fetched_tx.hash != updated_tx.hash or fetched_tx.is_outgoing != updated_tx.is_outgoing: + continue + updated_tx.merge(fetched_tx.copy()) + if updated_tx.block is None and fetched_tx.block is not None: + # copy block for testing + updated_tx.block = fetched_tx.block.copy() + updated_tx.block.txs = [updated_tx] + + # merge with original sent txs + for sent_tx in sent_txs: + if fetched_tx.hash != sent_tx.hash or fetched_tx.is_outgoing != sent_tx.is_outgoing: + continue + # TODO: it's mergeable but tests don't account for extra info + # from send (e.g. hex) so not tested; could specify in test config + sent_tx.merge(fetched_tx.copy()) + + return updated_txs + def wait_for_confirmations(self, sent_txs: list[MoneroTxWallet], num_confirmations_total: int) -> None: """Wait for txs to confirm. @@ -116,7 +155,6 @@ def wait_for_confirmations(self, sent_txs: list[MoneroTxWallet], num_confirmatio :param int num_confirmations_total: number of confirmed txs required. """ # track resulting outgoing and incoming txs as blocks are added to the chain - updated_txs: list[MoneroTxWallet] | None = None logger.info(f"{self.num_confirmations} < {num_confirmations_total} needed confirmations") header: MoneroBlockHeader = self.daemon.wait_for_next_block_header() logger.info(f"*** Block {header.height} added to chain ***") @@ -133,34 +171,14 @@ def wait_for_confirmations(self, sent_txs: list[MoneroTxWallet], num_confirmatio query: MoneroTxQuery = MoneroTxQuery() query.hashes = tx_hashes - fetched_txs: list[MoneroTxWallet] = TxUtils.get_and_test_txs(self.wallet, query, None, True, TestUtils.REGTEST) + fetched_txs: list[MoneroTxWallet] = WalletTxsUtils.get_and_test_txs(self.wallet, query, None, True, TestUtils.REGTEST) assert len(fetched_txs) > 0 # test fetched txs self.test_out_in_pairs(fetched_txs, False) # merge fetched txs into updated txs and original sent txs - for fetched_tx in fetched_txs: - # merge with updated txs - if updated_txs is None: - updated_txs = fetched_txs - else: - for updated_tx in updated_txs: - if fetched_tx.hash != updated_tx.hash or fetched_tx.is_outgoing != updated_tx.is_outgoing: - continue - updated_tx.merge(fetched_tx.copy()) - if updated_tx.block is None and fetched_tx.block is not None: - # copy block for testing - updated_tx.block = fetched_tx.block.copy() - updated_tx.block.txs = [updated_tx] - - # merge with original sent txs - for sent_tx in sent_txs: - if fetched_tx.hash != sent_tx.hash or fetched_tx.is_outgoing != sent_tx.is_outgoing: - continue - # TODO: it's mergeable but tests don't account for extra info - # from send (e.g. hex) so not tested; could specify in test config - sent_tx.merge(fetched_tx.copy()) + updated_txs: list[MoneroTxWallet] | None = self.merge_fetched_into_updated_txs(sent_txs, fetched_txs) # test updated txs assert updated_txs is not None @@ -187,7 +205,7 @@ def test(self) -> None: # test sent transactions for tx in sent_txs: - TxUtils.test_tx_wallet(tx, ctx) + TxWalletUtils.test_tx_wallet(tx, ctx) assert tx.is_confirmed is False assert tx.in_tx_pool is True diff --git a/tests/utils/single_tx_sender.py b/tests/utils/single_tx_sender.py index e8cde31..bcca428 100644 --- a/tests/utils/single_tx_sender.py +++ b/tests/utils/single_tx_sender.py @@ -7,11 +7,13 @@ MoneroTxWallet, MoneroTxQuery ) -from .tx_context import TxContext +from .context import TxContext from .assert_utils import AssertUtils from .wallet_tx_tracker import WalletTxTracker as TxTracker from .test_utils import TestUtils -from .tx_utils import TxUtils +from .tx_wallet_utils import TxWalletUtils +from .wallet_txs_utils import WalletTxsUtils +from .transfer_utils import TransferUtils logger: logging.Logger = logging.getLogger("SingleTxSender") @@ -55,7 +57,7 @@ def unlocked_balance_before(self) -> int: def send_amount(self) -> int: """Amount to send.""" b = self.unlocked_balance_before - return int((b - TxUtils.MAX_FEE) / self.SEND_DIVISOR) + return int((b - TxWalletUtils.MAX_FEE) / self.SEND_DIVISOR) @property def address(self) -> str: @@ -96,7 +98,7 @@ def _get_locked_txs(self) -> list[MoneroTxWallet]: # query locked txs query = MoneroTxQuery() query.is_locked = True - locked_txs = TxUtils.get_and_test_txs(self._wallet, query, None, True, TestUtils.REGTEST) + locked_txs = WalletTxsUtils.get_and_test_txs(self._wallet, query, None, True, TestUtils.REGTEST) for locked_tx in locked_txs: assert locked_tx.is_locked, "Expected locked tx" @@ -116,9 +118,9 @@ def _check_balance(self) -> None: continue assert subaddress.balance is not None assert subaddress.unlocked_balance is not None - if subaddress.balance > TxUtils.MAX_FEE: + if subaddress.balance > TxWalletUtils.MAX_FEE: sufficient_balance = True - if subaddress.unlocked_balance > TxUtils.MAX_FEE: + if subaddress.unlocked_balance > TxWalletUtils.MAX_FEE: self._from_account = account self._from_subaddress = subaddress break @@ -210,7 +212,7 @@ def _handle_non_relayed_tx(self, txs: list[MoneroTxWallet], config: MoneroTxConf ctx.is_send_response = True # test transactions - TxUtils.test_txs_wallet(txs, ctx) + TxWalletUtils.test_txs_wallet(txs, ctx) # txs are not in the pool for tx_created in txs: @@ -267,7 +269,7 @@ def send(self) -> None: AssertUtils.assert_equals(config_copy, config) # test common tx set among txs - TxUtils.test_common_tx_sets(txs, False, False, False) + TxWalletUtils.test_common_tx_sets(txs, False, False, False) # handle non-relayed transaction txs = self._handle_non_relayed_tx(txs, config) @@ -288,7 +290,7 @@ def send(self) -> None: # test transactions assert len(txs) > 0 for tx in txs: - TxUtils.test_tx_wallet(tx, ctx) + TxWalletUtils.test_tx_wallet(tx, ctx) assert tx.outgoing_transfer is not None assert self._from_account.index == tx.outgoing_transfer.account_index assert len(tx.outgoing_transfer.subaddress_indices) == 1 @@ -302,7 +304,7 @@ def send(self) -> None: if dest_count > 0: assert dest_count == 1 for dest in tx.outgoing_transfer.destinations: - TxUtils.test_destination(dest) + TransferUtils.test_destination(dest) assert dest.address == self.address assert self.send_amount == dest.amount diff --git a/tests/utils/string_utils.py b/tests/utils/string_utils.py index 81980e6..2980119 100644 --- a/tests/utils/string_utils.py +++ b/tests/utils/string_utils.py @@ -38,12 +38,3 @@ def get_random_string(cls, n: int = 25) -> str: """ # generate random string return token_hex(n) - - @classmethod - def is_none_or_empty(cls, str_value: str | None) -> bool: - """Checks if string is `None` or empty. - - :param str | None str_value: string value to check. - :returns bool: `True` if `str_value` is `None` or empty, `False` otherwise. - """ - return str_value is None or len(str_value) == 0 diff --git a/tests/utils/sync_progress_tester.py b/tests/utils/sync_progress_tester.py index 634fd75..439b7cc 100644 --- a/tests/utils/sync_progress_tester.py +++ b/tests/utils/sync_progress_tester.py @@ -99,4 +99,3 @@ def on_done(self, chain_height: int) -> None: # otherwise last height is chain height - 1 assert chain_height - 1 == self.prev_height assert chain_height == self.prev_complete_height - diff --git a/tests/utils/test_utils.py b/tests/utils/test_utils.py index 159e76a..1e6513f 100644 --- a/tests/utils/test_utils.py +++ b/tests/utils/test_utils.py @@ -499,7 +499,7 @@ def get_wallet_rpc(cls) -> MoneroWalletRpc: config.restore_height = cls.FIRST_RECEIVE_HEIGHT cls._WALLET_RPC.create_wallet(config) else: - raise e + raise # ensure we're testing the right wallet assert cls.SEED == cls._WALLET_RPC.get_seed() @@ -555,15 +555,6 @@ def free_wallet_rpc_resources(cls, save: bool = False) -> None: """ cls.RPC_WALLET_MANAGER.clear(save) - @classmethod - def is_wallet_rpc_resource(cls, wallet: MoneroWallet) -> bool: - """Indicates if wallet is using a docker rpc instance. - - :param MoneroWallet wallet: wallet to check. - :returns bool: `True` if wallet is a docker wallet-rpc instance, `False` otherwise. - """ - return cls.RPC_WALLET_MANAGER.is_docker_instance(wallet) - @classmethod def free_wallet_rpc_resource(cls, wallet: MoneroWallet, save: bool = False) -> None: """Free docker resource used by wallet. diff --git a/tests/utils/to_multiple_tx_sender.py b/tests/utils/to_multiple_tx_sender.py index cd3df72..1738832 100644 --- a/tests/utils/to_multiple_tx_sender.py +++ b/tests/utils/to_multiple_tx_sender.py @@ -6,7 +6,11 @@ MoneroTxPriority, MoneroDestination, MoneroTxWallet ) -from utils import TxUtils, AssertUtils, TestUtils, TxContext +from .assert_utils import AssertUtils +from .context import TxContext +from .tx_wallet_utils import TxWalletUtils +from .transfer_utils import TransferUtils +from .test_utils import TestUtils logger: logging.Logger = logging.getLogger("ToMultipleTxSender") @@ -39,7 +43,7 @@ def total_subaddresses(self) -> int: @property def min_account_amount(self) -> int: """Minimum account unlocked balance needed.""" - fee: int = TxUtils.MAX_FEE # 75000000000 + fee: int = TxWalletUtils.MAX_FEE # 75000000000 # compute the minimum account unlocked balance needed in order to fulfill the config if self._send_amount_per_subaddress is not None: # min account amount must cover the total amount being sent plus the tx fee = num_addresses * (amount_per_subaddress + fee) @@ -180,7 +184,7 @@ def send(self) -> None: send_amount_per_subaddress: Optional[int] = self._send_amount_per_subaddress if send_amount_per_subaddress is None: - send_amount = TxUtils.MAX_FEE * 5 * total_subaddresses + send_amount = TxWalletUtils.MAX_FEE * 5 * total_subaddresses send_amount_per_subaddress = int(send_amount / total_subaddresses) else: send_amount = send_amount_per_subaddress * total_subaddresses @@ -231,7 +235,7 @@ def send(self) -> None: assert len(txs) > 0 fee_sum: int = 0 outgoing_sum: int = 0 - TxUtils.test_txs_wallet(txs, ctx) + TxWalletUtils.test_txs_wallet(txs, ctx) for tx in txs: assert tx.fee is not None fee_sum += tx.fee @@ -240,7 +244,7 @@ def send(self) -> None: if tx.outgoing_transfer is not None and len(tx.outgoing_transfer.destinations) > 0: destination_sum: int = 0 for destination in tx.outgoing_transfer.destinations: - TxUtils.test_destination(destination) + TransferUtils.test_destination(destination) assert destination.amount is not None assert destination.address in destination_addresses destination_sum += destination.amount diff --git a/tests/utils/transfer_utils.py b/tests/utils/transfer_utils.py new file mode 100644 index 0000000..55d4243 --- /dev/null +++ b/tests/utils/transfer_utils.py @@ -0,0 +1,102 @@ +import logging + +from abc import ABC +from typing import Optional + +from monero import ( + MoneroTransfer, MoneroIncomingTransfer, MoneroOutgoingTransfer, + MoneroDestination, MoneroUtils +) + +from .gen_utils import GenUtils +from .context import TxContext +from .test_utils import TestUtils + +logger: logging.Logger = logging.getLogger("TransferUtils") + + +class TransferUtils(ABC): + """Monero transfer test utilities.""" + + @classmethod + def test_destination(cls, dest: Optional[MoneroDestination]) -> None: + """Test monero destination. + + :param MoneroDestination | None dest: destination to test. + """ + assert dest is not None + assert dest.address is not None + MoneroUtils.validate_address(dest.address, TestUtils.NETWORK_TYPE) + GenUtils.test_unsigned_big_integer(dest.amount, True) + + @classmethod + def test_incoming_transfer(cls, transfer: Optional[MoneroIncomingTransfer]) -> None: + """Test monero incoming transfer. + + :param MoneroIncomingTransfer | None transfer: transfer to test. + """ + assert transfer is not None + assert transfer.is_incoming() is True + assert transfer.is_outgoing() is False + assert transfer.address is not None + assert transfer.subaddress_index is not None + assert transfer.subaddress_index >= 0 + assert transfer.num_suggested_confirmations is not None + assert transfer.num_suggested_confirmations > 0 + + @classmethod + def test_outgoing_transfer(cls, transfer: Optional[MoneroOutgoingTransfer], ctx: TxContext) -> None: + """Test monero outgoing transfer. + + :param MoneroOutgoingTransfer | None transfer: outgoing transfer to test. + :param TxContext ctx: test context. + """ + assert transfer is not None + assert transfer.is_incoming() is False + assert transfer.is_outgoing() is True + if ctx.is_send_response is not True: + assert len(transfer.subaddress_indices) > 0 + + for subaddress_idx in transfer.subaddress_indices: + assert subaddress_idx >= 0 + + if len(transfer.addresses) > 0: + assert len(transfer.subaddress_indices) == len(transfer.addresses) + for address in transfer.addresses: + assert address is not None + + # test destinations sum to outgoing amount + if len(transfer.destinations) > 0: + transfer_sum: int = 0 + for destination in transfer.destinations: + cls.test_destination(destination) + assert destination.amount is not None + transfer_sum += destination.amount + + assert transfer_sum == transfer.amount, f"Destinations sum doesn't equal transfer amount: {transfer_sum} != {transfer.amount}" + + @classmethod + def test_transfer(cls, transfer: Optional[MoneroTransfer], context: Optional[TxContext]) -> None: + """Test monero transfer. + + :param MoneroTransfer | None transfer: transfer to test. + :param TxContext | None: test context. + """ + ctx = context if context is not None else TxContext() + assert transfer is not None + GenUtils.test_unsigned_big_integer(transfer.amount) + if ctx.is_sweep_output_response is not True: + assert transfer.account_index is not None + assert transfer.account_index >= 0 + if transfer.is_incoming(): + assert isinstance(transfer, MoneroIncomingTransfer) + cls.test_incoming_transfer(transfer) + else: + assert isinstance(transfer, MoneroOutgoingTransfer) + cls.test_outgoing_transfer(transfer, ctx) + + # transfer and tx reference each other + assert transfer.tx is not None + if transfer != transfer.tx.outgoing_transfer: + assert len(transfer.tx.incoming_transfers) != 0 + assert transfer in transfer.tx.incoming_transfers, "Transaction does not reference given transfer" diff --git a/tests/utils/tx_spammer.py b/tests/utils/tx_spammer.py index 53044f7..d86e43a 100644 --- a/tests/utils/tx_spammer.py +++ b/tests/utils/tx_spammer.py @@ -3,7 +3,7 @@ from typing import Optional from monero import MoneroWalletKeys, MoneroTxWallet, MoneroNetworkType -from .wallet_utils import WalletUtils +from .wallet_test_utils import WalletTestUtils logger: logging.Logger = logging.getLogger("TxSpammer") @@ -30,7 +30,7 @@ def get_wallets(self) -> list[MoneroWalletKeys]: """ if self._wallets is None: # create random wallets to use - self._wallets = WalletUtils.create_random_wallets(self._network_type) + self._wallets = WalletTestUtils.create_random_wallets(self._network_type) # return list copy return self._wallets.copy() @@ -46,7 +46,7 @@ def spam(self) -> list[MoneroTxWallet]: for i, wallet in enumerate(wallets): # fund random wallet - spam_txs = WalletUtils.fund_wallet(wallet, 1, 1, 0) + spam_txs = WalletTestUtils.fund_wallet(wallet, 1, 1, 0) wallet_addr = wallet.get_primary_address() assert spam_txs is not None and len(spam_txs) > 0, f"Could not spam tx for random wallet ({i}): {wallet_addr}" for tx in spam_txs: diff --git a/tests/utils/tx_utils.py b/tests/utils/tx_utils.py index 7ca0a68..200a4ee 100644 --- a/tests/utils/tx_utils.py +++ b/tests/utils/tx_utils.py @@ -2,22 +2,12 @@ from abc import ABC from typing import Optional -from random import shuffle -from monero import ( - MoneroWallet, MoneroTxQuery, MoneroTxWallet, - MoneroBlock, MoneroTransfer, MoneroIncomingTransfer, - MoneroOutgoingTransfer, MoneroDestination, - MoneroUtils, MoneroOutputWallet, MoneroTx, - MoneroOutput, MoneroKeyImage, MoneroDaemon, - MoneroTxConfig, MoneroTxSet, MoneroTransferQuery, - MoneroOutputQuery, MoneroCheckTx, MoneroCheckReserve -) +from monero import MoneroTx -from .tx_context import TxContext from .gen_utils import GenUtils -from .test_context import TestContext +from .context import TestContext from .assert_utils import AssertUtils -from .test_utils import TestUtils +from .output_utils import OutputUtils logger: logging.Logger = logging.getLogger("TxUtils") @@ -27,471 +17,6 @@ class TxUtils(ABC): __test__ = False - MAX_FEE: int = 7500000*10000 - """Max tx fee""" - - @classmethod - def test_key_image(cls, image: Optional[MoneroKeyImage], context: Optional[TestContext] = None) -> None: - """Test monero key image. - - :param MoneroKeyImage | None image: key image to test. - :param TestContext context: test context (default `None`). - """ - assert image is not None - assert image.hex is not None - assert len(image.hex) > 0 - if image.signature is not None: - assert len(image.signature) > 0 - - @classmethod - def test_output(cls, output: Optional[MoneroOutput], context: Optional[TestContext] = None) -> None: - """Test monero output. - - :param MoneroOutput | None output: output to test. - :param TestContext | None: test context (default `None`). - """ - assert output is not None - GenUtils.test_unsigned_big_integer(output.amount) - if context is None: - return - assert output.tx is not None - ctx = TestContext(context) - if output.tx.in_tx_pool or ctx.has_output_indices is False: - assert output.index is None - else: - assert output.index is not None - assert output.index >= 0 - assert output.stealth_public_key is not None - assert len(output.stealth_public_key) > 0 - - @classmethod - def test_input(cls, xmr_input: Optional[MoneroOutput], ctx: Optional[TestContext]) -> None: - """Test monero input. - - :param MoneroOutput | None zmr_input: input to test. - :param TestContext | None ctx: test context (default `None`). - """ - assert xmr_input is not None - cls.test_output(xmr_input) - cls.test_key_image(xmr_input.key_image, ctx) - assert len(xmr_input.ring_output_indices) > 0 - - @classmethod - def test_input_wallet(cls, xmr_input: Optional[MoneroOutputWallet]) -> None: - """Test monero input wallet. - - :param MoneroOutputWallet xmr_input: wallet input to test. - """ - assert xmr_input is not None - assert xmr_input.key_image is not None - assert xmr_input.key_image.hex is not None - assert len(xmr_input.key_image.hex) > 0 - assert xmr_input.amount is None - - @classmethod - def test_output_wallet(cls, output: Optional[MoneroOutputWallet]) -> None: - """Test monero output wallet. - - :param MoneroOutputWallet | None output: wallet output to test. - """ - assert output is not None - assert output.account_index is not None - assert output.account_index >= 0 - assert output.subaddress_index is not None - assert output.subaddress_index >= 0 - assert output.index is not None - assert output.index >= 0 - assert output.is_spent is not None - # TODO implement is_locked - #assert output.is_locked is not None - assert output.is_frozen is not None - assert output.key_image is not None - assert output.key_image.hex is not None - assert len(output.key_image.hex) > 0 - GenUtils.test_unsigned_big_integer(output.amount, True) - - # output has circular reference to its transaction which has some initialized fields - tx = output.tx - assert tx is not None - assert output in tx.outputs - assert tx.hash is not None - # TODO implement is_locked - #assert tx.is_locked is not None - # TODO monero-wallet-rpc: possible to get unconfirmed outputs? - assert tx.is_confirmed is True - assert tx.is_relayed is True - assert tx.is_failed is False - tx_height = tx.get_height() - assert tx_height is not None - assert tx_height > 0 - - # test copying - copy = output.copy() - assert copy != output - AssertUtils.assert_equals(copy, output) - # TODO: should output copy do deep copy of tx so models are graph instead of tree? Would need to work out circular references - # monero-cpp gives non-null output tx - # assert copy.tx is None - assert copy.tx is not None - assert copy.tx == output.tx - - @classmethod - def test_destination(cls, dest: Optional[MoneroDestination]) -> None: - """Test monero destination. - - :param MoneroDestination | None dest: destination to test. - """ - assert dest is not None - assert dest.address is not None - MoneroUtils.validate_address(dest.address, TestUtils.NETWORK_TYPE) - GenUtils.test_unsigned_big_integer(dest.amount, True) - - @classmethod - def test_incoming_transfer(cls, transfer: Optional[MoneroIncomingTransfer]) -> None: - """Test monero incoming transfer. - - :param MoneroIncomingTransfer | None transfer: transfer to test. - """ - assert transfer is not None - assert transfer.is_incoming() is True - assert transfer.is_outgoing() is False - assert transfer.address is not None - assert transfer.subaddress_index is not None - assert transfer.subaddress_index >= 0 - assert transfer.num_suggested_confirmations is not None - assert transfer.num_suggested_confirmations > 0 - - @classmethod - def test_outgoing_transfer(cls, transfer: Optional[MoneroOutgoingTransfer], ctx: TxContext) -> None: - """Test monero outgoing transfer. - - :param MoneroOutgoingTransfer | None transfer: outgoing transfer to test. - :param TxContext ctx: test context. - """ - assert transfer is not None - assert transfer.is_incoming() is False - assert transfer.is_outgoing() is True - if ctx.is_send_response is not True: - assert len(transfer.subaddress_indices) > 0 - - for subaddress_idx in transfer.subaddress_indices: - assert subaddress_idx >= 0 - - if len(transfer.addresses) > 0: - assert len(transfer.subaddress_indices) == len(transfer.addresses) - for address in transfer.addresses: - assert address is not None - - # test destinations sum to outgoing amount - if len(transfer.destinations) > 0: - transfer_sum: int = 0 - for destination in transfer.destinations: - cls.test_destination(destination) - assert destination.amount is not None - transfer_sum += destination.amount - - assert transfer_sum == transfer.amount, f"Destinations sum doesn't equal transfer amount: {transfer_sum} != {transfer.amount}" - - @classmethod - def test_transfer(cls, transfer: Optional[MoneroTransfer], context: Optional[TxContext]) -> None: - """Test monero transfer. - - :param MoneroTransfer | None transfer: transfer to test. - :param TxContext | None: test context. - """ - ctx = context if context is not None else TxContext() - assert transfer is not None - GenUtils.test_unsigned_big_integer(transfer.amount) - if ctx.is_sweep_output_response is not True: - assert transfer.account_index is not None - assert transfer.account_index >= 0 - if transfer.is_incoming(): - assert isinstance(transfer, MoneroIncomingTransfer) - cls.test_incoming_transfer(transfer) - else: - assert isinstance(transfer, MoneroOutgoingTransfer) - cls.test_outgoing_transfer(transfer, ctx) - - # transfer and tx reference each other - assert transfer.tx is not None - if transfer != transfer.tx.outgoing_transfer: - assert len(transfer.tx.incoming_transfers) != 0 - assert transfer in transfer.tx.incoming_transfers, "Transaction does not reference given transfer" - - @classmethod - def test_tx_wallet(cls, tx: Optional[MoneroTxWallet], context: Optional[TxContext] = None) -> None: - """Test monero tx wallet. - - :param MoneroTxWallet | None tx: wallet transaction to test. - :param TxContext | None context: test context (default `None`). - """ - # validate / sanitize inputs - ctx = TxContext(context) - ctx.wallet = None # TODO: re-enable - assert tx is not None - if ctx.is_send_response is None or ctx.config is None: - assert ctx.is_send_response is None, "if either sendRequest or isSendResponse is defined, they must both be defined" - assert ctx.config is None, "if either sendRequest or isSendResponse is defined, they must both be defined" - - # test common field types - assert tx.hash is not None - assert tx.is_confirmed is not None - assert tx.is_miner_tx is not None - assert tx.is_failed is not None - assert tx.is_relayed is not None - assert tx.in_tx_pool is not None - assert tx.is_locked is not None - GenUtils.test_unsigned_big_integer(tx.fee) - if tx.payment_id is not None: - # default payment id converted to None - assert MoneroTxWallet.DEFAULT_PAYMENT_ID != tx.payment_id - if tx.note is not None: - # empty notes converted to undefined - assert len(tx.note) > 0 - - assert tx.unlock_time is not None - assert tx.unlock_time >= 0 - assert tx.size is None # TODO monero-wallet-rpc: add tx_size to get_transfers and get_transfer_by_txid - assert tx.received_timestamp is None # TODO monero-wallet-rpc: return received timestamp (asked to file issue if wanted) - - # test send tx - if ctx.is_send_response is True: - assert tx.weight is not None - assert tx.weight > 0 - assert len(tx.inputs) > 0 - for tx_input in tx.inputs: - assert tx_input.tx == tx - else: - assert tx.weight is None - assert len(tx.inputs) == 0 - - # test confirmed - if tx.is_confirmed: - assert tx.block is not None - assert tx in tx.block.txs - assert tx.block.height is not None - assert tx.block.height > 0 - assert tx.block.timestamp is not None - assert tx.block.timestamp > 0 - assert tx.relay is True - assert tx.is_relayed is True - assert tx.is_failed is False - assert tx.in_tx_pool is False - assert tx.is_double_spend_seen is False - assert tx.num_confirmations is not None - assert tx.num_confirmations > 0 - else: - assert tx.block is None - assert tx.num_confirmations is not None - assert tx.num_confirmations == 0 - - # test in tx pool - if tx.in_tx_pool: - assert tx.is_confirmed is False - assert tx.relay is True - assert tx.is_relayed is True - assert tx.is_double_spend_seen is False - assert tx.is_locked is True - - # these should be initialized unless a response from sending - # TODO re-enable when received timestamp returned in wallet rpc - #if ctx.is_send_response: - # assert tx.received_timestamp > 0 - else: - assert tx.last_relayed_timestamp is None - - # test miner tx - if tx.is_miner_tx: - assert tx.fee is not None - assert tx.fee == 0 - - # test failed - # TODO what else to test associated with failed - if tx.is_failed: - assert isinstance(tx.outgoing_transfer, MoneroTransfer) - # TODO re-enable when received timestamp returned in wallet rpc - #assert tx.received_timestamp > 0 - else: - if tx.is_relayed: - assert tx.is_double_spend_seen is False - else: - assert tx.relay is False - assert tx.is_relayed is False - assert tx.is_double_spend_seen is None - - assert tx.last_failed_height is None - assert tx.last_failed_hash is None - - # received time only for tx pool or failed txs - if tx.received_timestamp is not None: - assert tx.in_tx_pool or tx.is_failed - - # test relayed tx - if tx.is_relayed: - assert tx.relay is True - if tx.relay is False: - assert (not tx.is_relayed) is True - - # test outgoing transfer per configuration - if ctx.has_outgoing_transfer is False: - assert tx.outgoing_transfer is None - if ctx.has_destinations is True: - assert tx.outgoing_transfer is not None - assert len(tx.outgoing_transfer.destinations) > 0 - - # test outgoing transfer - if tx.outgoing_transfer is not None: - assert tx.is_outgoing is True - cls.test_transfer(tx.outgoing_transfer, ctx) - if ctx.is_sweep_response is True: - assert len(tx.outgoing_transfer.destinations) == 1, f"Expected 1 tx, got {len(tx.outgoing_transfer.destinations)}" - # TODO handle special cases - else: - assert len(tx.incoming_transfers) > 0 - assert tx.get_outgoing_amount() == 0 - assert tx.outgoing_transfer is None - assert tx.ring_size is None - assert tx.full_hex is None - assert tx.metadata is None - assert tx.key is None - - # test incoming transfers - if len(tx.incoming_transfers) > 0: - assert tx.is_incoming is True - GenUtils.test_unsigned_big_integer(tx.get_incoming_amount()) - assert tx.is_failed is False - - # test each transfer and collect transfer sum - transfer_sum: int = 0 - for transfer in tx.incoming_transfers: - cls.test_transfer(transfer, ctx) - assert transfer.amount is not None - transfer_sum += transfer.amount - if ctx.wallet is not None: - addr = ctx.wallet.get_address(transfer.account_index, transfer.subaddress_index) - assert transfer.address == addr - # TODO special case: transfer amount of 0 - - # incoming transfers add up to incoming tx amount - assert tx.get_incoming_amount() == transfer_sum - else: - assert tx.outgoing_transfer is not None - assert tx.get_incoming_amount() == 0 - assert len(tx.incoming_transfers) == 0 - - # test tx results from send or relay - if ctx.is_send_response is True: - # test tx set - assert tx.tx_set is not None - found: bool = False - for a_tx in tx.tx_set.txs: - if a_tx == tx: - found = True - break - - if ctx.is_copy is True: - assert found is False - else: - assert found - - # test common attributes - assert ctx.config is not None - config: MoneroTxConfig = ctx.config - assert tx.is_confirmed is False - cls.test_transfer(tx.outgoing_transfer, ctx) - assert tx.ring_size == MoneroUtils.get_ring_size() - assert tx.unlock_time == 0 - assert tx.block is None - assert tx.key is not None - assert len(tx.key) > 0 - assert tx.full_hex is not None - assert len(tx.full_hex) > 0 - assert tx.metadata is not None - assert tx.received_timestamp is None - assert tx.is_locked is True - - # get locked state - if tx.unlock_time == 0: - assert tx.is_confirmed == (not tx.is_locked) - else: - assert tx.is_locked is True - - # TODO implement is_locked - #for output in tx.get_outputs_wallet(): - # assert tx.is_locked == output.is_locked - - # test destinations of sent tx - assert tx.outgoing_transfer is not None - if len(tx.outgoing_transfer.destinations) == 0: - assert config.can_split is True - # TODO: remove this after >18.3.1 when amounts_by_dest_list official - logger.warning("Destinations not returned from split transactions") - else: - subtract_fee_from_dests: bool = len(config.subtract_fee_from) > 0 - if ctx.is_sweep_response is True: - dests: list[MoneroDestination] = config.get_normalized_destinations() - assert len(dests) == 1 - assert dests[0].amount is None - if not subtract_fee_from_dests: - assert tx.outgoing_transfer.amount == tx.outgoing_transfer.destinations[0].amount - - if config.relay is True: - # test relayed txs - assert tx.in_tx_pool is True - assert tx.relay is True - assert tx.is_relayed is True - assert tx.last_relayed_timestamp is not None - assert tx.last_relayed_timestamp > 0 - assert tx.is_double_spend_seen is False - else: - # test non-relayed txs - assert tx.in_tx_pool is False - assert tx.relay is False - assert tx.is_relayed is False - assert tx.last_relayed_timestamp is None - assert tx.is_double_spend_seen is None - - else: - # test tx result query - # tx set only initialized on send responses - assert tx.tx_set is None - assert tx.ring_size is None - assert tx.key is None - assert tx.full_hex is None - assert tx.metadata is None - assert tx.last_relayed_timestamp is None - - # test inputs - if tx.is_outgoing is True and ctx.is_send_response is True: - assert len(tx.inputs) > 0 - - for wallet_input in tx.get_inputs_wallet(): - cls.test_input_wallet(wallet_input) - - # test outputs - if tx.is_incoming is True and ctx.include_outputs is True: - if tx.is_confirmed is True: - assert len(tx.outputs) > 0 - else: - assert len(tx.outputs) == 0 - - for output in tx.get_outputs_wallet(): - cls.test_output_wallet(output) - - # TODO test deep copy - #if ctx.is_copy is not True: - # cls.test_tx_wallet_copy(tx, ctx) - - @classmethod - def test_txs_wallet(cls, txs: list[MoneroTxWallet], context: Optional[TxContext]) -> None: - """Test a list of transactions. - - :param list[MoneroTxWallet] txs: transactions to test. - :param TxContext | None context: test context. - """ - for tx in txs: - cls.test_tx_wallet(tx, context) - @classmethod def test_tx_copy(cls, tx: Optional[MoneroTx], context: Optional[TestContext]) -> None: """Test monero tx copy. @@ -664,12 +189,12 @@ def test_tx(cls, tx: Optional[MoneroTx], ctx: Optional[TestContext]) -> None: for tx_input in tx.inputs: assert tx == tx_input.tx - cls.test_input(tx_input, ctx) + OutputUtils.test_input(tx_input, ctx) assert len(tx.outputs) > 0 for output in tx.outputs: assert tx == output.tx - cls.test_output(output, ctx) + OutputUtils.test_output(output, ctx) # test pruned vs not pruned # tx might be pruned regardless of configuration @@ -752,592 +277,3 @@ def test_miner_tx(cls, miner_tx: Optional[MoneroTx]) -> None: # ctx.is_miner = true # ctx.from_get_tx_pool = true # cls.test_tx(miner_tx, ctx) - - @classmethod - def test_spend_tx(cls, spend_tx: Optional[MoneroTxWallet]) -> None: - """Test spend transaction. - - :param MoneroTxWallet | None spend_tx: Spend transaction to test. - """ - # validate spend tx - assert spend_tx is not None - assert len(spend_tx.inputs) > 0 - # validate tx inputs - for input_wallet in spend_tx.inputs: - assert input_wallet.key_image is not None - assert input_wallet.key_image.hex is not None - assert len(input_wallet.key_image.hex) > 0 - - @classmethod - def test_check_tx(cls, tx: Optional[MoneroTxWallet], check: MoneroCheckTx) -> None: - """Test tx with check result. - - :param MoneroTxWallet | None tx: transaction to test. - :param MoneroCheckTx check: transaction check result to test. - """ - assert tx is not None - assert check.is_good is not None - if check.is_good is True: - assert check.num_confirmations is not None - assert check.num_confirmations >= 0 - assert check.in_tx_pool is not None - GenUtils.test_unsigned_big_integer(check.received_amount) - if check.in_tx_pool is True: - assert check.num_confirmations == 0 - else: - # TODO (monero-wall-rpc) this fails (confirmations is 0) for (at least one) transaction - # that has 1 confirmation on test_check_tx_key() - assert check.num_confirmations > 0 - else: - assert check.in_tx_pool is None - assert check.num_confirmations is None - assert check.received_amount is None - - @classmethod - def test_check_reserve(cls, check: MoneroCheckReserve) -> None: - """Test wallet check reserve. - - :param MoneroCheckReserve check: reserve check to test. - """ - assert check.is_good is not None - if check.is_good is True: - assert check.total_amount is not None - GenUtils.test_unsigned_big_integer(check.total_amount) - assert check.total_amount >= 0 - - assert check.unconfirmed_spent_amount is not None - GenUtils.test_unsigned_big_integer(check.unconfirmed_spent_amount) - assert check.unconfirmed_spent_amount >= 0 - else: - 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: - """Test exception is invalid address. - - :param Exception ex: exception to test. - """ - msg: str = str(ex) - assert msg == "Invalid address", msg - - @classmethod - def test_invalid_tx_hash_error(cls, ex: Exception) -> None: - """Test exception is invalid hash format. - - :param Exception ex: exception to test. - """ - msg: str = str(ex) - assert msg == "TX hash has invalid format", msg - - @classmethod - def test_invalid_tx_key_error(cls, ex: Exception) -> None: - """Test exception is invalid key error. - - :param Exception ex: exception to test. - """ - msg: str = str(ex) - assert msg == "Tx key has invalid format", msg - - @classmethod - def test_invalid_signature_error(cls, ex: Exception) -> None: - """Test exception is invalid signature error. - - :param Exception ex: exception to test. - """ - msg: str = str(ex) - assert msg == "Signature size mismatch with additional tx pubkeys", msg - - @classmethod - def test_no_subaddress_error(cls, ex: Exception) -> None: - """Test exception is no subaddress error. - - :param Exception ex: exception to test. - """ - msg: str = str(ex) - assert msg == "Address must not be a subaddress", msg - - @classmethod - def test_signature_header_error(cls, ex: Exception) -> None: - """Test exception is signature header error. - - :param Exception ex: exception to test. - """ - msg: str = str(ex) - assert msg == "Signature header check error", msg - - @classmethod - def get_and_test_txs( - cls, - wallet: MoneroWallet, - query: Optional[MoneroTxQuery], - ctx: Optional[TxContext], - is_expected: Optional[bool], - regtest: bool - ) -> list[MoneroTxWallet]: - """Get and test txs from wallet. - - :param MoneroWallet wallet: wallet to get txs from. - :param MoneroTxQuery | None query: filter wallet txs by query if defined. - :param TxContext | None ctx: transaction context. - :param bool | None is_expected: expects empty/non-empty txs. - """ - copy: Optional[MoneroTxQuery] = query.copy() if query is not None else None - txs = wallet.get_txs(query) if query is not None else wallet.get_txs() - assert txs is not None - - if is_expected is False: - assert len(txs) == 0 - - if is_expected is True: - assert len(txs) > 0 - - cls.test_txs_wallet(txs, ctx) - cls.test_get_txs_structure(txs, query, regtest) - - if query is not None: - AssertUtils.assert_equals(copy, query) - - return txs - - @classmethod - def get_and_test_transfers( - cls, - wallet: MoneroWallet, - query: Optional[MoneroTransferQuery], - ctx: Optional[TxContext], - is_expected: Optional[bool] - ) -> list[MoneroTransfer]: - """Get and test transfers from wallet. - - :param MoneroWallet wallet: wallet to get transfers from. - :param MoneroTransferQuery | None query: filter wallet transfers by query if defined. - :param TxContext | None ctx: transaction context. - :param bool | None is_expected: expects empty/non-empty transfers. - """ - copy: Optional[MoneroTransferQuery] = query.copy() if query is not None else None - transfers = wallet.get_transfers(query) if query is not None else wallet.get_transfers(MoneroTransferQuery()) - - if is_expected is False: - assert len(transfers) == 0 - elif is_expected is True: - assert len(transfers) > 0, "Transfers were expected but not found; run send tests?" - - if ctx is None: - ctx = TxContext() - - ctx.wallet = wallet - for transfer in transfers: - TxUtils.test_tx_wallet(transfer.tx, ctx) - - if query is not None: - AssertUtils.assert_equals(copy, query) - - return transfers - - @classmethod - def get_and_test_outputs(cls, wallet: MoneroWallet, query: Optional[MoneroOutputQuery], is_expected: Optional[bool]) -> list[MoneroOutputWallet]: - """Fetches and tests wallet outputs (i.e. wallet tx outputs) according to the given query. - - :param MoneroWallet wallet: wallet to get outputs from. - :param MoneroOutputQuery | None query: output query. - :param bool | None is_expected: expected non-empty outputs. - """ - - copy = query.copy() if query is not None else None - outputs = wallet.get_outputs(query) if query is not None else wallet.get_outputs(MoneroOutputQuery()) - AssertUtils.assert_equals(copy, query) - - if is_expected is False: - assert len(outputs) == 0 - elif is_expected is True: - assert len(outputs) > 0, "Outputs were expected but not found; run send tests" - - for output in outputs: - TxUtils.test_output_wallet(output) - - return outputs - - @classmethod - def test_scan_txs(cls, wallet: MoneroWallet, scan_wallet: MoneroWallet) -> None: - """Test wallet transaction scan. - - :param MoneroWallet wallet: original wallet to test. - :param MoneroWallet scan_wallet: scan wallet to test. - """ - # get a few tx hashes - tx_hashes: list[str] = [] - txs: list[MoneroTxWallet] = wallet.get_txs() - assert len(txs) > 2, "Not enough txs to scan" - for i in range(1, 3): - tx_hash = txs[i].hash - assert tx_hash is not None - tx_hashes.append(tx_hash) - - # start wallet without scanning - # TODO create wallet without daemon connection (offline does not reconnect, default connects to localhost, - # offline then online causes confirmed txs to disappear) - scan_wallet.stop_syncing() - assert scan_wallet.is_connected_to_daemon() - - # scan txs - scan_wallet.scan_txs(tx_hashes) - - # TODO scanning txs causes merge problems reconciling 0 fee, is_miner_tx with test txs - - # close wallet - scan_wallet.close(False) - - @classmethod - def is_tx_in_block(cls, tx: MoneroTxWallet, block: MoneroBlock) -> bool: - """Check if transaction is included in block. - - :param MoneroTxWallet tx: tx to check if included in block. - :param MoneroBlock block: block to check if `tx` is included in. - """ - for block_tx in block.txs: - if block_tx.hash == tx.hash: - return True - - return False - - @classmethod - def test_get_txs_structure(cls, txs: list[MoneroTxWallet], q: Optional[MoneroTxQuery], regtest: bool) -> None: - """ - Tests the integrity of the full structure in the given txs from the block down - to transfers / destinations. - - :param list[MoneroTxWallet] txs: list of txs to get structure from. - :param MoneroTxQuery | None q: filter txs by query, if set. - :param bool regtest: indicates if running test on regtest network. - """ - query = q if q is not None else MoneroTxQuery() - # collect unique blocks in order - seen_blocks: set[MoneroBlock] = set() - blocks: list[MoneroBlock] = [] - unconfirmed_txs: list[MoneroTxWallet] = [] - - for tx in txs: - if tx.block is None: - unconfirmed_txs.append(tx) - else: - assert cls.is_tx_in_block(tx, tx.block) - if tx.block not in seen_blocks: - seen_blocks.add(tx.block) - blocks.append(tx.block) - - # tx hashes must be in order if requested - if len(query.hashes) > 0: - assert len(txs) == len(query.hashes) - for i, query_hash in enumerate(query.hashes): - assert query_hash == txs[i].hash - - # test that txs and blocks reference each other and blocks are in ascending order unless specific tx hashes queried - index: int = 0 - prev_block_height: Optional[int] = None - for block in blocks: - if prev_block_height is None: - prev_block_height = block.height - elif len(query.hashes) == 0: - assert block.height is not None - msg = f"Blocks are not in order of heights: {prev_block_height} vs {block.height}" - assert block.height > prev_block_height, msg - - for tx in block.txs: - assert tx.block == block - if len(query.hashes) == 0: - other = txs[index] - if not regtest: - assert other.hash == tx.hash, "Txs in block are not in order" - # verify tx order is self-consistent with blocks unless txs manually re-ordered by querying by hash - assert other == tx - else: - # TODO regtest wallet2 has inconsinstent txs order betwenn - assert other in block.txs, "Tx not found in block" - - index += 1 - - assert len(txs) == index + len(unconfirmed_txs), f"txs: {len(txs)}, unconfirmed txs: {len(unconfirmed_txs)}, index: {index}" - - # test that incoming transfers are in order of ascending accounts and subaddresses - for tx in txs: - if len(tx.incoming_transfers) == 0: - continue - - prev_account_idx: Optional[int] = None - prev_subaddress_idx: Optional[int] = None - for transfer in tx.incoming_transfers: - if prev_account_idx is None: - prev_account_idx = transfer.account_index - - else: - assert prev_account_idx is not None - assert transfer.account_index is not None - assert prev_account_idx <= transfer.account_index - if prev_account_idx < transfer.account_index: - prev_subaddress_idx = None - prev_account_idx = transfer.account_index - if prev_subaddress_idx is None: - prev_subaddress_idx = transfer.subaddress_index - else: - assert transfer.subaddress_index is not None - assert prev_subaddress_idx < transfer.subaddress_index - - # test that outputs are in order of ascending accounts and subaddresses - for tx in txs: - if len(tx.outputs) == 0: - continue - - prev_account_idx: Optional[int] = None - prev_subaddress_idx: Optional[int] = None - for output in tx.get_outputs_wallet(): - if prev_account_idx is None: - prev_account_idx = output.account_index - else: - assert output.account_index is not None - assert prev_account_idx <= output.account_index - if prev_account_idx < output.account_index: - prev_subaddress_idx = None - prev_account_idx = output.account_index - if prev_subaddress_idx is None: - prev_subaddress_idx = output.subaddress_index - else: - assert prev_subaddress_idx is not None - assert output.subaddress_index is not None - # TODO: this does not test that index < other index if subaddresses are equal - assert prev_subaddress_idx <= output.subaddress_index - - @classmethod - def get_random_transactions( - cls, - wallet: MoneroWallet, - query: Optional[MoneroTxQuery] = None, - min_txs: Optional[int] = None, - max_txs: Optional[int] = None - ) -> list[MoneroTxWallet]: - """Get random transaction from wallet. - - :param Wallet wallet: wallet to get random txs from. - :param MoneroTxQuery | None: filter txs by query (default `None`). - :param int | None min_txs: minimum number of txs to get (default `None`). - :param int | None max_txs: maximum number of txs to get (default `None`). - """ - txs = wallet.get_txs(query if query is not None else MoneroTxQuery()) - - if min_txs is not None: - assert len(txs) >= min_txs, f"{len(txs)}/{min_txs} transactions found with the query" - - shuffle(txs) - - if max_txs is None: - return txs - - result: list[MoneroTxWallet] = [] - - for i, tx in enumerate(txs): - result.append(tx) - if i >= max_txs - 1: - break - - return result - - @classmethod - def get_confirmed_tx_hashes(cls, daemon: MoneroDaemon) -> list[str]: - """Get confirmed tx hashes from daemon from last 5 blocks. - - :param MoneroDaemon daemon: daemon instance to get confirmed txs from. - :returns list[str]: confirmed txs hashes. - """ - hashes: list[str] = [] - height: int = daemon.get_height() - while len(hashes) < 5 and height > 0: - height -= 1 - block = daemon.get_block_by_height(height) - for tx_hash in block.tx_hashes: - hashes.append(tx_hash) - return hashes - - @classmethod - def get_unrelayed_tx(cls, wallet: MoneroWallet, account_idx: int) -> MoneroTxWallet: - """Get unrelayed tx from wallet account. - - :param MoneroWallet wallet: wallet to get unrelayed tx from. - :param int account_idx: wallet account index to get unrelayed tx from. - :returns MoneroTxWallet: unrealyed wallet tx. - """ - # TODO monero-project - assert account_idx > 0, "Txs sent from/to same account are not properly synced from the pool" - config = MoneroTxConfig() - config.account_index = account_idx - config.address = wallet.get_primary_address() - config.amount = cls.MAX_FEE - - tx = wallet.create_tx(config) - assert (tx.full_hex is None or tx.full_hex == "") is False - assert tx.relay is False, f"Expected tx.relay to be False, got {tx.relay}" - return tx - - @classmethod - def test_common_tx_sets(cls, txs: list[MoneroTxWallet], has_signed: bool, has_unsigned: bool, has_multisig: bool) -> None: - """Test common tx set in txs. - - :param list[MoneroTxWallet] txs: txs to test common sets. - :param bool has_signed: expects signed tx hex to be defined in tx set. - :param bool has_unsigned: expects unsigned tx hex to be defined in tx set. - :param bool has_multisig: expects multisig tx hex to be defined in tx set. - """ - assert len(txs) > 0 - # assert that all sets are same reference - tx_set: Optional[MoneroTxSet] = None - for i, tx in enumerate(txs): - assert isinstance(tx, MoneroTxWallet) - if i == 0: - tx_set = tx.tx_set - else: - assert tx.tx_set == tx_set - - # test expected set - assert tx_set is not None - - if has_signed: - # check signed tx hex - assert tx_set.signed_tx_hex is not None - assert len(tx_set.signed_tx_hex) > 0 - - if has_unsigned: - # check unsigned tx hex - assert tx_set.unsigned_tx_hex is not None - assert len(tx_set.unsigned_tx_hex) > 0 - - if has_multisig: - # check multisign tx hex - assert tx_set.multisig_tx_hex is not None - assert len(tx_set.multisig_tx_hex) > 0 - - @classmethod - def set_block_copy(cls, copy: MoneroTxWallet, tx: MoneroTxWallet) -> None: - """ - Replace tx block reference with copy. - - :param MoneroTxWallet copy: copy transaction reference. - :param MoneroTxWallet tx: original transaction reference. - """ - # skip unconfirmed tx - if copy.is_confirmed is not True: - return - - # copy block - assert tx.block is not None - block = tx.block.copy() - - # set copy tx in block copy - block.txs = [copy] - - # set block copy reference to tx copy - copy.block = block - - @classmethod - def txs_mergeable(cls, tx1: MoneroTxWallet, tx2: MoneroTxWallet) -> bool: - """Check if txs are mergeable. - - :param MoneroTxWallet tx1: first tx to check merge with. - :param MoneroTxWallet tx2: second tx to check merge with. - :returns bool: `True` if txs are mergeable, `False` otherwise. - """ - try: - # copy txs - copy1 = tx1.copy() - copy2 = tx2.copy() - # set block copies - cls.set_block_copy(copy1, tx1) - cls.set_block_copy(copy2, tx2) - # test merge - copy1.merge(copy2) - return True - except Exception as e: - # merge failed - logger.warning(f"Txs are not mergeable: {e}") - return False - - @classmethod - def assert_list_txs_equals(cls, txs1: list[MoneroTxWallet], txs2: list[MoneroTxWallet], check_order: bool = False) -> None: - """Checks txs lists equality. - - :param list[MoneroTxWallet] txs1: first list to check equality. - :param list[MoneroTxWallet] txs2: second list to check equality. - :param bool check_order: check also order (default `False`). - """ - # assert lists have same size - assert len(txs1) == len(txs2), "Txs lists count doesn't equal" - - if check_order: - # check lists have same objects and order - AssertUtils.assert_list_equals(txs1, txs2, "Txs lists doesn't equal") - return - - # collect tx hashes - tx_hashes1: list[str] = [] - tx_hashes2: list[str] = [] - - for i, tx1 in enumerate(txs1): - tx2: MoneroTxWallet = txs2[i] - assert tx1.hash is not None - assert tx2.hash is not None - tx_hashes1.append(tx1.hash) - tx_hashes2.append(tx2.hash) - - # assert that lists have same hashes - for tx_hash1 in tx_hashes1: - assert tx_hash1 in tx_hashes2 - - @classmethod - def remove_txs(cls, txs: list[MoneroTxWallet], to_remove: set[MoneroTxWallet]) -> None: - """Removes a set of txs from a list. - - :param list[MoneroTxWallet] txs: txs list. - :param set[MoneroTxWallet] to_remove: set of txs to remove from list. - """ - for tx_to_remove in to_remove: - txs.remove(tx_to_remove) diff --git a/tests/utils/tx_wallet_utils.py b/tests/utils/tx_wallet_utils.py new file mode 100644 index 0000000..6ed1b66 --- /dev/null +++ b/tests/utils/tx_wallet_utils.py @@ -0,0 +1,625 @@ +import logging + +from abc import ABC +from typing import Optional + + +from monero import ( + MoneroTransfer, MoneroTxWallet, + MoneroTxConfig, MoneroUtils, + MoneroDestination, MoneroTxSet, + MoneroTxQuery, MoneroBlock, + MoneroNetworkType, MoneroCheckTx, + MoneroCheckReserve +) + +from .assert_utils import AssertUtils +from .gen_utils import GenUtils +from .context import TxContext +from .block_utils import BlockUtils +from .output_utils import OutputUtils +from .transfer_utils import TransferUtils + + +logger: logging.Logger = logging.getLogger("TxWalletUtils") + + +class TxWalletUtils(ABC): + + MAX_FEE: int = 7500000*10000 + """Max tx fee.""" + + @classmethod + def test_tx_wallet(cls, tx: Optional[MoneroTxWallet], context: Optional[TxContext] = None) -> None: + """Test monero tx wallet. + + :param MoneroTxWallet | None tx: wallet transaction to test. + :param TxContext | None context: test context (default `None`). + """ + # validate / sanitize inputs + ctx = TxContext(context) + ctx.wallet = None # TODO: re-enable + assert tx is not None + if ctx.is_send_response is None or ctx.config is None: + assert ctx.is_send_response is None, "if either sendRequest or isSendResponse is defined, they must both be defined" + assert ctx.config is None, "if either sendRequest or isSendResponse is defined, they must both be defined" + + # test common field types + assert tx.hash is not None + assert tx.is_confirmed is not None + assert tx.is_miner_tx is not None + assert tx.is_failed is not None + assert tx.is_relayed is not None + assert tx.in_tx_pool is not None + assert tx.is_locked is not None + GenUtils.test_unsigned_big_integer(tx.fee) + if tx.payment_id is not None: + # default payment id converted to None + assert MoneroTxWallet.DEFAULT_PAYMENT_ID != tx.payment_id + if tx.note is not None: + # empty notes converted to undefined + assert len(tx.note) > 0 + + assert tx.unlock_time is not None + assert tx.unlock_time >= 0 + assert tx.size is None # TODO monero-wallet-rpc: add tx_size to get_transfers and get_transfer_by_txid + assert tx.received_timestamp is None # TODO monero-wallet-rpc: return received timestamp (asked to file issue if wanted) + + # test send tx + if ctx.is_send_response is True: + assert tx.weight is not None + assert tx.weight > 0 + assert len(tx.inputs) > 0 + for tx_input in tx.inputs: + assert tx_input.tx == tx + else: + assert tx.weight is None + assert len(tx.inputs) == 0 + + # test confirmed + if tx.is_confirmed: + assert tx.block is not None + assert tx in tx.block.txs + assert tx.block.height is not None + assert tx.block.height > 0 + assert tx.block.timestamp is not None + assert tx.block.timestamp > 0 + assert tx.relay is True + assert tx.is_relayed is True + assert tx.is_failed is False + assert tx.in_tx_pool is False + assert tx.is_double_spend_seen is False + assert tx.num_confirmations is not None + assert tx.num_confirmations > 0 + else: + assert tx.block is None + assert tx.num_confirmations is not None + assert tx.num_confirmations == 0 + + # test in tx pool + if tx.in_tx_pool: + assert tx.is_confirmed is False + assert tx.relay is True + assert tx.is_relayed is True + assert tx.is_double_spend_seen is False + assert tx.is_locked is True + + # these should be initialized unless a response from sending + # TODO re-enable when received timestamp returned in wallet rpc + #if ctx.is_send_response: + # assert tx.received_timestamp > 0 + else: + assert tx.last_relayed_timestamp is None + + # test miner tx + if tx.is_miner_tx: + assert tx.fee is not None + assert tx.fee == 0 + + # test failed + # TODO what else to test associated with failed + if tx.is_failed: + assert isinstance(tx.outgoing_transfer, MoneroTransfer) + # TODO re-enable when received timestamp returned in wallet rpc + #assert tx.received_timestamp > 0 + else: + if tx.is_relayed: + assert tx.is_double_spend_seen is False + else: + assert tx.relay is False + assert tx.is_relayed is False + assert tx.is_double_spend_seen is None + + assert tx.last_failed_height is None + assert tx.last_failed_hash is None + + # received time only for tx pool or failed txs + if tx.received_timestamp is not None: + assert tx.in_tx_pool or tx.is_failed + + # test relayed tx + if tx.is_relayed: + assert tx.relay is True + if tx.relay is False: + assert (not tx.is_relayed) is True + + # test outgoing transfer per configuration + if ctx.has_outgoing_transfer is False: + assert tx.outgoing_transfer is None + if ctx.has_destinations is True: + assert tx.outgoing_transfer is not None + assert len(tx.outgoing_transfer.destinations) > 0 + + # test outgoing transfer + if tx.outgoing_transfer is not None: + assert tx.is_outgoing is True + TransferUtils.test_transfer(tx.outgoing_transfer, ctx) + if ctx.is_sweep_response is True: + assert len(tx.outgoing_transfer.destinations) == 1, f"Expected 1 tx, got {len(tx.outgoing_transfer.destinations)}" + # TODO handle special cases + else: + assert len(tx.incoming_transfers) > 0 + assert tx.get_outgoing_amount() == 0 + assert tx.outgoing_transfer is None + assert tx.ring_size is None + assert tx.full_hex is None + assert tx.metadata is None + assert tx.key is None + + # test incoming transfers + if len(tx.incoming_transfers) > 0: + assert tx.is_incoming is True + GenUtils.test_unsigned_big_integer(tx.get_incoming_amount()) + assert tx.is_failed is False + + # test each transfer and collect transfer sum + transfer_sum: int = 0 + for transfer in tx.incoming_transfers: + TransferUtils.test_transfer(transfer, ctx) + assert transfer.amount is not None + transfer_sum += transfer.amount + if ctx.wallet is not None: + addr = ctx.wallet.get_address(transfer.account_index, transfer.subaddress_index) + assert transfer.address == addr + # TODO special case: transfer amount of 0 + + # incoming transfers add up to incoming tx amount + assert tx.get_incoming_amount() == transfer_sum + else: + assert tx.outgoing_transfer is not None + assert tx.get_incoming_amount() == 0 + assert len(tx.incoming_transfers) == 0 + + # test tx results from send or relay + if ctx.is_send_response is True: + # test tx set + assert tx.tx_set is not None + found: bool = False + for a_tx in tx.tx_set.txs: + if a_tx == tx: + found = True + break + + if ctx.is_copy is True: + assert found is False + else: + assert found + + # test common attributes + assert ctx.config is not None + config: MoneroTxConfig = ctx.config + assert tx.is_confirmed is False + TransferUtils.test_transfer(tx.outgoing_transfer, ctx) + assert tx.ring_size == MoneroUtils.get_ring_size() + assert tx.unlock_time == 0 + assert tx.block is None + assert tx.key is not None + assert len(tx.key) > 0 + assert tx.full_hex is not None + assert len(tx.full_hex) > 0 + assert tx.metadata is not None + assert tx.received_timestamp is None + assert tx.is_locked is True + + # get locked state + if tx.unlock_time == 0: + assert tx.is_confirmed == (not tx.is_locked) + else: + assert tx.is_locked is True + + # TODO implement is_locked + #for output in tx.get_outputs_wallet(): + # assert tx.is_locked == output.is_locked + + # test destinations of sent tx + assert tx.outgoing_transfer is not None + if len(tx.outgoing_transfer.destinations) == 0: + assert config.can_split is True + # TODO: remove this after >18.3.1 when amounts_by_dest_list official + logger.warning("Destinations not returned from split transactions") + else: + subtract_fee_from_dests: bool = len(config.subtract_fee_from) > 0 + if ctx.is_sweep_response is True: + dests: list[MoneroDestination] = config.get_normalized_destinations() + assert len(dests) == 1 + assert dests[0].amount is None + if not subtract_fee_from_dests: + assert tx.outgoing_transfer.amount == tx.outgoing_transfer.destinations[0].amount + + if config.relay is True: + # test relayed txs + assert tx.in_tx_pool is True + assert tx.relay is True + assert tx.is_relayed is True + assert tx.last_relayed_timestamp is not None + assert tx.last_relayed_timestamp > 0 + assert tx.is_double_spend_seen is False + else: + # test non-relayed txs + assert tx.in_tx_pool is False + assert tx.relay is False + assert tx.is_relayed is False + assert tx.last_relayed_timestamp is None + assert tx.is_double_spend_seen is None + + else: + # test tx result query + # tx set only initialized on send responses + assert tx.tx_set is None + assert tx.ring_size is None + assert tx.key is None + assert tx.full_hex is None + assert tx.metadata is None + assert tx.last_relayed_timestamp is None + + # test inputs + if tx.is_outgoing is True and ctx.is_send_response is True: + assert len(tx.inputs) > 0 + + for wallet_input in tx.get_inputs_wallet(): + OutputUtils.test_input_wallet(wallet_input) + + # test outputs + if tx.is_incoming is True and ctx.include_outputs is True: + if tx.is_confirmed is True: + assert len(tx.outputs) > 0 + else: + assert len(tx.outputs) == 0 + + for output in tx.get_outputs_wallet(): + OutputUtils.test_output_wallet(output) + + # TODO test deep copy + #if ctx.is_copy is not True: + # cls.test_tx_wallet_copy(tx, ctx) + + @classmethod + def test_txs_wallet(cls, txs: list[MoneroTxWallet], context: Optional[TxContext]) -> None: + """Test a list of transactions. + + :param list[MoneroTxWallet] txs: transactions to test. + :param TxContext | None context: test context. + """ + for tx in txs: + cls.test_tx_wallet(tx, context) + + @classmethod + def test_described_tx_set(cls, described_tx_set: MoneroTxSet, network_type: MoneroNetworkType) -> None: + """Test described tx set. + + :param MoneroTxSet described_tx_set: described tx set to test. + :param MoneroNetworkType network_type: tx set network type. + """ + 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, 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: + TransferUtils.test_destination(destination) + + @classmethod + def test_get_txs_structure(cls, txs: list[MoneroTxWallet], q: Optional[MoneroTxQuery], regtest: bool) -> None: + """ + Tests the integrity of the full structure in the given txs from the block down + to transfers / destinations. + + :param list[MoneroTxWallet] txs: list of txs to get structure from. + :param MoneroTxQuery | None q: filter txs by query, if set. + :param bool regtest: indicates if running test on regtest network. + """ + query = q if q is not None else MoneroTxQuery() + # collect unique blocks in order + seen_blocks: set[MoneroBlock] = set() + blocks: list[MoneroBlock] = [] + unconfirmed_txs: list[MoneroTxWallet] = [] + + for tx in txs: + if tx.block is None: + unconfirmed_txs.append(tx) + else: + assert BlockUtils.is_tx_in_block(tx.hash, tx.block) + if tx.block not in seen_blocks: + seen_blocks.add(tx.block) + blocks.append(tx.block) + + # tx hashes must be in order if requested + if len(query.hashes) > 0: + assert len(txs) == len(query.hashes) + for i, query_hash in enumerate(query.hashes): + assert query_hash == txs[i].hash + + # test that txs and blocks reference each other and blocks are in ascending order unless specific tx hashes queried + index: int = 0 + prev_block_height: Optional[int] = None + for block in blocks: + if prev_block_height is None: + prev_block_height = block.height + elif len(query.hashes) == 0: + assert block.height is not None + msg = f"Blocks are not in order of heights: {prev_block_height} vs {block.height}" + assert block.height > prev_block_height, msg + + for tx in block.txs: + assert tx.block == block + if len(query.hashes) == 0: + other = txs[index] + if not regtest: + assert other.hash == tx.hash, "Txs in block are not in order" + # verify tx order is self-consistent with blocks unless txs manually re-ordered by querying by hash + assert other == tx + else: + # TODO regtest wallet2 has inconsinstent txs order betwenn + assert other in block.txs, "Tx not found in block" + + index += 1 + + assert len(txs) == index + len(unconfirmed_txs), f"txs: {len(txs)}, unconfirmed txs: {len(unconfirmed_txs)}, index: {index}" + + # test that incoming transfers are in order of ascending accounts and subaddresses + for tx in txs: + if len(tx.incoming_transfers) == 0: + continue + + prev_account_idx: Optional[int] = None + prev_subaddress_idx: Optional[int] = None + for transfer in tx.incoming_transfers: + if prev_account_idx is None: + prev_account_idx = transfer.account_index + + else: + assert prev_account_idx is not None + assert transfer.account_index is not None + assert prev_account_idx <= transfer.account_index + if prev_account_idx < transfer.account_index: + prev_subaddress_idx = None + prev_account_idx = transfer.account_index + if prev_subaddress_idx is None: + prev_subaddress_idx = transfer.subaddress_index + else: + assert transfer.subaddress_index is not None + assert prev_subaddress_idx < transfer.subaddress_index + + # test that outputs are in order of ascending accounts and subaddresses + for tx in txs: + if len(tx.outputs) == 0: + continue + + prev_account_idx: Optional[int] = None + prev_subaddress_idx: Optional[int] = None + for output in tx.get_outputs_wallet(): + if prev_account_idx is None: + prev_account_idx = output.account_index + else: + assert output.account_index is not None + assert prev_account_idx <= output.account_index + if prev_account_idx < output.account_index: + prev_subaddress_idx = None + prev_account_idx = output.account_index + if prev_subaddress_idx is None: + prev_subaddress_idx = output.subaddress_index + else: + assert prev_subaddress_idx is not None + assert output.subaddress_index is not None + # TODO: this does not test that index < other index if subaddresses are equal + assert prev_subaddress_idx <= output.subaddress_index + + @classmethod + def test_common_tx_sets(cls, txs: list[MoneroTxWallet], has_signed: bool, has_unsigned: bool, has_multisig: bool) -> None: + """Test common tx set in txs. + + :param list[MoneroTxWallet] txs: txs to test common sets. + :param bool has_signed: expects signed tx hex to be defined in tx set. + :param bool has_unsigned: expects unsigned tx hex to be defined in tx set. + :param bool has_multisig: expects multisig tx hex to be defined in tx set. + """ + assert len(txs) > 0 + # assert that all sets are same reference + tx_set: Optional[MoneroTxSet] = None + for i, tx in enumerate(txs): + assert isinstance(tx, MoneroTxWallet) + if i == 0: + tx_set = tx.tx_set + else: + assert tx.tx_set == tx_set + + # test expected set + assert tx_set is not None + + if has_signed: + # check signed tx hex + assert tx_set.signed_tx_hex is not None + assert len(tx_set.signed_tx_hex) > 0 + + if has_unsigned: + # check unsigned tx hex + assert tx_set.unsigned_tx_hex is not None + assert len(tx_set.unsigned_tx_hex) > 0 + + if has_multisig: + # check multisign tx hex + assert tx_set.multisig_tx_hex is not None + assert len(tx_set.multisig_tx_hex) > 0 + + @classmethod + def test_spend_tx(cls, spend_tx: Optional[MoneroTxWallet]) -> None: + """Test spend transaction. + + :param MoneroTxWallet | None spend_tx: Spend transaction to test. + """ + # validate spend tx + assert spend_tx is not None + assert len(spend_tx.inputs) > 0 + # validate tx inputs + for input_wallet in spend_tx.inputs: + assert input_wallet.key_image is not None + assert input_wallet.key_image.hex is not None + assert len(input_wallet.key_image.hex) > 0 + + @classmethod + def test_check_tx(cls, tx: Optional[MoneroTxWallet], check: MoneroCheckTx) -> None: + """Test tx with check result. + + :param MoneroTxWallet | None tx: transaction to test. + :param MoneroCheckTx check: transaction check result to test. + """ + assert tx is not None + assert check.is_good is not None + if check.is_good is True: + assert check.num_confirmations is not None + assert check.num_confirmations >= 0 + assert check.in_tx_pool is not None + GenUtils.test_unsigned_big_integer(check.received_amount) + if check.in_tx_pool is True: + assert check.num_confirmations == 0 + else: + # TODO (monero-wall-rpc) this fails (confirmations is 0) for (at least one) transaction + # that has 1 confirmation on test_check_tx_key() + assert check.num_confirmations > 0 + else: + assert check.in_tx_pool is None + assert check.num_confirmations is None + assert check.received_amount is None + + @classmethod + def test_check_reserve(cls, check: MoneroCheckReserve) -> None: + """Test wallet check reserve. + + :param MoneroCheckReserve check: reserve check to test. + """ + assert check.is_good is not None + if check.is_good is True: + assert check.total_amount is not None + GenUtils.test_unsigned_big_integer(check.total_amount) + assert check.total_amount >= 0 + + assert check.unconfirmed_spent_amount is not None + GenUtils.test_unsigned_big_integer(check.unconfirmed_spent_amount) + assert check.unconfirmed_spent_amount >= 0 + else: + assert check.total_amount is None + assert check.unconfirmed_spent_amount is None + + @classmethod + def set_block_copy(cls, copy: MoneroTxWallet, tx: MoneroTxWallet) -> None: + """ + Replace tx block reference with copy. + + :param MoneroTxWallet copy: copy transaction reference. + :param MoneroTxWallet tx: original transaction reference. + """ + # skip unconfirmed tx + if copy.is_confirmed is not True: + return + + # copy block + assert tx.block is not None + block = tx.block.copy() + + # set copy tx in block copy + block.txs = [copy] + + # set block copy reference to tx copy + copy.block = block + + @classmethod + def txs_mergeable(cls, tx1: MoneroTxWallet, tx2: MoneroTxWallet) -> bool: + """Check if txs are mergeable. + + :param MoneroTxWallet tx1: first tx to check merge with. + :param MoneroTxWallet tx2: second tx to check merge with. + :returns bool: `True` if txs are mergeable, `False` otherwise. + """ + try: + # copy txs + copy1 = tx1.copy() + copy2 = tx2.copy() + # set block copies + cls.set_block_copy(copy1, tx1) + cls.set_block_copy(copy2, tx2) + # test merge + copy1.merge(copy2) + return True + except Exception as e: + # merge failed + logger.warning(f"Txs are not mergeable: {e}") + return False + + @classmethod + def assert_list_txs_equals(cls, txs1: list[MoneroTxWallet], txs2: list[MoneroTxWallet], check_order: bool = False) -> None: + """Checks txs lists equality. + + :param list[MoneroTxWallet] txs1: first list to check equality. + :param list[MoneroTxWallet] txs2: second list to check equality. + :param bool check_order: check also order (default `False`). + """ + # assert lists have same size + assert len(txs1) == len(txs2), "Txs lists count doesn't equal" + + if check_order: + # check lists have same objects and order + AssertUtils.assert_list_equals(txs1, txs2, "Txs lists doesn't equal") + return + + # collect tx hashes + tx_hashes1: list[str] = [] + tx_hashes2: list[str] = [] + + for i, tx1 in enumerate(txs1): + tx2: MoneroTxWallet = txs2[i] + assert tx1.hash is not None + assert tx2.hash is not None + tx_hashes1.append(tx1.hash) + tx_hashes2.append(tx2.hash) + + # assert that lists have same hashes + for tx_hash1 in tx_hashes1: + assert tx_hash1 in tx_hashes2 diff --git a/tests/utils/view_only_and_offline_wallet_tester.py b/tests/utils/view_only_and_offline_wallet_tester.py index a04bd7d..15ff1b7 100644 --- a/tests/utils/view_only_and_offline_wallet_tester.py +++ b/tests/utils/view_only_and_offline_wallet_tester.py @@ -4,7 +4,7 @@ MoneroKeyImage, MoneroTxConfig, MoneroTxSet ) from .test_utils import TestUtils -from .tx_utils import TxUtils +from .tx_wallet_utils import TxWalletUtils class ViewOnlyAndOfflineWalletTester: @@ -34,7 +34,7 @@ def _setup(self) -> None: """Setup tester.""" # wait for txs to confirm and for sufficient unlocked balance TestUtils.WALLET_TX_TRACKER.wait_for_txs_to_clear_pool([self._wallet, self._view_only_wallet]) - TestUtils.WALLET_TX_TRACKER.wait_for_unlocked_balance(self._wallet, 0, None, TxUtils.MAX_FEE * 4) + TestUtils.WALLET_TX_TRACKER.wait_for_unlocked_balance(self._wallet, 0, None, TxWalletUtils.MAX_FEE * 4) # test getting txs, transfers, and outputs from view-only wallet txs: list[MoneroTxWallet] = self._view_only_wallet.get_txs() @@ -132,7 +132,7 @@ def test(self) -> None: tx_config: MoneroTxConfig = MoneroTxConfig() tx_config.account_index = 0 tx_config.address = primary_address - tx_config.amount = TxUtils.MAX_FEE * 3 + tx_config.amount = TxWalletUtils.MAX_FEE * 3 unsigned_tx: MoneroTxWallet = self._view_only_wallet.create_tx(tx_config) assert unsigned_tx.tx_set is not None assert unsigned_tx.tx_set.unsigned_tx_hex is not None @@ -148,7 +148,7 @@ def test(self) -> None: # parse or "describe" unsigned tx set described_tx_set: MoneroTxSet = self._offline_wallet.describe_unsigned_tx_set(unsigned_tx.tx_set.unsigned_tx_hex) - TxUtils.test_described_tx_set(described_tx_set) + TxWalletUtils.test_described_tx_set(described_tx_set, TestUtils.NETWORK_TYPE) # submit signed tx using view-only wallet if TestUtils.TEST_RELAYS: diff --git a/tests/utils/wallet_equality_utils.py b/tests/utils/wallet_equality_utils.py index 462bb84..a913000 100644 --- a/tests/utils/wallet_equality_utils.py +++ b/tests/utils/wallet_equality_utils.py @@ -13,7 +13,7 @@ from .gen_utils import GenUtils from .assert_utils import AssertUtils -from .tx_utils import TxUtils +from .tx_wallet_utils import TxWalletUtils from .test_utils import TestUtils as Utils logger: logging.Logger = logging.getLogger("WalletEqualityUtils") @@ -138,10 +138,10 @@ def test_account_equal_on_chain(cls, account1: MoneroAccount, account2: MoneroAc @classmethod def test_subaddresses_equal_on_chain( - cls, - subaddresses1: list[MoneroSubaddress], - subaddresses2: list[MoneroSubaddress] - ) -> None: + cls, + subaddresses1: list[MoneroSubaddress], + subaddresses2: list[MoneroSubaddress] + ) -> None: """Test subaddresses equality based on on-chain data. :param list[MoneroSubaddress] subaddresses1: first subaddress list to compare on-chain data. @@ -189,21 +189,8 @@ def test_tx_wallets_equal_on_chain(cls, txs_1: list[MoneroTxWallet], txs_2: list :param list[MoneroTxWallet] txs_2: second wallet tx list to compare on-chain data. """ # remove pool or failed txs for comparison - txs1: list[MoneroTxWallet] = txs_1.copy() - to_remove: set[MoneroTxWallet] = set() - for tx in txs1: - if tx.in_tx_pool or tx.is_failed: - to_remove.add(tx) - - TxUtils.remove_txs(txs1, to_remove) - - txs2: list[MoneroTxWallet] = txs_2.copy() - to_remove.clear() - for tx in txs2: - if tx.in_tx_pool or tx.is_failed: - to_remove.add(tx) - - TxUtils.remove_txs(txs2, to_remove) + txs1: list[MoneroTxWallet] = list(filter(lambda tx: not tx.in_tx_pool and not tx.is_failed, txs_1)) + txs2: list[MoneroTxWallet] = list(filter(lambda tx: not tx.in_tx_pool and not tx.is_failed, txs_2)) # nullify off-chain data for comparison all_txs: list[MoneroTxWallet] = txs1.copy() @@ -230,7 +217,7 @@ def test_tx_wallets_equal_on_chain(cls, txs_1: list[MoneroTxWallet], txs_2: list cls.transfer_cached_info(tx2, tx1) # test tx equality - assert TxUtils.txs_mergeable(tx1, tx2), "Txs are not mergeable" + assert TxWalletUtils.txs_mergeable(tx1, tx2), "Txs are not mergeable" AssertUtils.assert_equals(tx1, tx2) found = True diff --git a/tests/utils/wallet_error_utils.py b/tests/utils/wallet_error_utils.py new file mode 100644 index 0000000..59c632b --- /dev/null +++ b/tests/utils/wallet_error_utils.py @@ -0,0 +1,82 @@ +from abc import ABC + + +class WalletErrorUtils(ABC): + + CREATED_WALLET_KEYS_ERROR: str = "Wallet created from keys is not connected to authenticated daemon" + """Create wallet error message.""" + + WALLET_IS_CLOSED_ERROR: str = "Wallet is closed" + """Wallet is closed error message.""" + + @classmethod + def test_invalid_address_error(cls, ex: Exception) -> None: + """Test exception is invalid address. + + :param Exception ex: exception to test. + """ + msg: str = str(ex) + assert msg == "Invalid address", msg + + @classmethod + def test_invalid_tx_hash_error(cls, ex: Exception) -> None: + """Test exception is invalid hash format. + + :param Exception ex: exception to test. + """ + msg: str = str(ex) + assert msg == "TX hash has invalid format", msg + + @classmethod + def test_invalid_tx_key_error(cls, ex: Exception) -> None: + """Test exception is invalid key error. + + :param Exception ex: exception to test. + """ + msg: str = str(ex) + assert msg == "Tx key has invalid format", msg + + @classmethod + def test_invalid_signature_error(cls, ex: Exception) -> None: + """Test exception is invalid signature error. + + :param Exception ex: exception to test. + """ + msg: str = str(ex) + assert msg == "Signature size mismatch with additional tx pubkeys", msg + + @classmethod + def test_no_subaddress_error(cls, ex: Exception) -> None: + """Test exception is no subaddress error. + + :param Exception ex: exception to test. + """ + msg: str = str(ex) + assert msg == "Address must not be a subaddress", msg + + @classmethod + def test_signature_header_error(cls, ex: Exception) -> None: + """Test exception is signature header error. + + :param Exception ex: exception to test. + """ + msg: str = str(ex) + assert msg == "Signature header check error", msg + + @classmethod + def test_no_wallet_file_error(cls, error: Exception) -> None: + """Test for `No wallet file` monero error. + + :param Exception | None error: error to test. + """ + err_msg: str = str(error) + assert err_msg == "No wallet file", err_msg + + @classmethod + def test_wallet_is_closed_error(cls, error: Exception) -> None: + """Test for `Wallet is closed` monero error. + + :param Exception | None error: error to test. + """ + err_msg: str = str(error) + assert err_msg == cls.WALLET_IS_CLOSED_ERROR, err_msg diff --git a/tests/utils/wallet_send_utils.py b/tests/utils/wallet_send_utils.py new file mode 100644 index 0000000..a523c35 --- /dev/null +++ b/tests/utils/wallet_send_utils.py @@ -0,0 +1,79 @@ + +from abc import ABC +from typing import Optional + +from monero import MoneroWallet, MoneroTxConfig, MoneroDaemon + +from .single_tx_sender import SingleTxSender +from .from_multiple_tx_sender import FromMultipleTxSender +from .to_multiple_tx_sender import ToMultipleTxSender +from .wallet_sweeper import WalletSweeper +from .send_and_update_txs_tester import SendAndUpdateTxsTester +from .sync_with_pool_submit_tester import SyncWithPoolSubmitTester + + +class WalletSendUtils(ABC): + + # Convenience method for single tx send tests + @classmethod + def test_send_to_single(cls, wallet: MoneroWallet, can_split: bool, relay: Optional[bool] = None, payment_id: Optional[str] = None) -> None: + """Test creating transaction and sending to single destination. + + :param MoneroWallet wallet: wallet to send funds from. + :param bool can_split: Can split transactions. + :param bool | None relay: Relay created transaction(s). + :param str | None payment_id: Transaction payment id. + """ + config = MoneroTxConfig() + config.can_split = can_split + config.relay = relay + config.payment_id = payment_id + sender = SingleTxSender(wallet, config) + sender.send() + + # Convenience method for sending funds from multiple sources + @classmethod + def test_send_from_multiple(cls, wallet: MoneroWallet, can_split: bool | None) -> None: + """Test send multiple txs from wallet. + + :param MoneroWallet wallet: test wallet to send txs from. + :param bool can_split: can split wallet txs. + """ + sender: FromMultipleTxSender = FromMultipleTxSender(wallet, can_split) + sender.send() + + # Convenience method for multiple tx send tests + @classmethod + def test_send_to_multiple( + cls, + wallet: MoneroWallet, + num_accounts: int, + num_subaddresses_per_account: int, + can_split: bool, + send_amount_per_subaddress: Optional[int] = None, + subtract_fee_from_destinations: bool = False + ) -> None: + sender: ToMultipleTxSender = ToMultipleTxSender( + wallet, num_accounts, num_subaddresses_per_account, + can_split, send_amount_per_subaddress, subtract_fee_from_destinations) + sender.send() + + @classmethod + def test_sweep_wallet(cls, wallet: MoneroWallet, sweep_each_subaddress: Optional[bool]) -> None: + """Test creating sweep wallet transaction. + + :param MoneroWallet wallet: test wallet to sweep. + :param bool | None sweep_each_subaddress: sweep each wallet subaddresses. + """ + sweeper: WalletSweeper = WalletSweeper(wallet, sweep_each_subaddress) + sweeper.sweep() + + @classmethod + def test_send_and_update_txs(cls, daemon: MoneroDaemon, wallet: MoneroWallet, config: MoneroTxConfig) -> None: + tester: SendAndUpdateTxsTester = SendAndUpdateTxsTester(daemon, wallet, config) + tester.test() + + @classmethod + def test_sync_with_pool_submit(cls, daemon: MoneroDaemon, wallet: MoneroWallet, config: MoneroTxConfig) -> None: + tester: SyncWithPoolSubmitTester = SyncWithPoolSubmitTester(daemon, wallet, config) + tester.test() diff --git a/tests/utils/wallet_sweeper.py b/tests/utils/wallet_sweeper.py index 11ba276..6280faf 100644 --- a/tests/utils/wallet_sweeper.py +++ b/tests/utils/wallet_sweeper.py @@ -5,9 +5,9 @@ MoneroTxQuery ) -from .tx_context import TxContext +from .context import TxContext from .assert_utils import AssertUtils -from .tx_utils import TxUtils +from .tx_wallet_utils import TxWalletUtils from .test_utils import TestUtils @@ -38,9 +38,9 @@ def _check_for_balance(self) -> None: for subaddress in account.subaddresses: assert subaddress.balance is not None assert subaddress.unlocked_balance is not None - if subaddress.balance > TxUtils.MAX_FEE: + if subaddress.balance > TxWalletUtils.MAX_FEE: subaddresses_balance.append(subaddress) - if subaddress.unlocked_balance > TxUtils.MAX_FEE: + if subaddress.unlocked_balance > TxWalletUtils.MAX_FEE: subaddresses_unlocked.append(subaddress) assert len(subaddresses_balance) >= 2, "Test requires multiple accounts with a balance greater than the fee; run send to multiple first" @@ -57,13 +57,13 @@ def _check_outputs(self) -> None: spendable_outputs: list[MoneroOutputWallet] = self._wallet.get_outputs(query) for spendable_output in spendable_outputs: assert spendable_output.amount is not None - assert spendable_output.amount < TxUtils.MAX_FEE, f"Unspent output should have been swept\n{spendable_output.serialize()}" + assert spendable_output.amount < TxWalletUtils.MAX_FEE, f"Unspent output should have been swept\n{spendable_output.serialize()}" # all subaddress unlocked balances must be less than fee for account in self._wallet.get_accounts(True): for subaddress in account.subaddresses: assert subaddress.unlocked_balance is not None - assert subaddress.unlocked_balance < TxUtils.MAX_FEE, "No subaddress should have more unlocked than the fee" + assert subaddress.unlocked_balance < TxWalletUtils.MAX_FEE, "No subaddress should have more unlocked than the fee" def sweep(self) -> None: """Sweep outputs from wallet.""" @@ -102,6 +102,6 @@ def sweep(self) -> None: ctx.config = config ctx.is_send_response = True ctx.is_sweep_response = True - TxUtils.test_tx_wallet(tx, ctx) + TxWalletUtils.test_tx_wallet(tx, ctx) self._check_outputs() diff --git a/tests/utils/wallet_test_utils.py b/tests/utils/wallet_test_utils.py new file mode 100644 index 0000000..5626d48 --- /dev/null +++ b/tests/utils/wallet_test_utils.py @@ -0,0 +1,194 @@ +import logging + +from abc import ABC +from typing import Optional + +from monero import ( + MoneroNetworkType, MoneroUtils, MoneroAccount, + MoneroSubaddress, MoneroWalletKeys, MoneroWalletConfig, + MoneroWallet, MoneroTxConfig, MoneroDestination, + MoneroTxWallet, MoneroWalletFull, MoneroWalletRpc, +) + +from .test_utils import TestUtils + +logger: logging.Logger = logging.getLogger("WalletTestUtils") + + +class WalletTestUtils(ABC): + + @classmethod + def get_external_wallet_address(cls) -> str: + """Gets an external wallet address. + + :returns str: external wallet address. + """ + network_type: MoneroNetworkType | None = TestUtils.NETWORK_TYPE + + if network_type == MoneroNetworkType.STAGENET: + # subaddress + return "78Zq71rS1qK4CnGt8utvMdWhVNMJexGVEDM2XsSkBaGV9bDSnRFFhWrQTbmCACqzevE8vth9qhWfQ9SUENXXbLnmMVnBwgW" + if network_type == MoneroNetworkType.TESTNET: + # subaddress + return "BhsbVvqW4Wajf4a76QW3hA2B3easR5QdNE5L8NwkY7RWXCrfSuaUwj1DDUsk3XiRGHBqqsK3NPvsATwcmNNPUQQ4SRR2b3V" + if network_type == MoneroNetworkType.MAINNET: + # subaddress + return "87a1Yf47UqyQFCrMqqtxfvhJN9se3PgbmU7KUFWqhSu5aih6YsZYoxfjgyxAM1DztNNSdoYTZYn9xa3vHeJjoZqdAybnLzN" + else: + raise Exception("Invalid network type: " + str(network_type)) + + @classmethod + def select_subaddress_with_min_balance(cls, wallet: MoneroWallet, min_balance: int, skip_primary: bool = True) -> Optional[MoneroSubaddress]: + """Select a wallet subaddress with minimum unlocked balance. + + :param MoneroWallet wallet: wallet to select subaddress from. + :param int min_balance: miniumum subaddress unlocked balance. + :param bool skip_primary: skip primary account address (default `True`). + :returns MoneroSubaddress | None: selected subaddress with unlocked `min_balance`, if any. + """ + # get wallet accounts + accounts: list[MoneroAccount] = wallet.get_accounts(True) + for account in accounts: + assert account.index is not None + i: int = account.index + for subaddress in account.subaddresses: + assert subaddress.index is not None + j: int = subaddress.index + if i == 0 and j == 0 and skip_primary: + continue + + assert subaddress.unlocked_balance is not None + if subaddress.unlocked_balance > min_balance - 1: + return subaddress + + return None + + @classmethod + def create_random_wallets(cls, network_type: MoneroNetworkType, n: int = 10) -> list[MoneroWalletKeys]: + """Create random wallet used as spam destinations. + + :param MoneroNetworkType network_type: Network type. + :param int n: number of wallets to create. + :returns list[MoneroWalletKeys]: random wallets created. + """ + assert n >= 0, "n must be >= 0" + wallets: list[MoneroWalletKeys] = [] + # setup basic wallet config + config = MoneroWalletConfig() + config.network_type = network_type + # create n random wallets + for i in range(n): + logger.debug(f"Creating random wallet ({i})...") + wallet = MoneroWalletKeys.create_wallet_random(config) + logger.debug(f"Created random wallet ({i}): {wallet.get_primary_address()}") + wallets.append(wallet) + + return wallets + + @classmethod + def is_wallet_funded(cls, wallet: MoneroWallet, xmr_amount_per_address: float, num_accounts: int, num_subaddresses: int) -> bool: + """Check if wallet has required funds. + + :param MoneroWallet wallet: wallet to check balance. + :param float xmr_amount_per_address: human readable xmr amount to check per address. + :param int num_accounts: number of wallet accounts to check balance. + :param int num_subaddresses: number of wallet subaddresses to check balance for each `num_accounts`. + :returns bool: `True` if `wallet` has enough balance, `False` otherwise. + """ + amount_per_address: int = MoneroUtils.xmr_to_atomic_units(xmr_amount_per_address) + amount_required_per_account: int = amount_per_address * (num_subaddresses + 1) # include primary address + amount_required: int = amount_required_per_account * num_accounts + required_subaddresses: int = num_accounts * (num_subaddresses + 1) # include primary address + + if isinstance(wallet, MoneroWalletFull) or isinstance(wallet, MoneroWalletRpc): + wallet.sync() + else: + return False + + wallet_balance: int = wallet.get_balance() + + if wallet_balance < amount_required: + return False + + accounts: list[MoneroAccount] = wallet.get_accounts(True) + subaddresses_found: int = 0 + num_wallet_accounts: int = len(accounts) + + if num_wallet_accounts < num_accounts: + return False + + for account in accounts: + for subaddress in account.subaddresses: + balance = subaddress.unlocked_balance + assert balance is not None + if balance >= amount_per_address: + subaddresses_found += 1 + + return subaddresses_found >= required_subaddresses + + @classmethod + def fund_wallet(cls, wallet: MoneroWallet, xmr_amount_per_address: float = 10, num_accounts: int = 3, num_subaddresses: int = 5) -> list[MoneroTxWallet]: + """Fund a wallet with mined coins. + + :param MoneroWallet wallet: wallet to fund with mined coins. + :param float xmr_amount_per_address: XMR amount to fund each address. + :param int num_accounts: number of accounts to fund. + :param int num_subaddresses: number of subaddress to fund for each account. + :returns list[MoneroTxWallet] | None: Funding transactions created from mining wallet. + """ + primary_addr: str = wallet.get_primary_address() + if cls.is_wallet_funded(wallet, xmr_amount_per_address, num_accounts, num_subaddresses): + logger.debug(f"Already funded wallet {primary_addr}") + return [] + + amount_per_address: int = MoneroUtils.xmr_to_atomic_units(xmr_amount_per_address) + amount_per_account: int = amount_per_address * (num_subaddresses + 1) # include primary address + amount_required: int = amount_per_account * num_accounts + amount_required_str: str = f"{MoneroUtils.atomic_units_to_xmr(amount_required)} XMR" + + logger.debug(f"Funding wallet {primary_addr} with {amount_required_str}...") + + tx_config: MoneroTxConfig = MoneroTxConfig() + tx_config.account_index = 0 + tx_config.relay = True + tx_config.can_split = True + + supports_get_accounts: bool = isinstance(wallet, MoneroWalletRpc) or isinstance(wallet, MoneroWalletFull) + while supports_get_accounts and len(wallet.get_accounts()) < num_accounts: + wallet.create_account() + + for account_idx in range(num_accounts): + account: MoneroAccount = wallet.get_account(account_idx) + num_subaddr: int = len(account.subaddresses) + + while num_subaddr < num_subaddresses: + wallet.create_subaddress(account_idx) + num_subaddr += 1 + + addresses: list[MoneroSubaddress] = wallet.get_subaddresses(account_idx, list(range(num_subaddresses + 1))) + for address in addresses: + assert address.address is not None + dest = MoneroDestination(address.address, amount_per_address) + tx_config.destinations.append(dest) + + mining_wallet: MoneroWalletFull = TestUtils.get_mining_wallet() + wallet_balance: int = mining_wallet.get_balance() + err_msg: str = f"Mining wallet doesn't have enough balance: {MoneroUtils.atomic_units_to_xmr(wallet_balance)}" + assert wallet_balance > amount_required, err_msg + + txs: list[MoneroTxWallet] = mining_wallet.create_txs(tx_config) + txs_amount: int = 0 + for tx in txs: + assert tx.is_failed is False, "Cannot fund wallet: tx failed" + tx_amount: int = tx.get_outgoing_amount() + assert tx_amount > 0, "Tx outgoing amount should be > 0" + txs_amount += tx_amount + + sent_amount_xmr_str: str = f"{MoneroUtils.atomic_units_to_xmr(txs_amount)} XMR" + + if supports_get_accounts: + wallet.save() + + logger.debug(f"Funded test wallet {primary_addr} with {sent_amount_xmr_str} in {len(txs)} txs") + + return txs diff --git a/tests/utils/wallet_transfers_utils.py b/tests/utils/wallet_transfers_utils.py new file mode 100644 index 0000000..3abfd48 --- /dev/null +++ b/tests/utils/wallet_transfers_utils.py @@ -0,0 +1,50 @@ +from abc import ABC +from typing import Optional + +from monero import ( + MoneroWallet, MoneroTransfer, + MoneroTransferQuery +) + +from .assert_utils import AssertUtils +from .context import TxContext +from .tx_wallet_utils import TxWalletUtils + + +class WalletTransfersUtils(ABC): + """Wallet transfers utilities.""" + + @classmethod + def get_and_test_transfers( + cls, + wallet: MoneroWallet, + query: Optional[MoneroTransferQuery], + ctx: Optional[TxContext], + is_expected: Optional[bool] + ) -> list[MoneroTransfer]: + """Get and test transfers from wallet. + + :param MoneroWallet wallet: wallet to get transfers from. + :param MoneroTransferQuery | None query: filter wallet transfers by query if defined. + :param TxContext | None ctx: transaction context. + :param bool | None is_expected: expects empty/non-empty transfers. + """ + copy: Optional[MoneroTransferQuery] = query.copy() if query is not None else None + transfers = wallet.get_transfers(query) if query is not None else wallet.get_transfers(MoneroTransferQuery()) + + if is_expected is False: + assert len(transfers) == 0 + elif is_expected is True: + assert len(transfers) > 0, "Transfers were expected but not found; run send tests?" + + if ctx is None: + ctx = TxContext() + + ctx.wallet = wallet + for transfer in transfers: + TxWalletUtils.test_tx_wallet(transfer.tx, ctx) + + if query is not None: + AssertUtils.assert_equals(copy, query) + + return transfers diff --git a/tests/utils/wallet_txs_utils.py b/tests/utils/wallet_txs_utils.py new file mode 100644 index 0000000..4e36235 --- /dev/null +++ b/tests/utils/wallet_txs_utils.py @@ -0,0 +1,133 @@ +from abc import ABC +from typing import Optional +from random import shuffle + +from monero import ( + MoneroWallet, MoneroTxWallet, MoneroTxQuery, + MoneroTxConfig +) + +from .assert_utils import AssertUtils +from .context import TxContext +from .tx_wallet_utils import TxWalletUtils + + +class WalletTxsUtils(ABC): + + @classmethod + def get_and_test_txs( + cls, + wallet: MoneroWallet, + query: Optional[MoneroTxQuery], + ctx: Optional[TxContext], + is_expected: Optional[bool], + regtest: bool + ) -> list[MoneroTxWallet]: + """Get and test txs from wallet. + + :param MoneroWallet wallet: wallet to get txs from. + :param MoneroTxQuery | None query: filter wallet txs by query if defined. + :param TxContext | None ctx: transaction context. + :param bool | None is_expected: expects empty/non-empty txs. + """ + copy: Optional[MoneroTxQuery] = query.copy() if query is not None else None + txs = wallet.get_txs(query) if query is not None else wallet.get_txs() + assert txs is not None + + if is_expected is False: + assert len(txs) == 0 + + if is_expected is True: + assert len(txs) > 0 + + TxWalletUtils.test_txs_wallet(txs, ctx) + TxWalletUtils.test_get_txs_structure(txs, query, regtest) + + if query is not None: + AssertUtils.assert_equals(copy, query) + + return txs + + @classmethod + def get_random_transactions( + cls, + wallet: MoneroWallet, + query: Optional[MoneroTxQuery] = None, + min_txs: Optional[int] = None, + max_txs: Optional[int] = None + ) -> list[MoneroTxWallet]: + """Get random transaction from wallet. + + :param Wallet wallet: wallet to get random txs from. + :param MoneroTxQuery | None: filter txs by query (default `None`). + :param int | None min_txs: minimum number of txs to get (default `None`). + :param int | None max_txs: maximum number of txs to get (default `None`). + """ + txs = wallet.get_txs(query if query is not None else MoneroTxQuery()) + + if min_txs is not None: + assert len(txs) >= min_txs, f"{len(txs)}/{min_txs} transactions found with the query" + + shuffle(txs) + + if max_txs is None: + return txs + + result: list[MoneroTxWallet] = [] + + for i, tx in enumerate(txs): + result.append(tx) + if i >= max_txs - 1: + break + + return result + + @classmethod + def get_unrelayed_tx(cls, wallet: MoneroWallet, account_idx: int) -> MoneroTxWallet: + """Get unrelayed tx from wallet account. + + :param MoneroWallet wallet: wallet to get unrelayed tx from. + :param int account_idx: wallet account index to get unrelayed tx from. + :returns MoneroTxWallet: unrealyed wallet tx. + """ + # TODO monero-project + assert account_idx > 0, "Txs sent from/to same account are not properly synced from the pool" + config = MoneroTxConfig() + config.account_index = account_idx + config.address = wallet.get_primary_address() + config.amount = TxWalletUtils.MAX_FEE + + tx = wallet.create_tx(config) + assert (tx.full_hex is None or tx.full_hex == "") is False + assert tx.relay is False, f"Expected tx.relay to be False, got {tx.relay}" + return tx + + @classmethod + def test_scan_txs(cls, wallet: MoneroWallet, scan_wallet: MoneroWallet) -> None: + """Test wallet transaction scan. + + :param MoneroWallet wallet: original wallet to test. + :param MoneroWallet scan_wallet: scan wallet to test. + """ + # get a few tx hashes + tx_hashes: list[str] = [] + txs: list[MoneroTxWallet] = wallet.get_txs() + assert len(txs) > 2, "Not enough txs to scan" + for i in range(1, 3): + tx_hash = txs[i].hash + assert tx_hash is not None + tx_hashes.append(tx_hash) + + # start wallet without scanning + # TODO create wallet without daemon connection (offline does not reconnect, default connects to localhost, + # offline then online causes confirmed txs to disappear) + scan_wallet.stop_syncing() + assert scan_wallet.is_connected_to_daemon() + + # scan txs + scan_wallet.scan_txs(tx_hashes) + + # TODO scanning txs causes merge problems reconciling 0 fee, is_miner_tx with test txs + + # close wallet + scan_wallet.close(False) diff --git a/tests/utils/wallet_utils.py b/tests/utils/wallet_utils.py index 0bd50d4..851248f 100644 --- a/tests/utils/wallet_utils.py +++ b/tests/utils/wallet_utils.py @@ -5,18 +5,12 @@ from monero import ( MoneroNetworkType, MoneroUtils, MoneroAccount, - MoneroSubaddress, MoneroWalletKeys, MoneroWalletConfig, - MoneroMessageSignatureResult, MoneroWallet, - MoneroTxWallet, MoneroWalletFull, MoneroWalletRpc, - MoneroTxConfig, MoneroDestination, MoneroAddressBookEntry + MoneroSubaddress, MoneroAddressBookEntry, + MoneroMessageSignatureResult, MoneroWallet ) from .gen_utils import GenUtils from .test_utils import TestUtils -from .single_tx_sender import SingleTxSender -from .to_multiple_tx_sender import ToMultipleTxSender -from .from_multiple_tx_sender import FromMultipleTxSender -from .wallet_sweeper import WalletSweeper logger: logging.Logger = logging.getLogger("WalletUtils") @@ -24,9 +18,6 @@ class WalletUtils(ABC): """Wallet test utilities.""" - WALLET_IS_CLOSED_ERROR: str = "Wallet is closed" - """Wallet is closed error message.""" - MAX_TX_PROOFS: Optional[int] = 25 """maximum number of transactions to check for each proof, undefined to check all.""" @@ -226,258 +217,6 @@ def test_address_book_entry(cls, entry: Optional[MoneroAddressBookEntry]) -> Non MoneroUtils.validate_address(entry.address, TestUtils.NETWORK_TYPE) assert entry.description is not None - # Convenience method for single tx send tests - @classmethod - def test_send_to_single(cls, wallet: MoneroWallet, can_split: bool, relay: Optional[bool] = None, payment_id: Optional[str] = None) -> None: - """Test creating transaction and sending to single destination. - - :param MoneroWallet wallet: wallet to send funds from. - :param bool can_split: Can split transactions. - :param bool | None relay: Relay created transaction(s). - :param str | None payment_id: Transaction payment id. - """ - config = MoneroTxConfig() - config.can_split = can_split - config.relay = relay - config.payment_id = payment_id - sender = SingleTxSender(wallet, config) - sender.send() - - # Convenience method for sending funds from multiple sources - @classmethod - def test_send_from_multiple(cls, wallet: MoneroWallet, can_split: bool | None) -> None: - """Test send multiple txs from wallet. - - :param MoneroWallet wallet: test wallet to send txs from. - :param bool can_split: can split wallet txs. - """ - sender: FromMultipleTxSender = FromMultipleTxSender(wallet, can_split) - sender.send() - - # Convenience method for multiple tx send tests - @classmethod - def test_send_to_multiple( - cls, - wallet: MoneroWallet, - num_accounts: int, - num_subaddresses_per_account: int, - can_split: bool, - send_amount_per_subaddress: Optional[int] = None, - subtract_fee_from_destinations: bool = False - ) -> None: - sender: ToMultipleTxSender = ToMultipleTxSender( - wallet, num_accounts, num_subaddresses_per_account, - can_split, send_amount_per_subaddress, subtract_fee_from_destinations) - sender.send() - - @classmethod - def test_no_wallet_file_error(cls, error: Optional[Exception]) -> None: - """Test for `No wallet file` monero error. - - :param Exception | None error: error to test. - """ - assert error is not None - err_msg: str = str(error) - assert err_msg == "No wallet file", err_msg - - @classmethod - def test_wallet_is_closed_error(cls, error: Optional[Exception]) -> None: - """Test for `Wallet is closed` monero error. - - :param Exception | None error: error to test. - """ - assert error is not None - err_msg: str = str(error) - assert err_msg == cls.WALLET_IS_CLOSED_ERROR, err_msg - - #endregion - - @classmethod - def get_external_wallet_address(cls) -> str: - """Gets an external wallet address. - - :returns str: external wallet address. - """ - network_type: MoneroNetworkType | None = TestUtils.NETWORK_TYPE - - if network_type == MoneroNetworkType.STAGENET: - # subaddress - return "78Zq71rS1qK4CnGt8utvMdWhVNMJexGVEDM2XsSkBaGV9bDSnRFFhWrQTbmCACqzevE8vth9qhWfQ9SUENXXbLnmMVnBwgW" - if network_type == MoneroNetworkType.TESTNET: - # subaddress - return "BhsbVvqW4Wajf4a76QW3hA2B3easR5QdNE5L8NwkY7RWXCrfSuaUwj1DDUsk3XiRGHBqqsK3NPvsATwcmNNPUQQ4SRR2b3V" - if network_type == MoneroNetworkType.MAINNET: - # subaddress - return "87a1Yf47UqyQFCrMqqtxfvhJN9se3PgbmU7KUFWqhSu5aih6YsZYoxfjgyxAM1DztNNSdoYTZYn9xa3vHeJjoZqdAybnLzN" - else: - raise Exception("Invalid network type: " + str(network_type)) - - @classmethod - def select_subaddress_with_min_balance(cls, wallet: MoneroWallet, min_balance: int, skip_primary: bool = True) -> Optional[MoneroSubaddress]: - """Select a wallet subaddress with minimum unlocked balance. - - :param MoneroWallet wallet: wallet to select subaddress from. - :param int min_balance: miniumum subaddress unlocked balance. - :param bool skip_primary: skip primary account address (default `True`). - :returns MoneroSubaddress | None: selected subaddress with unlocked `min_balance`, if any. - """ - # get wallet accounts - accounts: list[MoneroAccount] = wallet.get_accounts(True) - for account in accounts: - assert account.index is not None - i: int = account.index - for subaddress in account.subaddresses: - assert subaddress.index is not None - j: int = subaddress.index - if i == 0 and j == 0 and skip_primary: - continue - - assert subaddress.unlocked_balance is not None - if subaddress.unlocked_balance > min_balance - 1: - return subaddress - - return None - - @classmethod - def create_random_wallets(cls, network_type: MoneroNetworkType, n: int = 10) -> list[MoneroWalletKeys]: - """Create random wallet used as spam destinations. - - :param MoneroNetworkType network_type: Network type. - :param int n: number of wallets to create. - :returns list[MoneroWalletKeys]: random wallets created. - """ - assert n >= 0, "n must be >= 0" - wallets: list[MoneroWalletKeys] = [] - # setup basic wallet config - config = MoneroWalletConfig() - config.network_type = network_type - # create n random wallets - for i in range(n): - logger.debug(f"Creating random wallet ({i})...") - wallet = MoneroWalletKeys.create_wallet_random(config) - logger.debug(f"Created random wallet ({i}): {wallet.get_primary_address()}") - wallets.append(wallet) - - return wallets - - @classmethod - def is_wallet_funded(cls, wallet: MoneroWallet, xmr_amount_per_address: float, num_accounts: int, num_subaddresses: int) -> bool: - """Check if wallet has required funds. - - :param MoneroWallet wallet: wallet to check balance. - :param float xmr_amount_per_address: human readable xmr amount to check per address. - :param int num_accounts: number of wallet accounts to check balance. - :param int num_subaddresses: number of wallet subaddresses to check balance for each `num_accounts`. - :returns bool: `True` if `wallet` has enough balance, `False` otherwise. - """ - amount_per_address: int = MoneroUtils.xmr_to_atomic_units(xmr_amount_per_address) - amount_required_per_account: int = amount_per_address * (num_subaddresses + 1) # include primary address - amount_required: int = amount_required_per_account * num_accounts - required_subaddresses: int = num_accounts * (num_subaddresses + 1) # include primary address - - if isinstance(wallet, MoneroWalletFull) or isinstance(wallet, MoneroWalletRpc): - wallet.sync() - else: - return False - - wallet_balance: int = wallet.get_balance() - - if wallet_balance < amount_required: - return False - - accounts: list[MoneroAccount] = wallet.get_accounts(True) - subaddresses_found: int = 0 - num_wallet_accounts: int = len(accounts) - - if num_wallet_accounts < num_accounts: - return False - - for account in accounts: - for subaddress in account.subaddresses: - balance = subaddress.unlocked_balance - assert balance is not None - if balance >= amount_per_address: - subaddresses_found += 1 - - return subaddresses_found >= required_subaddresses - - @classmethod - def fund_wallet(cls, wallet: MoneroWallet, xmr_amount_per_address: float = 10, num_accounts: int = 3, num_subaddresses: int = 5) -> list[MoneroTxWallet]: - """Fund a wallet with mined coins. - - :param MoneroWallet wallet: wallet to fund with mined coins. - :param float xmr_amount_per_address: XMR amount to fund each address. - :param int num_accounts: number of accounts to fund. - :param int num_subaddresses: number of subaddress to fund for each account. - :returns list[MoneroTxWallet] | None: Funding transactions created from mining wallet. - """ - primary_addr: str = wallet.get_primary_address() - if cls.is_wallet_funded(wallet, xmr_amount_per_address, num_accounts, num_subaddresses): - logger.debug(f"Already funded wallet {primary_addr}") - return [] - - amount_per_address: int = MoneroUtils.xmr_to_atomic_units(xmr_amount_per_address) - amount_per_account: int = amount_per_address * (num_subaddresses + 1) # include primary address - amount_required: int = amount_per_account * num_accounts - amount_required_str: str = f"{MoneroUtils.atomic_units_to_xmr(amount_required)} XMR" - - logger.debug(f"Funding wallet {primary_addr} with {amount_required_str}...") - - tx_config: MoneroTxConfig = MoneroTxConfig() - tx_config.account_index = 0 - tx_config.relay = True - tx_config.can_split = True - - supports_get_accounts: bool = isinstance(wallet, MoneroWalletRpc) or isinstance(wallet, MoneroWalletFull) - while supports_get_accounts and len(wallet.get_accounts()) < num_accounts: - wallet.create_account() - - for account_idx in range(num_accounts): - account: MoneroAccount = wallet.get_account(account_idx) - num_subaddr: int = len(account.subaddresses) - - while num_subaddr < num_subaddresses: - wallet.create_subaddress(account_idx) - num_subaddr += 1 - - addresses: list[MoneroSubaddress] = wallet.get_subaddresses(account_idx, list(range(num_subaddresses + 1))) - for address in addresses: - assert address.address is not None - dest = MoneroDestination(address.address, amount_per_address) - tx_config.destinations.append(dest) - - mining_wallet: MoneroWalletFull = TestUtils.get_mining_wallet() - wallet_balance: int = mining_wallet.get_balance() - err_msg: str = f"Mining wallet doesn't have enough balance: {MoneroUtils.atomic_units_to_xmr(wallet_balance)}" - assert wallet_balance > amount_required, err_msg - - txs: list[MoneroTxWallet] = mining_wallet.create_txs(tx_config) - txs_amount: int = 0 - for tx in txs: - assert tx.is_failed is False, "Cannot fund wallet: tx failed" - tx_amount: int = tx.get_outgoing_amount() - assert tx_amount > 0, "Tx outgoing amount should be > 0" - txs_amount += tx_amount - - sent_amount_xmr_str: str = f"{MoneroUtils.atomic_units_to_xmr(txs_amount)} XMR" - - if supports_get_accounts: - wallet.save() - - logger.debug(f"Funded test wallet {primary_addr} with {sent_amount_xmr_str} in {len(txs)} txs") - - return txs - - @classmethod - def test_sweep_wallet(cls, wallet: MoneroWallet, sweep_each_subaddress: Optional[bool]) -> None: - """Test creating sweep wallet transaction. - - :param MoneroWallet wallet: test wallet to sweep. - :param bool | None sweep_each_subaddress: sweep each wallet subaddresses. - """ - sweeper: WalletSweeper = WalletSweeper(wallet, sweep_each_subaddress) - sweeper.sweep() - @classmethod def test_wallet_keys(cls, address: str, view_key: str, spend_key: str, w: MoneroWallet) -> None: """Test wallet keys. @@ -493,3 +232,5 @@ def test_wallet_keys(cls, address: str, view_key: str, spend_key: str, w: Monero assert spend_key == w.get_private_spend_key() MoneroUtils.validate_mnemonic(w.get_seed()) assert MoneroWallet.DEFAULT_LANGUAGE == w.get_seed_language() + + #endregion