From 91a67caa55831da1084a305c24bfecd906098876 Mon Sep 17 00:00:00 2001 From: everoddandeven Date: Mon, 5 Jan 2026 22:30:00 +0100 Subject: [PATCH] Reorganize code * Also minor code issues fixes --- CMakeLists.txt | 5 +- setup.py | 5 +- src/cpp/daemon/py_monero_daemon.h | 142 +- src/cpp/daemon/py_monero_daemon_default.cpp | 72 + src/cpp/daemon/py_monero_daemon_default.h | 25 + src/cpp/daemon/py_monero_daemon_model.cpp | 2 +- ...ro_daemon.cpp => py_monero_daemon_rpc.cpp} | 73 +- src/cpp/daemon/py_monero_daemon_rpc.h | 121 ++ src/cpp/py_monero.cpp | 4 +- src/cpp/wallet/py_monero_wallet.cpp | 1706 ----------------- src/cpp/wallet/py_monero_wallet.h | 205 -- src/cpp/wallet/py_monero_wallet_full.cpp | 11 + src/cpp/wallet/py_monero_wallet_full.h | 19 + src/cpp/wallet/py_monero_wallet_model.cpp | 2 +- src/cpp/wallet/py_monero_wallet_rpc.cpp | 1700 ++++++++++++++++ src/cpp/wallet/py_monero_wallet_rpc.h | 194 ++ tests/test_monero_daemon_rpc.py | 4 +- 17 files changed, 2160 insertions(+), 2130 deletions(-) create mode 100644 src/cpp/daemon/py_monero_daemon_default.cpp create mode 100644 src/cpp/daemon/py_monero_daemon_default.h rename src/cpp/daemon/{py_monero_daemon.cpp => py_monero_daemon_rpc.cpp} (93%) create mode 100644 src/cpp/daemon/py_monero_daemon_rpc.h create mode 100644 src/cpp/wallet/py_monero_wallet_full.cpp create mode 100644 src/cpp/wallet/py_monero_wallet_full.h create mode 100644 src/cpp/wallet/py_monero_wallet_rpc.cpp create mode 100644 src/cpp/wallet/py_monero_wallet_rpc.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 064d9e3..4b5979c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,9 +7,12 @@ find_package(pybind11 REQUIRED) set(SOURCES src/cpp/common/py_monero_common.cpp src/cpp/daemon/py_monero_daemon_model.cpp - src/cpp/daemon/py_monero_daemon.cpp + src/cpp/daemon/py_monero_daemon_default.cpp + src/cpp/daemon/py_monero_daemon_rpc.cpp src/cpp/wallet/py_monero_wallet_model.cpp src/cpp/wallet/py_monero_wallet.cpp + src/cpp/wallet/py_monero_wallet_full.cpp + src/cpp/wallet/py_monero_wallet_rpc.cpp src/cpp/py_monero.cpp ) diff --git a/setup.py b/setup.py index 390c4c8..c5090b1 100644 --- a/setup.py +++ b/setup.py @@ -13,9 +13,12 @@ [ 'src/cpp/common/py_monero_common.cpp', 'src/cpp/daemon/py_monero_daemon_model.cpp', - 'src/cpp/daemon/py_monero_daemon.cpp', + 'src/cpp/daemon/py_monero_daemon_default.cpp', + 'src/cpp/daemon/py_monero_daemon_rpc.cpp', 'src/cpp/wallet/py_monero_wallet_model.cpp', 'src/cpp/wallet/py_monero_wallet.cpp', + 'src/cpp/wallet/py_monero_wallet_full.cpp', + 'src/cpp/wallet/py_monero_wallet_rpc.cpp', 'src/cpp/py_monero.cpp' ], include_dirs=[ diff --git a/src/cpp/daemon/py_monero_daemon.h b/src/cpp/daemon/py_monero_daemon.h index eec1a7f..c08a363 100644 --- a/src/cpp/daemon/py_monero_daemon.h +++ b/src/cpp/daemon/py_monero_daemon.h @@ -16,7 +16,7 @@ class PyMoneroDaemonListener : public monero_daemon_listener { public: virtual void on_block_header(const std::shared_ptr &header) { PYBIND11_OVERRIDE( - void, + void, monero_daemon_listener, on_block_header, header @@ -111,143 +111,3 @@ class PyMoneroDaemon { virtual void stop() { throw std::runtime_error("PyMoneroDaemon: not implemented"); } virtual std::shared_ptr wait_for_next_block_header() { throw std::runtime_error("PyMoneroDaemon: not implemented"); } }; - -class PyMoneroDaemonDefault : public PyMoneroDaemon { -public: - - std::vector> get_listeners() override { return m_listeners; } - void add_listener(const std::shared_ptr &listener) override; - void remove_listener(const std::shared_ptr &listener) override; - void remove_listeners() override; - std::optional> get_tx(const std::string& tx_hash, bool prune = false) override; - void relay_tx_by_hash(std::string& tx_hash) override; - PyMoneroKeyImageSpentStatus get_key_image_spent_status(std::string& key_image) override; - std::optional get_tx_hex(const std::string& tx_hash, bool prune = false); - void submit_block(const std::string& block_blob) override; - void set_peer_ban(const std::shared_ptr& ban) override; - -protected: - mutable boost::recursive_mutex m_listeners_mutex; - std::vector> m_listeners; - - virtual void refresh_listening() { } -}; - -class PyMoneroDaemonPoller { -public: - explicit PyMoneroDaemonPoller(PyMoneroDaemon* daemon, uint64_t poll_period_ms = 5000): m_poll_period_ms(poll_period_ms), m_is_polling(false) { - m_daemon = daemon; - } - - ~PyMoneroDaemonPoller(); - void set_is_polling(bool is_polling); - -private: - PyMoneroDaemon* m_daemon; - std::shared_ptr m_last_header; - uint64_t m_poll_period_ms; - std::atomic m_is_polling; - std::thread m_thread; - - void loop(); - void poll(); - void announce_block_header(const std::shared_ptr& header); -}; - - -class PyMoneroDaemonRpc : public PyMoneroDaemonDefault { -public: - PyMoneroDaemonRpc() { - m_rpc = std::make_shared(); - } - - PyMoneroDaemonRpc(std::shared_ptr rpc) { - m_rpc = rpc; - if (!rpc->is_online() && !rpc->m_uri->empty()) rpc->check_connection(); - } - - PyMoneroDaemonRpc(const std::string& uri, const std::string& username = "", const std::string& password = "") { - m_rpc = std::make_shared(); - m_rpc->m_uri = uri; - if (!username.empty() && !password.empty()) m_rpc->set_credentials(username, password); - if (!uri.empty()) m_rpc->check_connection(); - } - - ~PyMoneroDaemonRpc(); - - std::shared_ptr get_rpc_connection() const; - bool is_connected(); - monero::monero_version get_version() override; - bool is_trusted() override; - uint64_t get_height() override; - std::string get_block_hash(uint64_t height) override; - std::shared_ptr get_block_template(std::string& wallet_address, int reserve_size) override; - std::shared_ptr get_block_template(std::string& wallet_address) 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; - std::vector> get_block_headers_by_range(uint64_t start_height, uint64_t end_height) override; - std::shared_ptr get_block_by_hash(const std::string& hash) override; - std::vector> get_blocks_by_hash(const std::vector& block_hashes, uint64_t start_height, bool prune) override; - std::shared_ptr get_block_by_height(uint64_t height) override; - std::vector> get_blocks_by_height(const std::vector& heights) override; - std::vector> get_blocks_by_range(boost::optional start_height, boost::optional end_height) override; - std::vector> get_blocks_by_range_chunked(boost::optional start_height, boost::optional end_height, boost::optional max_chunk_size) override; - std::vector get_block_hashes(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(std::string& tx_hex, bool do_not_relay = false) override; - void relay_txs_by_hash(std::vector& tx_hashes) 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(std::vector& key_images) override; - std::vector> get_outputs(std::vector& outputs) override; - std::vector> get_output_histogram(std::vector amounts, int min_count, int max_count, bool is_unlocked, int 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; - int reset_download_limit() override; - 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; - 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; - 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; - 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 download_update() override; - void stop() override; - std::shared_ptr wait_for_next_block_header(); - static void check_response_status(std::shared_ptr response); - static void check_response_status(std::shared_ptr response); - -protected: - std::shared_ptr m_rpc; - 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); - void refresh_listening() override; - static void check_response_status(const boost::property_tree::ptree& node); -}; diff --git a/src/cpp/daemon/py_monero_daemon_default.cpp b/src/cpp/daemon/py_monero_daemon_default.cpp new file mode 100644 index 0000000..35fac21 --- /dev/null +++ b/src/cpp/daemon/py_monero_daemon_default.cpp @@ -0,0 +1,72 @@ +#include "py_monero_daemon_default.h" + + +void PyMoneroDaemonDefault::add_listener(const std::shared_ptr &listener) { + boost::lock_guard lock(m_listeners_mutex); + m_listeners.push_back(listener); + refresh_listening(); +} + +void PyMoneroDaemonDefault::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()); + refresh_listening(); +} + +void PyMoneroDaemonDefault::remove_listeners() { + boost::lock_guard lock(m_listeners_mutex); + m_listeners.clear(); + refresh_listening(); +} + +std::optional> PyMoneroDaemonDefault::get_tx(const std::string& tx_hash, bool prune) { + std::vector hashes; + hashes.push_back(tx_hash); + auto txs = get_txs(hashes, prune); + std::optional> tx; + + if (txs.size() > 0) { + tx = txs[0]; + } + + return tx; +} + +void PyMoneroDaemonDefault::relay_tx_by_hash(std::string& tx_hash) { + std::vector tx_hashes; + tx_hashes.push_back(tx_hash); + relay_txs_by_hash(tx_hashes); +} + +PyMoneroKeyImageSpentStatus PyMoneroDaemonDefault::get_key_image_spent_status(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]; +} + +std::optional PyMoneroDaemonDefault::get_tx_hex(const std::string& tx_hash, bool prune) { + std::vector hashes; + hashes.push_back(tx_hash); + auto hexes = get_tx_hexes(hashes, prune); + std::optional hex; + if (hexes.size() > 0) { + hex = hexes[0]; + } + + return hex; +} + +void PyMoneroDaemonDefault::submit_block(const std::string& block_blob) { + std::vector block_blobs; + block_blobs.push_back(block_blob); + return submit_blocks(block_blobs); +} + +void PyMoneroDaemonDefault::set_peer_ban(const std::shared_ptr& ban) { + if (ban == nullptr) throw std::runtime_error("Ban is none"); + std::vector> bans; + bans.push_back(ban); + set_peer_bans(bans); +} diff --git a/src/cpp/daemon/py_monero_daemon_default.h b/src/cpp/daemon/py_monero_daemon_default.h new file mode 100644 index 0000000..96453a4 --- /dev/null +++ b/src/cpp/daemon/py_monero_daemon_default.h @@ -0,0 +1,25 @@ +#pragma once + +#include "py_monero_daemon.h" + +class PyMoneroDaemonDefault : public PyMoneroDaemon { +public: + + std::vector> get_listeners() override { return m_listeners; } + void add_listener(const std::shared_ptr &listener) override; + void remove_listener(const std::shared_ptr &listener) override; + void remove_listeners() override; + std::optional> get_tx(const std::string& tx_hash, bool prune = false) override; + void relay_tx_by_hash(std::string& tx_hash) override; + PyMoneroKeyImageSpentStatus get_key_image_spent_status(std::string& key_image) override; + std::optional get_tx_hex(const std::string& tx_hash, bool prune = false); + void submit_block(const std::string& block_blob) override; + void set_peer_ban(const std::shared_ptr& ban) override; + +protected: + mutable boost::recursive_mutex m_listeners_mutex; + std::vector> m_listeners; + + virtual void refresh_listening() { } +}; + diff --git a/src/cpp/daemon/py_monero_daemon_model.cpp b/src/cpp/daemon/py_monero_daemon_model.cpp index 2d65ba0..a51c04f 100644 --- a/src/cpp/daemon/py_monero_daemon_model.cpp +++ b/src/cpp/daemon/py_monero_daemon_model.cpp @@ -218,7 +218,7 @@ bool PyMoneroRpcConnection::check_connection(int timeout_ms) { throw std::runtime_error("Could not set rpc connection: " + m_uri.get()); } - bool connected = m_http_client->connect(std::chrono::milliseconds(timeout_ms)); + m_http_client->connect(std::chrono::milliseconds(timeout_ms)); auto start = std::chrono::high_resolution_clock::now(); PyMoneroJsonRequest request("get_version"); auto response = send_json_request(request); diff --git a/src/cpp/daemon/py_monero_daemon.cpp b/src/cpp/daemon/py_monero_daemon_rpc.cpp similarity index 93% rename from src/cpp/daemon/py_monero_daemon.cpp rename to src/cpp/daemon/py_monero_daemon_rpc.cpp index 9abaae9..047b184 100644 --- a/src/cpp/daemon/py_monero_daemon.cpp +++ b/src/cpp/daemon/py_monero_daemon_rpc.cpp @@ -1,78 +1,8 @@ -#include "py_monero_daemon.h" +#include "py_monero_daemon_rpc.h" static const uint64_t MAX_REQ_SIZE = 3000000; static const uint64_t NUM_HEADERS_PER_REQ = 750; -void PyMoneroDaemonDefault::add_listener(const std::shared_ptr &listener) { - boost::lock_guard lock(m_listeners_mutex); - m_listeners.push_back(listener); - refresh_listening(); -} - -void PyMoneroDaemonDefault::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()); - refresh_listening(); -} - -void PyMoneroDaemonDefault::remove_listeners() { - boost::lock_guard lock(m_listeners_mutex); - m_listeners.clear(); - refresh_listening(); -} - -std::optional> PyMoneroDaemonDefault::get_tx(const std::string& tx_hash, bool prune) { - std::vector hashes; - hashes.push_back(tx_hash); - auto txs = get_txs(hashes, prune); - std::optional> tx; - - if (txs.size() > 0) { - tx = txs[0]; - } - - return tx; -} - -void PyMoneroDaemonDefault::relay_tx_by_hash(std::string& tx_hash) { - std::vector tx_hashes; - tx_hashes.push_back(tx_hash); - relay_txs_by_hash(tx_hashes); -} - -PyMoneroKeyImageSpentStatus PyMoneroDaemonDefault::get_key_image_spent_status(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]; -} - -std::optional PyMoneroDaemonDefault::get_tx_hex(const std::string& tx_hash, bool prune) { - std::vector hashes; - hashes.push_back(tx_hash); - auto hexes = get_tx_hexes(hashes, prune); - std::optional hex; - if (hexes.size() > 0) { - hex = hexes[0]; - } - - return hex; -} - -void PyMoneroDaemonDefault::submit_block(const std::string& block_blob) { - std::vector block_blobs; - block_blobs.push_back(block_blob); - return submit_blocks(block_blobs); -} - -void PyMoneroDaemonDefault::set_peer_ban(const std::shared_ptr& ban) { - if (ban == nullptr) throw std::runtime_error("Ban is none"); - std::vector> bans; - bans.push_back(ban); - set_peer_bans(bans); -} - PyMoneroDaemonPoller::~PyMoneroDaemonPoller() { set_is_polling(false); } @@ -893,3 +823,4 @@ void PyMoneroDaemonRpc::check_response_status(std::shared_ptr m_last_header; + uint64_t m_poll_period_ms; + std::atomic m_is_polling; + std::thread m_thread; + + void loop(); + void poll(); + void announce_block_header(const std::shared_ptr& header); +}; + +class PyMoneroDaemonRpc : public PyMoneroDaemonDefault { +public: + PyMoneroDaemonRpc() { + m_rpc = std::make_shared(); + } + + PyMoneroDaemonRpc(std::shared_ptr rpc) { + m_rpc = rpc; + if (!rpc->is_online() && !rpc->m_uri->empty()) rpc->check_connection(); + } + + PyMoneroDaemonRpc(const std::string& uri, const std::string& username = "", const std::string& password = "") { + m_rpc = std::make_shared(); + m_rpc->m_uri = uri; + if (!username.empty() && !password.empty()) m_rpc->set_credentials(username, password); + if (!uri.empty()) m_rpc->check_connection(); + } + + ~PyMoneroDaemonRpc(); + + std::shared_ptr get_rpc_connection() const; + bool is_connected(); + monero::monero_version get_version() override; + bool is_trusted() override; + uint64_t get_height() override; + std::string get_block_hash(uint64_t height) override; + std::shared_ptr get_block_template(std::string& wallet_address, int reserve_size) override; + std::shared_ptr get_block_template(std::string& wallet_address) 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; + std::vector> get_block_headers_by_range(uint64_t start_height, uint64_t end_height) override; + std::shared_ptr get_block_by_hash(const std::string& hash) override; + std::vector> get_blocks_by_hash(const std::vector& block_hashes, uint64_t start_height, bool prune) override; + std::shared_ptr get_block_by_height(uint64_t height) override; + std::vector> get_blocks_by_height(const std::vector& heights) override; + std::vector> get_blocks_by_range(boost::optional start_height, boost::optional end_height) override; + std::vector> get_blocks_by_range_chunked(boost::optional start_height, boost::optional end_height, boost::optional max_chunk_size) override; + std::vector get_block_hashes(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(std::string& tx_hex, bool do_not_relay = false) override; + void relay_txs_by_hash(std::vector& tx_hashes) 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(std::vector& key_images) override; + std::vector> get_outputs(std::vector& outputs) override; + std::vector> get_output_histogram(std::vector amounts, int min_count, int max_count, bool is_unlocked, int 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; + int reset_download_limit() override; + 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; + 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; + 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; + 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 download_update() override; + void stop() override; + std::shared_ptr wait_for_next_block_header(); + static void check_response_status(std::shared_ptr response); + static void check_response_status(std::shared_ptr response); + +protected: + std::shared_ptr m_rpc; + 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); + void refresh_listening() override; + 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 f1b60ac..a560bf9 100644 --- a/src/cpp/py_monero.cpp +++ b/src/cpp/py_monero.cpp @@ -1,4 +1,6 @@ -#include "wallet/py_monero_wallet.h" +#include "daemon/py_monero_daemon_rpc.h" +#include "wallet/py_monero_wallet_full.h" +#include "wallet/py_monero_wallet_rpc.h" #define MONERO_CATCH_AND_RETHROW(expr) \ try { \ diff --git a/src/cpp/wallet/py_monero_wallet.cpp b/src/cpp/wallet/py_monero_wallet.cpp index 9031661..762e7fe 100644 --- a/src/cpp/wallet/py_monero_wallet.cpp +++ b/src/cpp/wallet/py_monero_wallet.cpp @@ -136,1709 +136,3 @@ void PyMoneroWallet::announce_output_received(const std::shared_ptr PyMoneroWallet::get_balances(boost::optional account_idx, boost::optional subaddress_idx) const { throw std::runtime_error("MoneroWallet::get_balances(): not implemented"); } - -void PyMoneroWalletFull::close(bool save) { - if (m_is_closed) throw std::runtime_error("Wallet already closed"); - monero::monero_wallet_full::close(save); -} - -void PyMoneroWalletFull::set_account_label(uint32_t account_idx, const std::string& label) { - set_subaddress_label(account_idx, 0, label); -} - -PyMoneroWalletPoller::~PyMoneroWalletPoller() { - set_is_polling(false); -} - -void PyMoneroWalletPoller::set_is_polling(bool is_polling) { - if (is_polling == m_is_polling) return; - m_is_polling = is_polling; - - if (m_is_polling) { - m_thread = std::thread([this]() { - loop(); - }); - m_thread.detach(); - } else { - if (m_thread.joinable()) m_thread.join(); - } -} - -void PyMoneroWalletPoller::set_period_in_ms(uint64_t period_ms) { - m_poll_period_ms = period_ms; -} - -void PyMoneroWalletPoller::poll() { - if (m_num_polling > 1) return; - m_num_polling++; - - boost::lock_guard lock(m_mutex); - try { - // skip if wallet is closed - if (m_wallet->is_closed()) { - m_num_polling--; - return; - } - - 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; - } - - // 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); - } - - m_prev_height = height; - } - - // 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_locked_txs = locked_txs; - std::vector> unlocked_txs; - - if (!no_longer_locked_hashes.empty()) { - 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); - } - - // announce new unconfirmed and confirmed txs - for (const auto &locked_tx : locked_txs) { - if (locked_tx->m_is_confirmed) { - m_prev_confirmed_notifications.push_back(locked_tx->m_hash.get()); - notify_outputs(locked_tx); - } - else { - m_prev_unconfirmed_notifications.push_back(locked_tx->m_hash.get()); - } - } - - // announce new unlocked outputs - for (const auto &unlocked_tx : unlocked_txs) { - std::string tx_hash = unlocked_tx->m_hash.get(); - 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); - } - - check_for_changed_balances(); - - 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; - } - } -} - -std::shared_ptr PyMoneroWalletPoller::get_tx(const std::vector>& txs, const std::string& tx_hash) { - for (auto tx : txs) { - if (tx->m_hash == tx_hash) return tx; - } - - return nullptr; -} - -void PyMoneroWalletPoller::loop() { - while (m_is_polling) { - try { - poll(); - } catch (const std::exception& e) { - std::cout << "ERROR " << e.what() << std::endl; - } - - std::this_thread::sleep_for(std::chrono::milliseconds(m_poll_period_ms)); - } -} - -void PyMoneroWalletPoller::on_new_block(uint64_t height) { - m_wallet->announce_new_block(height); -} - -void PyMoneroWalletPoller::notify_outputs(const std::shared_ptr &tx) { - 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; - if (outgoing_transfer->m_subaddress_indices.size() == 1) { - output->m_subaddress_index = outgoing_transfer->m_subaddress_indices[0]; - } - tx->m_inputs.push_back(output); - m_wallet->announce_output_spent(output); - } - - if (tx->m_incoming_transfers.size() > 0) { - if (!tx->m_outputs.empty()) { - for(const auto &output : tx->get_outputs_wallet()) { - m_wallet->announce_output_received(output); - } - } - else { - 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); - } - } - } -} - -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; - } - return false; -} - -PyMoneroWalletRpc::~PyMoneroWalletRpc() { -} - -boost::optional PyMoneroWalletRpc::get_rpc_connection() const { - if (m_rpc == nullptr) return boost::none; - return boost::optional(*m_rpc); -} - -PyMoneroWalletRpc* PyMoneroWalletRpc::open_wallet(const std::shared_ptr &config) { - 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); - PyMoneroJsonRequest request("open_wallet", params); - m_rpc->send_json_request(request); - clear(); - - if (config->m_connection_manager != boost::none) { - if (config->m_server != boost::none) throw std::runtime_error("Wallet can be opened with a server or connection manager but not both"); - set_connection_manager(config->m_connection_manager.get()); - } - else if (config->m_server != boost::none) { - set_daemon_connection(config->m_server); - } - - return this; -} - -PyMoneroWalletRpc* PyMoneroWalletRpc::open_wallet(const std::string& name, const std::string& password) { - auto config = std::make_shared(); - config->m_path = name; - config->m_password = password; - return open_wallet(config); -} - -PyMoneroWalletRpc* PyMoneroWalletRpc::create_wallet(const std::shared_ptr &config) { - 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)) { - throw std::runtime_error("Wallet can be initialized with a seed or keys but not both"); - } - if (config->m_account_lookahead != boost::none || config->m_subaddress_lookahead != boost::none) throw std::runtime_error("monero-wallet-rpc does not support creating wallets with subaddress lookahead over rpc"); - if (config->m_connection_manager != boost::none) { - if (config->m_server != boost::none) throw std::runtime_error("Wallet can be opened with a server or connection manager but not both"); - auto cm = config->m_connection_manager.value(); - if (cm != nullptr) { - auto connection = cm->get_connection(); - if (connection) { - config->m_server = *connection; - } - } - } - - if (config->m_seed != boost::none) create_wallet_from_seed(config); - else if (config->m_private_spend_key != boost::none || config->m_primary_address != boost::none) create_wallet_from_keys(config); - else create_wallet_random(config); - - if (config->m_connection_manager != boost::none) { - set_connection_manager(config->m_connection_manager.get()); - } - else if (config->m_server != boost::none) { - set_daemon_connection(config->m_server); - } - - return this; -} - -std::vector PyMoneroWalletRpc::get_seed_languages() const { - PyMoneroJsonRequest request("get_languages"); - std::shared_ptr response = m_rpc->send_json_request(request); - - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - std::vector languages; - - for (auto it = node.begin(); it != node.end(); ++it) { - std::string key = it->first; - - if (key == std::string("languages")) { - auto languages_node = it->second; - - for (auto it2 = languages_node.begin(); it2 != languages_node.end(); ++it2) { - languages.push_back(it2->second.data()); - } - } - } - - return languages; -} - -void PyMoneroWalletRpc::stop() { - PyMoneroJsonRequest request("stop_wallet"); - m_rpc->send_json_request(request); -} - -bool PyMoneroWalletRpc::is_view_only() const { - try { - std::string key = "mnemonic"; - query_key(key); - return false; - } - catch (const PyMoneroRpcError& e) { - if (e.code == -29) return true; - if (e.code == -1) return false; - throw; - } -} - -boost::optional PyMoneroWalletRpc::get_daemon_connection() const { - if (m_daemon_connection == nullptr) return boost::none; - return boost::optional(*m_daemon_connection); -} - -void PyMoneroWalletRpc::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 = ""; - params->m_password = ""; - } - else { - params->m_address = connection->m_uri; - params->m_username = connection->m_username; - params->m_password = connection->m_password; - } - - params->m_trusted = is_trusted; - params->m_ssl_support = "autodetect"; - - if (ssl_options != boost::none) { - params->m_ssl_private_key_path = ssl_options.get()->m_ssl_private_key_path; - params->m_ssl_certificate_path = ssl_options.get()->m_ssl_certificate_path; - params->m_ssl_ca_file = ssl_options.get()->m_ssl_ca_file; - params->m_ssl_allowed_fingerprints = ssl_options.get()->m_ssl_allowed_fingerprints; - params->m_ssl_allow_any_cert = ssl_options.get()->m_ssl_allow_any_cert; - } - - PyMoneroJsonRequest request("set_daemon", params); - m_rpc->send_json_request(request); - - if (connection == boost::none || connection->m_uri == boost::none || connection->m_uri->empty()) { - m_daemon_connection = nullptr; - } - else { - m_daemon_connection = std::make_shared(connection.get()); - } -} - -void PyMoneroWalletRpc::set_daemon_connection(const boost::optional& connection) { - set_daemon_connection(connection, false, boost::none); -} - -bool PyMoneroWalletRpc::is_connected_to_daemon() const { - try { - check_reserve_proof(get_primary_address(), "", ""); - return false; - } - catch (const PyMoneroRpcError& e) { - if (e.message == std::string("Failed to connect to daemon")) return false; - return true; - } -} - -monero::monero_version PyMoneroWalletRpc::get_version() const { - PyMoneroJsonRequest request("get_version"); - std::shared_ptr response = m_rpc->send_json_request(request); - - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto res = response->m_result.get(); - - std::shared_ptr info = std::make_shared(); - PyMoneroVersion::from_property_tree(res, info); - return *info; -} - -std::string PyMoneroWalletRpc::get_path() const { - return m_path; -} - -std::string PyMoneroWalletRpc::get_seed() const { - std::string key = "mnemonic"; - return query_key(key); -} - -std::string PyMoneroWalletRpc::get_seed_language() const { - throw std::runtime_error("MoneroWalletRpc::get_seed_language() not supported"); -} - -std::string PyMoneroWalletRpc::get_public_view_key() const { - std::string key = "public_view_key"; - return query_key(key); -} - -std::string PyMoneroWalletRpc::get_private_view_key() const { - std::string key = "view_key"; - return query_key(key); -} - -std::string PyMoneroWalletRpc::get_public_spend_key() const { - std::string key = "public_spend_key"; - return query_key(key); -} - -std::string PyMoneroWalletRpc::get_private_spend_key() const { - 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 { - auto it = m_address_cache.find(account_idx); - std::vector empty_indices; - if (it == m_address_cache.end()) { - get_subaddresses(account_idx, empty_indices, true); - return get_address(account_idx, subaddress_idx); - } - - auto subaddress_map = it->second; - auto it2 = subaddress_map.find(subaddress_idx); - - if (it2 == subaddress_map.end()) { - get_subaddresses(account_idx, empty_indices, true); - auto it3 = m_address_cache.find(account_idx); - if (it3 == m_address_cache.end()) throw std::runtime_error("Could not find account address at index (" + std::to_string(account_idx) + ", " + std::to_string(subaddress_idx) + ")" ); - auto it4 = it3->second.find(subaddress_idx); - if (it4 == it3->second.end()) throw std::runtime_error("Could not find address at index (" + std::to_string(account_idx) + ", " + std::to_string(subaddress_idx) + ")" ); - return it4->second; - } - - return it2->second; -} - -monero_subaddress PyMoneroWalletRpc::get_address_index(const std::string& address) const { - auto params = std::make_shared(address); - PyMoneroJsonRequest request("get_address_index", params); - std::shared_ptr response = m_rpc->send_json_request(request); - - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto res = response->m_result.get(); - - 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); - PyMoneroJsonRequest request("make_integrated_address", params); - std::shared_ptr response = m_rpc->send_json_request(request); - - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto res = response->m_result.get(); - - 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); - PyMoneroJsonRequest request("split_integrated_address", params); - std::shared_ptr response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto res = response->m_result.get(); - auto tmplt = std::make_shared(); - PyMoneroIntegratedAddress::from_property_tree(res, tmplt); - tmplt->m_integrated_address = integrated_address; - return *tmplt; -} - -uint64_t PyMoneroWalletRpc::get_height() const { - PyMoneroJsonRequest request("get_height"); - std::shared_ptr response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto res = response->m_result.get(); - return PyMoneroWalletGetHeightResponse::from_property_tree(res); -} - -uint64_t PyMoneroWalletRpc::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 { - throw std::runtime_error("monero-wallet-rpc does not support getting a height by date"); -} - -monero_sync_result PyMoneroWalletRpc::sync() { - auto params = std::make_shared(); - boost::lock_guard lock(m_sync_mutex); - PyMoneroJsonRequest request("refresh", params); - poll(); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - monero_sync_result sync_result(0, false); - - for (auto it = node.begin(); it != node.end(); ++it) { - std::string key = it->first; - - if (key == std::string("blocks_fetched")) sync_result.m_num_blocks_fetched = it->second.get_value(); - else if (key == std::string("received_money")) sync_result.m_received_money = it->second.get_value(); - } - - return sync_result; -} - -monero_sync_result PyMoneroWalletRpc::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) { - 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); - boost::lock_guard lock(m_sync_mutex); - PyMoneroJsonRequest request("refresh", params); - auto response = m_rpc->send_json_request(request); - poll(); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - monero_sync_result sync_result(0, false); - - for (auto it = node.begin(); it != node.end(); ++it) { - std::string key = it->first; - - if (key == std::string("blocks_fetched")) sync_result.m_num_blocks_fetched = it->second.get_value(); - else if (key == std::string("received_money")) sync_result.m_received_money = it->second.get_value(); - } - - return sync_result; -} - -void PyMoneroWalletRpc::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); - PyMoneroJsonRequest request("auto_refresh", params); - auto response = m_rpc->send_json_request(request); - - // update sync period for poller - m_sync_period_in_ms = sync_period_in_seconds * 1000; - if (m_poller != nullptr) m_poller->set_period_in_ms(m_sync_period_in_ms.get()); - - // poll if listening - poll(); -} - -void PyMoneroWalletRpc::stop_syncing() { - auto params = std::make_shared(false); - PyMoneroJsonRequest request("auto_refresh", params); - m_rpc->send_json_request(request); -} - -void PyMoneroWalletRpc::scan_txs(const std::vector& tx_hashes) { - if (tx_hashes.empty()) throw std::runtime_error("No tx hashes given to scan"); - auto params = std::make_shared(tx_hashes); - PyMoneroJsonRequest request("scan_tx", params); - m_rpc->send_json_request(request); - poll(); -} - -void PyMoneroWalletRpc::rescan_spent() { - PyMoneroJsonRequest request("rescan_spent"); - m_rpc->send_json_request(request); -} - -void PyMoneroWalletRpc::rescan_blockchain() { - PyMoneroJsonRequest request("rescan_blockchain"); - m_rpc->send_json_request(request); -} - -uint64_t PyMoneroWalletRpc::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 { - 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 { - auto wallet_balance = get_balances(account_idx, subaddress_idx); - return wallet_balance->m_balance; -} - -uint64_t PyMoneroWalletRpc::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 { - 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 { - 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 { - 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 { - for(auto& account : monero::monero_wallet::get_accounts()) { - if (account.m_index.get() == account_idx) { - std::vector empty_indices; - if (include_subaddresses) account.m_subaddresses = get_subaddresses(account_idx, empty_indices, skip_balances); - return account; - } - } - 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 { - 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); - PyMoneroJsonRequest request("get_accounts", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - std::vector accounts; - PyMoneroAccount::from_property_tree(node, accounts); - if (include_subaddresses) { - - for (auto &account : accounts) { - std::vector empty_indices; - account.m_subaddresses = get_subaddresses(account.m_index.get(), empty_indices, true); - - if (!skip_balances) { - for (auto &subaddress : account.m_subaddresses) { - subaddress.m_balance = 0; - subaddress.m_unlocked_balance = 0; - subaddress.m_num_unspent_outputs = 0; - subaddress.m_num_blocks_to_unlock = 0; - } - } - } - - if (!skip_balances) { - auto params2 = std::make_shared(true); - PyMoneroJsonRequest request2("get_balance", params2); - auto response2 = m_rpc->send_json_request(request2); - if (response2->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node2 = response2->m_result.get(); - auto bal_res = std::make_shared(); - PyMoneroGetBalanceResponse::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()]; - if (account->m_index != subaddress->m_account_index) throw std::runtime_error("RPC accounts are out of order"); - auto tgt_subaddress = &account->m_subaddresses[subaddress->m_account_index.get()]; - if (tgt_subaddress->m_index != subaddress->m_index) throw std::runtime_error("RPC subaddresses are out of order"); - - if (subaddress->m_balance != boost::none) tgt_subaddress->m_balance = subaddress->m_balance; - if (subaddress->m_unlocked_balance != boost::none) tgt_subaddress->m_unlocked_balance = subaddress->m_unlocked_balance; - if (subaddress->m_num_unspent_outputs != boost::none) tgt_subaddress->m_num_unspent_outputs = subaddress->m_num_unspent_outputs; - if (subaddress->m_num_blocks_to_unlock != boost::none) tgt_subaddress->m_num_blocks_to_unlock = subaddress->m_num_blocks_to_unlock; - } - } - } - - return accounts; -} - -monero_account PyMoneroWalletRpc::create_account(const std::string& label) { - auto params = std::make_shared(label); - PyMoneroJsonRequest request("create_account", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - monero_account res; - res.m_balance = 0; - res.m_unlocked_balance = 0; - bool found_index = false; - bool address_found = false; - - for (auto it = node.begin(); it != node.end(); ++it) { - std::string key = it->first; - - if (key == std::string("account_index")) { - found_index = true; - res.m_index = it->second.get_value(); - } - else if (key == std::string("address")) { - address_found = true; - res.m_primary_address = it->second.data(); - } - } - - if (!found_index || !address_found) throw std::runtime_error("Could not create account"); - - return res; -} - -std::vector PyMoneroWalletRpc::get_subaddresses(const uint32_t account_idx, const std::vector& subaddress_indices, bool skip_balances) const { - // fetch subaddresses - auto params = std::make_shared(account_idx, subaddress_indices); - PyMoneroJsonRequest request("get_address", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - - std::vector subaddresses; - - // initialize subaddresses - for (auto it = node.begin(); it != node.end(); ++it) { - std::string key = it->first; - - if (key == std::string("addresses")) { - auto node2 = it->second; - for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) { - auto subaddress = std::make_shared(); - PyMoneroSubaddress::from_rpc_property_tree(it2->second, subaddress); - subaddress->m_account_index = account_idx; - subaddresses.push_back(*subaddress); - } - break; - } - - } - - // fetch and initialize subaddress balances - if (!skip_balances) { - - // these fields are not initialized if subaddress is unused and therefore not returned from `get_balance` - for (auto &subaddress : subaddresses) { - subaddress.m_balance = 0; - subaddress.m_unlocked_balance = 0; - subaddress.m_num_unspent_outputs = 0; - subaddress.m_num_blocks_to_unlock = 0; - } - - // fetch and initialize balances - PyMoneroJsonRequest request2("get_balance", params); - auto response2 = m_rpc->send_json_request(request); - if (response2->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node2 = response2->m_result.get(); - - std::vector> subaddresses2; - PyMoneroSubaddress::from_rpc_property_tree(node2, subaddresses2); - - for (auto &tgt_subaddress: subaddresses) { - for (const auto &rpc_subaddress : subaddresses2) { - if (rpc_subaddress->m_index != tgt_subaddress.m_index) continue; // skip to subaddress with same index - if (rpc_subaddress->m_balance != boost::none) tgt_subaddress.m_balance = rpc_subaddress->m_balance; - if (rpc_subaddress->m_unlocked_balance != boost::none) tgt_subaddress.m_unlocked_balance = rpc_subaddress->m_unlocked_balance; - if (rpc_subaddress->m_num_unspent_outputs != boost::none) tgt_subaddress.m_num_unspent_outputs = rpc_subaddress->m_num_unspent_outputs; - if (rpc_subaddress->m_num_blocks_to_unlock != boost::none) tgt_subaddress.m_num_blocks_to_unlock = rpc_subaddress->m_num_blocks_to_unlock; - } - } - } - - // cache addresses - auto it = m_address_cache.find(account_idx); - if (it == m_address_cache.end()) { - m_address_cache[account_idx] = serializable_unordered_map(); - } - - for (const auto& subaddress : subaddresses) { - m_address_cache[account_idx][subaddress.m_index.get()] = subaddress.m_address.get(); - } - - // return results - return subaddresses; -} - -std::vector PyMoneroWalletRpc::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 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 { - std::vector subaddress_indices; - subaddress_indices.push_back(subaddress_idx); - auto subaddresses = get_subaddresses(account_idx, subaddress_indices); - if (subaddresses.empty()) throw std::runtime_error("Subaddress is not initialized"); - if (subaddresses.size() != 1) throw std::runtime_error("Only 1 subaddress should be returned"); - return subaddresses[0]; -} - -monero_subaddress PyMoneroWalletRpc::create_subaddress(uint32_t account_idx, const std::string& label) { - auto params = std::make_shared(account_idx, label); - PyMoneroJsonRequest request("create_address", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - monero_subaddress sub; - sub.m_account_index = account_idx; - if (!label.empty()) sub.m_label = label; - sub.m_balance = 0; - sub.m_unlocked_balance = 0; - sub.m_num_unspent_outputs = 0; - sub.m_is_used = false; - sub.m_num_blocks_to_unlock = 0; - - for(auto it = node.begin(); it != node.end(); ++it) { - std::string key = it->first; - if (key == std::string("address_index")) sub.m_index = it->second.get_value(); - else if (key == std::string("address")) sub.m_address = it->second.data(); - } - - 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); - PyMoneroJsonRequest request("label_address", params); - m_rpc->send_json_request(request); -} - -std::string PyMoneroWalletRpc::export_outputs(bool all) const { - auto params = std::make_shared(all); - PyMoneroJsonRequest request("export_outputs", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - - for (auto it = node.begin(); it != node.end(); ++it) { - std::string key = it->first; - - if (key == std::string("outputs_data_hex")) return it->second.data(); - } - - 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); - PyMoneroJsonRequest request("import_outputs", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - - for (auto it = node.begin(); it != node.end(); ++it) { - std::string key = it->first; - - if (key == std::string("num_imported")) return it->second.get_value(); - } - - return 0; -} - -std::vector> PyMoneroWalletRpc::export_key_images(bool all) const { - auto params = std::make_shared(all); - PyMoneroJsonRequest request("export_key_images", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - 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); - PyMoneroJsonRequest request("import_key_images", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - auto import_result = std::make_shared(); - PyMoneroKeyImageImportResult::from_property_tree(node, import_result); - return import_result; -} - -std::vector> PyMoneroWalletRpc::get_new_key_images_from_last_import() { - throw std::runtime_error("get_new_key_images_from_last_import(): not implemented"); -} - -void PyMoneroWalletRpc::freeze_output(const std::string& key_image) { - auto params = std::make_shared(key_image); - PyMoneroJsonRequest request("freeze", params); - m_rpc->send_json_request(request); -} - -void PyMoneroWalletRpc::thaw_output(const std::string& key_image) { - auto params = std::make_shared(key_image); - PyMoneroJsonRequest request("thaw", params); - m_rpc->send_json_request(request); -} - -bool PyMoneroWalletRpc::is_output_frozen(const std::string& key_image) { - auto params = std::make_shared(key_image); - PyMoneroJsonRequest request("frozen", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - - for(auto it = node.begin(); it != node.end(); ++it) { - std::string key = it->first; - - if (key == std::string("frozen")) return it->second.get_value(); - } - - throw std::runtime_error("Could not get output"); -} - -monero_tx_priority PyMoneroWalletRpc::get_default_fee_priority() const { - PyMoneroJsonRequest request("get_default_fee_priority"); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - - for(auto it = node.begin(); it != node.end(); ++it) { - std::string key = it->first; - - if (key == std::string("priority")) { - int priority = it->second.get_value(); - - if (priority == 0) return monero_tx_priority::DEFAULT; - else if (priority == 1) return monero_tx_priority::UNIMPORTANT; - else if (priority == 2) return monero_tx_priority::NORMAL; - else if (priority == 3) return monero_tx_priority::ELEVATED; - } - } - - throw std::runtime_error("Could not get default fee priority"); -} - -std::vector> PyMoneroWalletRpc::create_txs(const monero_tx_config& conf) { - // validate, copy, and normalize request - monero_tx_config config = conf; - if (config.m_destinations.empty()) throw std::runtime_error("Destinations cannot be empty"); - if (config.m_sweep_each_subaddress != boost::none) throw std::runtime_error("Sweep each subaddress not supported"); - if (config.m_below_amount != boost::none) throw std::runtime_error("Below amount not supported"); - - if (config.m_can_split == boost::none) { - config = config.copy(); - config.m_can_split = true; - } - if (config.m_relay == true && is_multisig()) throw std::runtime_error("Cannot relay multisig transaction until co-signed"); - - // determine account and subaddresses to send from - if (config.m_account_index == boost::none) throw std::runtime_error("Must specify the account index to send from"); - auto account_idx = config.m_account_index.get(); - - // cannot apply subtractFeeFrom with `transfer_split` call - if (config.m_can_split && config.m_subtract_fee_from.size() > 0) { - throw std::runtime_error("subtractfeefrom transfers cannot be split over multiple transactions yet"); - } - - // build request parameters - auto params = std::make_shared(config); - std::string request_path = "transfer"; - if (config.m_can_split) request_path = "transfer_split"; - - PyMoneroJsonRequest request(request_path, params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - - // pre-initialize txs iff present. multisig and view-only wallets will have tx set without transactions - std::vector> txs; - int num_txs = 0; - - for(auto it = node.begin(); it != node.end(); ++it) { - std::string key = it->first; - - if (config.m_can_split && key == std::string("fee_list")) { - auto fee_list_node = it->second; - - for(auto it2 = fee_list_node.begin(); it2 != fee_list_node.end(); ++it2) { - num_txs++; - } - } - } - - bool copy_destinations = num_txs == 1; - for (int i = 0; i < num_txs; i++) { - auto tx = std::make_shared(); - PyMoneroTxWallet::init_sent(config, tx, copy_destinations); - tx->m_outgoing_transfer.get()->m_account_index = account_idx; - - if (config.m_subaddress_indices.size() == 1) { - tx->m_outgoing_transfer.get()->m_subaddress_indices = config.m_subaddress_indices; - } - - txs.push_back(tx); - } - - // notify of changes - if (config.m_relay) poll(); - - // initialize tx set from rpc response with pre-initialized txs - auto tx_set = std::make_shared(); - if (config.m_can_split) { - PyMoneroTxSet::from_sent_txs(node, tx_set, txs, config); - } - else { - if (txs.empty()) { - auto __tx = std::make_shared(); - PyMoneroTxSet::from_tx(node, tx_set, __tx, true, config); - } - else { - PyMoneroTxSet::from_tx(node, tx_set, txs[0], true, config); - } - } - - return tx_set->m_txs; -} - -std::shared_ptr PyMoneroWalletRpc::sweep_output(const monero_tx_config& config) { - auto params = std::make_shared(config); - PyMoneroJsonRequest request("sweep_single", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - if (config.m_relay) poll(); - auto set = std::make_shared(); - auto tx = std::make_shared(); - PyMoneroTxWallet::init_sent(config, tx, true); - PyMoneroTxSet::from_tx(node, set, tx, true, config); - return tx; -} - -std::vector> PyMoneroWalletRpc::sweep_dust(bool relay) { - auto params = std::make_shared(relay); - PyMoneroJsonRequest request("sweep_dust", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - auto set = std::make_shared(); - PyMoneroTxSet::from_sent_txs(node, set); - return set->m_txs; -} - -std::vector PyMoneroWalletRpc::relay_txs(const std::vector& tx_metadatas) { - 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); - PyMoneroJsonRequest request("relay_tx", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - - for (auto it = node.begin(); it != node.end(); ++it) { - std::string key = it->first; - if (key == std::string("tx_hash")) tx_hashes.push_back(it->second.data()); - } - } - - return tx_hashes; -} - -monero_tx_set PyMoneroWalletRpc::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; - PyMoneroJsonRequest request("describe_transfer", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - auto set = std::make_shared(); - PyMoneroTxSet::from_describe_transfer(node, set); - return *set; -} - -monero_tx_set PyMoneroWalletRpc::sign_txs(const std::string& unsigned_tx_hex) { - auto params = std::make_shared(unsigned_tx_hex); - PyMoneroJsonRequest request("sign_transfer", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - auto 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); - PyMoneroJsonRequest request("submit_transfer", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - poll(); - std::vector hashes; - - for (auto it = node.begin(); it != node.end(); ++it) { - std::string key = it->first; - - if (key == std::string("tx_hash_list")) { - auto hashes_node = it->second; - - for (auto it2 = hashes_node.begin(); it2 != hashes_node.end(); ++it2) { - hashes.push_back(it2->second.data()); - } - } - } - - 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); - PyMoneroJsonRequest request("sign", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - return PyMoneroReserveProofSignature::from_property_tree(node); -} - -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); - PyMoneroJsonRequest request("verify", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - auto sig_result = std::make_shared(); - PyMoneroMessageSignatureResult::from_property_tree(node, sig_result); - return *sig_result; -} - -std::string PyMoneroWalletRpc::get_tx_key(const std::string& tx_hash) const { - auto params = std::make_shared(tx_hash); - PyMoneroJsonRequest request("get_tx_key", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - for (auto it = node.begin(); it != node.end(); ++it) { - std::string key = it->first; - - if (key == std::string("tx_key")) { - return it->second.data(); - } - } - - 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 { - auto params = std::make_shared(tx_hash, tx_key, address); - PyMoneroJsonRequest request("check_tx_key", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - auto check = std::make_shared(); - PyMoneroCheckTxProof::from_property_tree(node, check); - return check; -} - -std::string PyMoneroWalletRpc::get_tx_proof(const std::string& tx_hash, const std::string& address, const std::string& message) const { - auto params = std::make_shared(tx_hash, message); - params->m_address = address; - PyMoneroJsonRequest request("get_tx_proof", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - return PyMoneroReserveProofSignature::from_property_tree(node); -} - -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 { - auto params = std::make_shared(tx_hash, address, message, signature); - PyMoneroJsonRequest request("check_tx_proof", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - auto check = std::make_shared(); - PyMoneroCheckTxProof::from_property_tree(node, check); - return check; -} - -std::string PyMoneroWalletRpc::get_spend_proof(const std::string& tx_hash, const std::string& message) const { - auto params = std::make_shared(tx_hash, message); - PyMoneroJsonRequest request("get_spend_proof", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - return PyMoneroReserveProofSignature::from_property_tree(node); -} - -bool PyMoneroWalletRpc::check_spend_proof(const std::string& tx_hash, const std::string& message, const std::string& signature) const { - auto params = std::make_shared(tx_hash, message, signature); - PyMoneroJsonRequest request("check_spend_proof", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - auto proof = std::make_shared(); - PyMoneroCheckReserve::from_property_tree(node, proof); - return proof->m_is_good; -} - -std::string PyMoneroWalletRpc::get_reserve_proof_wallet(const std::string& message) const { - auto params = std::make_shared(message); - PyMoneroJsonRequest request("get_reserve_proof", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - return PyMoneroReserveProofSignature::from_property_tree(node); -} - -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); - PyMoneroJsonRequest request("get_reserve_proof", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - return PyMoneroReserveProofSignature::from_property_tree(node); -} - -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); - PyMoneroJsonRequest request("check_reserve_proof", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - auto proof = std::make_shared(); - PyMoneroCheckReserve::from_property_tree(node, proof); - return proof; -} - -std::vector PyMoneroWalletRpc::get_tx_notes(const std::vector& tx_hashes) const { - auto params = std::make_shared(tx_hashes); - PyMoneroJsonRequest request("get_tx_notes", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - std::vector notes; - - for (auto it = node.begin(); it != node.end(); ++it) { - std::string key = it->first; - - if (key == std::string("notes")) { - auto notes_node = it->second; - - for (auto it2 = notes_node.begin(); it2 != notes_node.end(); ++it2) { - notes.push_back(it2->second.data()); - } - } - } - - return notes; -} - -void PyMoneroWalletRpc::set_tx_notes(const std::vector& tx_hashes, const std::vector& notes) { - auto params = std::make_shared(tx_hashes, notes); - PyMoneroJsonRequest request("set_tx_notes", params); - m_rpc->send_json_request(request); -} - -std::vector PyMoneroWalletRpc::get_address_book_entries(const std::vector& indices) const { - auto params = std::make_shared(indices); - PyMoneroJsonRequest request("get_address_book", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - std::vector> entries_ptr; - PyMoneroAddressBookEntry::from_property_tree(node, entries_ptr); - std::vector entries; - - for (const auto &entry : entries_ptr) { - entries.push_back(*entry); - } - - return entries; -} - -uint64_t PyMoneroWalletRpc::add_address_book_entry(const std::string& address, const std::string& description) { - auto params = std::make_shared(address, description); - PyMoneroJsonRequest request("add_address_book", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - - for (auto it = node.begin(); it != node.end(); ++it) { - std::string key = it->first; - - if (key == std::string("index")) { - return it->second.get_value(); - } - } - - 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); - PyMoneroJsonRequest request("edit_address_book", params); - m_rpc->send_json_request(request); -} - -void PyMoneroWalletRpc::delete_address_book_entry(uint64_t index) { - auto params = std::make_shared(index); - PyMoneroJsonRequest request("delete_address_book", params); - m_rpc->send_json_request(request); -} - -void PyMoneroWalletRpc::tag_accounts(const std::string& tag, const std::vector& account_indices) { - auto params = std::make_shared(tag, account_indices); - PyMoneroJsonRequest request("tag_accounts", params); - m_rpc->send_json_request(request); -} - -void PyMoneroWalletRpc::untag_accounts(const std::vector& account_indices) { - auto params = std::make_shared(account_indices); - PyMoneroJsonRequest request("untag_accounts", params); - m_rpc->send_json_request(request); -} - -std::vector> PyMoneroWalletRpc::get_account_tags() { - PyMoneroJsonRequest request("get_account_tags"); - std::shared_ptr response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto res = response->m_result.get(); - std::vector> account_tags; - PyMoneroAccountTag::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); - PyMoneroJsonRequest request("set_account_tag_description", params); - m_rpc->send_json_request(request); -} - -void PyMoneroWalletRpc::set_account_label(uint32_t account_index, const std::string& label) { - set_subaddress_label(account_index, 0, label); -} - -std::string PyMoneroWalletRpc::get_payment_uri(const monero_tx_config& config) const { - auto params = std::make_shared(config); - PyMoneroJsonRequest request("make_uri", params); - std::shared_ptr response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto res = response->m_result.get(); - return PyMoneroGetPaymentUriResponse::from_property_tree(res); -} - -std::shared_ptr PyMoneroWalletRpc::parse_payment_uri(const std::string& uri) const { - auto params = std::make_shared(uri); - PyMoneroJsonRequest request("parse_uri", params); - std::shared_ptr response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto res = response->m_result.get(); - auto uri_response = std::make_shared(); - PyMoneroParsePaymentUriResponse::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); - PyMoneroJsonRequest request("set_attribute", params); - m_rpc->send_json_request(request); -} - -bool PyMoneroWalletRpc::get_attribute(const std::string& key, std::string& value) const { - try { - auto params = std::make_shared(key); - PyMoneroJsonRequest request("get_attribute", params); - std::shared_ptr response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto res = response->m_result.get(); - PyMoneroWalletAttributeParams::from_property_tree(res, params); - if (params->m_value == boost::none) return false; - value = params->m_value.get(); - return true; - } - catch (...) { - 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)); - PyMoneroJsonRequest request("start_mining", params); - auto response = m_rpc->send_json_request(request); - PyMoneroDaemonRpc::check_response_status(response); -} - -void PyMoneroWalletRpc::stop_mining() { - PyMoneroJsonRequest request("stop_mining"); - m_rpc->send_json_request(request); -} - -bool PyMoneroWalletRpc::is_multisig_import_needed() const { - PyMoneroJsonRequest request("get_balance"); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto res = response->m_result.get(); - auto balance = std::make_shared(); - PyMoneroGetBalanceResponse::from_property_tree(res, balance); - if (balance->m_multisig_import_needed) return true; - return false; -} - -monero_multisig_info PyMoneroWalletRpc::get_multisig_info() const { - PyMoneroJsonRequest request("is_multisig"); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto res = response->m_result.get(); - auto info = std::make_shared(); - PyMoneroMultisigInfo::from_property_tree(res, info); - return *info; -} - -std::string PyMoneroWalletRpc::prepare_multisig() { - auto params = std::make_shared(); - PyMoneroJsonRequest request("prepare_multisig", params); - auto response = m_rpc->send_json_request(request); - clear_address_cache(); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto res = response->m_result.get(); - return PyMoneroPrepareMakeMultisigResponse::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); - PyMoneroJsonRequest request("make_multisig", params); - auto response = m_rpc->send_json_request(request); - clear_address_cache(); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto res = response->m_result.get(); - return PyMoneroPrepareMakeMultisigResponse::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); - PyMoneroJsonRequest request("exchange_multisig_keys", params); - auto response = m_rpc->send_json_request(request); - clear_address_cache(); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto res = response->m_result.get(); - auto multisig_init = std::make_shared(); - PyMoneroMultisigInitResult::from_property_tree(res, multisig_init); - return *multisig_init; -} - -std::string PyMoneroWalletRpc::export_multisig_hex() { - PyMoneroJsonRequest request("export_multisig_info"); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto res = response->m_result.get(); - return PyMoneroExportMultisigHexResponse::from_property_tree(res); -} - -int PyMoneroWalletRpc::import_multisig_hex(const std::vector& multisig_hexes) { - auto params = std::make_shared(multisig_hexes); - PyMoneroJsonRequest request("import_multisig_info", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto res = response->m_result.get(); - return PyMoneroImportMultisigHexResponse::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); - PyMoneroJsonRequest request("sign_multisig", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto res = response->m_result.get(); - 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); - PyMoneroJsonRequest request("submit_multisig", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto res = response->m_result.get(); - return PyMoneroSubmitMultisigTxHexResponse::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); - PyMoneroJsonRequest request("change_wallet_password", params); - m_rpc->send_json_request(request); -} - -void PyMoneroWalletRpc::save() { - PyMoneroJsonRequest request("store"); - m_rpc->send_json_request(request); -} - -void PyMoneroWalletRpc::close(bool save) { - auto params = std::make_shared(save); - PyMoneroJsonRequest request("close_wallet", params); - m_rpc->send_json_request(request); -} - -std::shared_ptr PyMoneroWalletRpc::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"); - - auto accounts = monero::monero_wallet::get_accounts(); - - for(const auto &account : accounts) { - balance->m_balance += account.m_balance.get(); - balance->m_unlocked_balance += account.m_unlocked_balance.get(); - } - - return balance; - } - else { - auto params = std::make_shared(account_idx.get(), subaddress_idx); - PyMoneroJsonRequest request("get_balance", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto res = response->m_result.get(); - auto bal_res = std::make_shared(); - PyMoneroGetBalanceResponse::from_property_tree(res, bal_res); - - if (subaddress_idx == boost::none) { - balance->m_balance = bal_res->m_balance.get(); - balance->m_unlocked_balance = bal_res->m_unlocked_balance.get(); - return balance; - } - else if (bal_res->m_per_subaddress.size() > 0) { - auto sub = bal_res->m_per_subaddress[0]; - balance->m_balance = sub->m_balance.get(); - balance->m_unlocked_balance = sub->m_unlocked_balance.get(); - } - } - - return balance; -} - -PyMoneroWalletRpc* PyMoneroWalletRpc::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"); - if (config.m_restore_height != boost::none) throw std::runtime_error("Cannot specify restore height when creating random wallet"); - if (config.m_save_current != boost::none && config.m_save_current == false) throw std::runtime_error("Current wallet is saved automatically when creating random wallet"); - if (config.m_path == boost::none || config.m_path->empty()) throw std::runtime_error("Wallet name is not initialized"); - if (config.m_language == boost::none || config.m_language->empty()) config.m_language = "English"; - - // send request - std::string filename = config.m_path.get(); - std::string password = config.m_password.get(); - std::string language = config.m_language.get(); - - auto params = std::make_shared(filename, password, language); - PyMoneroJsonRequest request("create_wallet", params); - m_rpc->send_json_request(request); - clear(); - m_path = config.m_path.get(); - return this; -} - -PyMoneroWalletRpc* PyMoneroWalletRpc::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; - auto password = config.m_password; - auto seed = config.m_seed; - auto seed_offset = config.m_seed_offset; - auto restore_height = config.m_restore_height; - auto language = config.m_language; - bool autosave_current = false; - 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); - PyMoneroJsonRequest request("restore_deterministic_wallet", params); - m_rpc->send_json_request(request); - clear(); - m_path = config.m_path.get(); - return this; -} - -PyMoneroWalletRpc* PyMoneroWalletRpc::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(); - std::string password = config->m_password.get(); - std::string address = config->m_primary_address.get(); - std::string view_key = ""; - std::string spend_key = ""; - if (config->m_private_view_key != boost::none) view_key = config->m_private_view_key.get(); - if (config->m_private_spend_key != boost::none) spend_key = config->m_private_spend_key.get(); - 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); - PyMoneroJsonRequest request("generate_from_keys", params); - m_rpc->send_json_request(request); - 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); - PyMoneroJsonRequest request("query_key", params); - std::shared_ptr response = m_rpc->send_json_request(request); - - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - - for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { - std::string key = it->first; - if (key == std::string("key")) return it->second.data(); - } - - throw std::runtime_error(std::string("Cloud not query key: ") + key_type); -} - -std::vector> PyMoneroWalletRpc::sweep_account(const monero_tx_config &conf) { - auto config = conf.copy(); - if (config.m_account_index == boost::none) throw std::runtime_error("Must specify an account index to sweep from"); - if (config.m_destinations.size() != 1) throw std::runtime_error("Must specify exactly one destination to sweep to"); - if (config.m_destinations[0]->m_address == boost::none) throw std::runtime_error("Must specify destination address to sweep to"); - if (config.m_destinations[0]->m_amount != boost::none) throw std::runtime_error("Cannot specify amount in sweep request"); - if (config.m_key_image != boost::none) throw std::runtime_error("Key image defined; use sweepOutput() to sweep an output by its key image"); - //if (config.m_subaddress_indices.size() == 0) throw std::runtime_error("Empty list given for subaddresses indices to sweep"); - if (config.m_sweep_each_subaddress) throw std::runtime_error("Cannot sweep each subaddress with RPC `sweep_all`"); - if (config.m_subtract_fee_from.size() > 0) throw std::runtime_error("Sweeping output does not support subtracting fees from destinations"); - - // sweep from all subaddresses if not otherwise defined - if (config.m_subaddress_indices.empty()) { - uint32_t account_idx = config.m_account_index.get(); - auto subaddresses = get_subaddresses(account_idx); - for (const auto &subaddress : subaddresses) { - config.m_subaddress_indices.push_back(subaddress.m_index.get()); - } - } - 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); - PyMoneroJsonRequest request("sweep_all", params); - auto response = m_rpc->send_json_request(request); - if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); - auto node = response->m_result.get(); - if (config.m_relay) poll(); - std::vector> txs; - auto set = std::make_shared(); - PyMoneroTxSet::from_sent_txs(node, set, txs, config); - - for (auto &tx : set->m_txs) { - tx->m_is_locked = true; - tx->m_is_confirmed = false; - tx->m_num_confirmations = 0; - tx->m_relay = relay; - tx->m_in_tx_pool = relay; - tx->m_is_relayed = relay; - tx->m_is_miner_tx = false; - tx->m_is_failed = false; - tx->m_ring_size = monero_utils::RING_SIZE; - auto transfer = tx->m_outgoing_transfer.get(); - transfer->m_account_index = config.m_account_index; - if (config.m_subaddress_indices.size() == 1) - { - transfer->m_subaddress_indices = config.m_subaddress_indices; - } - auto destination = std::make_shared(); - destination->m_address = config.m_destinations[0]->m_address; - destination->m_amount = config.m_destinations[0]->m_amount; - std::vector> destinations; - destinations.push_back(destination); - transfer->m_destinations = destinations; - tx->m_payment_id = config.m_payment_id; - if (tx->m_unlock_time == boost::none) tx->m_unlock_time = 0; - if (tx->m_relay) { - if (tx->m_last_relayed_timestamp == boost::none) { - //tx.setLastRelayedTimestamp(System.currentTimeMillis()); // TODO (monero-wallet-rpc): provide timestamp on response; unconfirmed timestamps vary - } - if (tx->m_is_double_spend_seen == boost::none) tx->m_is_double_spend_seen = false; - } - } - - return set->m_txs; -} - -void PyMoneroWalletRpc::clear_address_cache() { - m_address_cache.clear(); -} - -void PyMoneroWalletRpc::refresh_listening() { - if (m_rpc->m_zmq_uri == boost::none) { - if (m_poller == nullptr && m_listeners.size() > 0) m_poller = std::make_shared(this); - if (m_poller != nullptr) m_poller->set_is_polling(m_listeners.size() > 0); - } - /* - else { - if (m_zmq_listener == nullptr && m_listeners.size() > 0) m_zmq_listener = std::make_shared(); - if (m_zmq_listener != nullptr) m_zmq_listener.set_is_polling(m_listeners.size() > 0); - } - */ -} - -void PyMoneroWalletRpc::poll() { - if (m_poller != nullptr && m_poller->is_polling()) m_poller->poll(); -} - -void PyMoneroWalletRpc::clear() { - m_listeners.clear(); - refresh_listening(); - clear_address_cache(); - m_path = ""; -} diff --git a/src/cpp/wallet/py_monero_wallet.h b/src/cpp/wallet/py_monero_wallet.h index d1da70e..b80f0ae 100644 --- a/src/cpp/wallet/py_monero_wallet.h +++ b/src/cpp/wallet/py_monero_wallet.h @@ -566,208 +566,3 @@ class PyMoneroWallet : public monero::monero_wallet { std::shared_ptr m_connection_manager_listener; std::set m_listeners; }; - -class PyMoneroWalletFull : public monero::monero_wallet_full { -public: - - bool is_closed() const { return m_is_closed; } - void close(bool save = false) override; - void set_account_label(uint32_t account_idx, const std::string& label); - - std::vector> get_new_key_images_from_last_import() { - throw std::runtime_error("get_new_key_images_from_last_import(): not implemented"); - } - -protected: - bool m_is_closed = false; -}; - -class PyMoneroWalletPoller { -public: - explicit PyMoneroWalletPoller(PyMoneroWallet *wallet) { - m_wallet = wallet; - m_is_polling = false; - m_num_polling = 0; - } - - ~PyMoneroWalletPoller(); - - 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 poll(); - -protected: - mutable boost::recursive_mutex m_mutex; - PyMoneroWallet *m_wallet; - std::atomic m_is_polling; - uint64_t m_poll_period_ms; - std::thread m_thread; - int 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 loop(); - void on_new_block(uint64_t height); - void notify_outputs(const std::shared_ptr &tx); - bool check_for_changed_balances(); -}; - -class PyMoneroWalletRpc : public PyMoneroWallet { -public: - - PyMoneroWalletRpc() { - m_rpc = std::make_shared(); - } - - PyMoneroWalletRpc(std::shared_ptr rpc_connection) { - m_rpc = rpc_connection; - if (!m_rpc->is_online() && !m_rpc->m_uri->empty()) m_rpc->check_connection(); - } - - PyMoneroWalletRpc(const std::string& uri = "", const std::string& username = "", const std::string& password = "") { - m_rpc = std::make_shared(uri, username, password); - if (!m_rpc->m_uri->empty()) m_rpc->check_connection(); - } - - ~PyMoneroWalletRpc(); - - 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); - boost::optional get_rpc_connection() const; - std::vector get_seed_languages() const; - void stop(); - 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) override; - bool is_connected_to_daemon() const override; - monero::monero_version get_version() const override; - std::string get_path() const override; - std::string get_seed() const override; - std::string get_seed_language() const override; - std::string get_public_view_key() const override; - std::string get_private_view_key() const override; - std::string get_public_spend_key() const override; - std::string get_private_spend_key() const override; - std::string get_address(const uint32_t account_idx, const uint32_t subaddress_idx) const override; - monero_subaddress get_address_index(const std::string& address) const override; - monero_integrated_address get_integrated_address(const std::string& standard_address = "", const std::string& payment_id = "") const override; - monero_integrated_address decode_integrated_address(const std::string& integrated_address) const override; - uint64_t get_height() const override; - uint64_t get_daemon_height() const override; - uint64_t get_height_by_date(uint16_t year, uint8_t month, uint8_t day) const override; - monero_sync_result sync() override; - monero_sync_result sync(monero_wallet_listener& listener) override; - monero_sync_result sync(uint64_t start_height, monero_wallet_listener& listener) override; - monero_sync_result sync(uint64_t start_height) override; - void start_syncing(uint64_t sync_period_in_ms = 10000) override; - void stop_syncing() override; - void scan_txs(const std::vector& tx_hashes) override; - void rescan_spent() override; - void rescan_blockchain() override; - uint64_t get_balance() const override; - uint64_t get_balance(uint32_t account_index) const override; - uint64_t get_balance(uint32_t account_idx, uint32_t subaddress_idx) const override; - uint64_t get_unlocked_balance() const override; - uint64_t get_unlocked_balance(uint32_t account_index) const override; - uint64_t get_unlocked_balance(uint32_t account_idx, uint32_t subaddress_idx) const override; - monero_account get_account(const uint32_t account_idx, bool include_subaddresses) const override; - monero_account get_account(const uint32_t account_idx, bool include_subaddresses, bool skip_balances) const; - std::vector get_accounts(bool include_subaddresses, const std::string& tag) const override; - std::vector get_accounts(bool include_subaddresses, const std::string& tag, bool skip_balances) const; - monero_account create_account(const std::string& label = "") override; - std::vector get_subaddresses(const uint32_t account_idx, const std::vector& subaddress_indices, bool skip_balances) const; - std::vector get_subaddresses(uint32_t account_idx, const std::vector& subaddress_indices) const override; - std::vector get_subaddresses(const uint32_t account_idx) const override; - monero_subaddress get_subaddress(const uint32_t account_idx, const uint32_t subaddress_idx) const override; - monero_subaddress create_subaddress(uint32_t account_idx, const std::string& label = "") override; - void set_subaddress_label(uint32_t account_idx, uint32_t subaddress_idx, const std::string& label = "") override; - std::string export_outputs(bool all = false) const override; - int import_outputs(const std::string& outputs_hex) override; - std::vector> export_key_images(bool all = false) const override; - std::shared_ptr import_key_images(const std::vector>& key_images) override; - std::vector> get_new_key_images_from_last_import() override; - void freeze_output(const std::string& key_image) override; - void thaw_output(const std::string& key_image) override; - bool is_output_frozen(const std::string& key_image) override; - monero_tx_priority get_default_fee_priority() const override; - std::vector> create_txs(const monero_tx_config& conf) override; - std::shared_ptr sweep_output(const monero_tx_config& config) override; - std::vector> sweep_dust(bool relay = false) override; - std::vector relay_txs(const std::vector& tx_metadatas) override; - monero_tx_set describe_tx_set(const monero_tx_set& tx_set) override; - monero_tx_set sign_txs(const std::string& unsigned_tx_hex) override; - std::vector submit_txs(const std::string& signed_tx_hex) override; - std::string sign_message(const std::string& msg, monero_message_signature_type signature_type, uint32_t account_idx = 0, uint32_t subaddress_idx = 0) const override; - monero_message_signature_result verify_message(const std::string& msg, const std::string& address, const std::string& signature) const override; - std::string get_tx_key(const std::string& tx_hash) const override; - std::shared_ptr check_tx_key(const std::string& tx_hash, const std::string& tx_key, const std::string& address) const override; - std::string get_tx_proof(const std::string& tx_hash, const std::string& address, const std::string& message) const override; - // TODO why no override ? - std::shared_ptr check_tx_proof(const std::string& tx_hash, const std::string& address, const std::string& message, const std::string& signature) const; - std::string get_spend_proof(const std::string& tx_hash, const std::string& message) const override; - bool check_spend_proof(const std::string& tx_hash, const std::string& message, const std::string& signature) const override; - std::string get_reserve_proof_wallet(const std::string& message) const override; - std::string get_reserve_proof_account(uint32_t account_idx, uint64_t amount, const std::string& message) const override; - std::shared_ptr check_reserve_proof(const std::string& address, const std::string& message, const std::string& signature) const override; - std::vector get_tx_notes(const std::vector& tx_hashes) const override; - void set_tx_notes(const std::vector& tx_hashes, const std::vector& notes) override; - std::vector get_address_book_entries(const std::vector& indices) const override; - uint64_t add_address_book_entry(const std::string& address, const std::string& description) override; - void edit_address_book_entry(uint64_t index, bool set_address, const std::string& address, bool set_description, const std::string& description) override; - 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; - void set_account_tag_label(const std::string& tag, const std::string& label) override; - void set_account_label(uint32_t account_index, const std::string& label) override; - std::string get_payment_uri(const monero_tx_config& config) const override; - // TODO why no override ? - std::shared_ptr parse_payment_uri(const std::string& uri) const; - void set_attribute(const std::string& key, const std::string& val) override; - bool get_attribute(const std::string& key, std::string& value) const override; - void start_mining(boost::optional num_threads, boost::optional background_mining, boost::optional ignore_battery) override; - void stop_mining() override; - bool is_multisig_import_needed() const override; - monero_multisig_info get_multisig_info() const override; - std::string prepare_multisig() override; - std::string make_multisig(const std::vector& multisig_hexes, int threshold, const std::string& password) override; - monero_multisig_init_result exchange_multisig_keys(const std::vector& multisig_hexes, const std::string& password); - std::string export_multisig_hex() override; - int import_multisig_hex(const std::vector& multisig_hexes) override; - monero_multisig_sign_result sign_multisig_tx_hex(const std::string& multisig_tx_hex) override; - std::vector submit_multisig_tx_hex(const std::string& signed_multisig_tx_hex); - void change_password(const std::string& old_password, const std::string& new_password) override; - void save() override; - void close(bool save = false) override; - std::shared_ptr get_balances(boost::optional account_idx, boost::optional subaddress_idx) const override; - -protected: - 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::shared_ptr m_poller; - - mutable boost::recursive_mutex m_sync_mutex; - mutable serializable_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); - - std::string query_key(const std::string& key_type) const; - std::vector> sweep_account(const monero_tx_config &conf); - void clear_address_cache(); - void refresh_listening(); - void poll(); - void clear(); -}; diff --git a/src/cpp/wallet/py_monero_wallet_full.cpp b/src/cpp/wallet/py_monero_wallet_full.cpp new file mode 100644 index 0000000..b67f01e --- /dev/null +++ b/src/cpp/wallet/py_monero_wallet_full.cpp @@ -0,0 +1,11 @@ +#include "py_monero_wallet_full.h" + + +void PyMoneroWalletFull::close(bool save) { + if (m_is_closed) throw std::runtime_error("Wallet already closed"); + monero::monero_wallet_full::close(save); +} + +void PyMoneroWalletFull::set_account_label(uint32_t account_idx, const std::string& label) { + set_subaddress_label(account_idx, 0, label); +} diff --git a/src/cpp/wallet/py_monero_wallet_full.h b/src/cpp/wallet/py_monero_wallet_full.h new file mode 100644 index 0000000..c5e5b7c --- /dev/null +++ b/src/cpp/wallet/py_monero_wallet_full.h @@ -0,0 +1,19 @@ +#pragma once + +#include "py_monero_wallet.h" + + +class PyMoneroWalletFull : public monero::monero_wallet_full { +public: + + bool is_closed() const { return m_is_closed; } + void close(bool save = false) override; + void set_account_label(uint32_t account_idx, const std::string& label); + + std::vector> get_new_key_images_from_last_import() { + throw std::runtime_error("get_new_key_images_from_last_import(): not implemented"); + } + +protected: + bool m_is_closed = false; +}; diff --git a/src/cpp/wallet/py_monero_wallet_model.cpp b/src/cpp/wallet/py_monero_wallet_model.cpp index c6c6499..57998a9 100644 --- a/src/cpp/wallet/py_monero_wallet_model.cpp +++ b/src/cpp/wallet/py_monero_wallet_model.cpp @@ -216,7 +216,7 @@ void PyMoneroTxWallet::from_property_tree_with_transfer(const boost::property_tr if (is_outgoing) { if (outgoing_transfer == nullptr) outgoing_transfer = std::make_shared(); } - else if (!is_outgoing) { + else { if (incoming_transfer == nullptr) incoming_transfer = std::make_shared(); incoming_transfer->m_tx = tx; } diff --git a/src/cpp/wallet/py_monero_wallet_rpc.cpp b/src/cpp/wallet/py_monero_wallet_rpc.cpp new file mode 100644 index 0000000..e77eac1 --- /dev/null +++ b/src/cpp/wallet/py_monero_wallet_rpc.cpp @@ -0,0 +1,1700 @@ +#include "py_monero_wallet_rpc.h" + + +PyMoneroWalletPoller::~PyMoneroWalletPoller() { + set_is_polling(false); +} + +void PyMoneroWalletPoller::set_is_polling(bool is_polling) { + if (is_polling == m_is_polling) return; + m_is_polling = is_polling; + + if (m_is_polling) { + m_thread = std::thread([this]() { + loop(); + }); + m_thread.detach(); + } else { + if (m_thread.joinable()) m_thread.join(); + } +} + +void PyMoneroWalletPoller::set_period_in_ms(uint64_t period_ms) { + m_poll_period_ms = period_ms; +} + +void PyMoneroWalletPoller::poll() { + if (m_num_polling > 1) return; + m_num_polling++; + + boost::lock_guard lock(m_mutex); + try { + // skip if wallet is closed + if (m_wallet->is_closed()) { + m_num_polling--; + return; + } + + 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; + } + + // 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); + } + + m_prev_height = height; + } + + // 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_locked_txs = locked_txs; + std::vector> unlocked_txs; + + if (!no_longer_locked_hashes.empty()) { + 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); + } + + // announce new unconfirmed and confirmed txs + for (const auto &locked_tx : locked_txs) { + if (locked_tx->m_is_confirmed) { + m_prev_confirmed_notifications.push_back(locked_tx->m_hash.get()); + notify_outputs(locked_tx); + } + else { + m_prev_unconfirmed_notifications.push_back(locked_tx->m_hash.get()); + } + } + + // announce new unlocked outputs + for (const auto &unlocked_tx : unlocked_txs) { + std::string tx_hash = unlocked_tx->m_hash.get(); + 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); + } + + check_for_changed_balances(); + + 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; + } + } +} + +std::shared_ptr PyMoneroWalletPoller::get_tx(const std::vector>& txs, const std::string& tx_hash) { + for (auto tx : txs) { + if (tx->m_hash == tx_hash) return tx; + } + + return nullptr; +} + +void PyMoneroWalletPoller::loop() { + while (m_is_polling) { + try { + poll(); + } catch (const std::exception& e) { + std::cout << "ERROR " << e.what() << std::endl; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(m_poll_period_ms)); + } +} + +void PyMoneroWalletPoller::on_new_block(uint64_t height) { + m_wallet->announce_new_block(height); +} + +void PyMoneroWalletPoller::notify_outputs(const std::shared_ptr &tx) { + 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; + if (outgoing_transfer->m_subaddress_indices.size() == 1) { + output->m_subaddress_index = outgoing_transfer->m_subaddress_indices[0]; + } + tx->m_inputs.push_back(output); + m_wallet->announce_output_spent(output); + } + + if (tx->m_incoming_transfers.size() > 0) { + if (!tx->m_outputs.empty()) { + for(const auto &output : tx->get_outputs_wallet()) { + m_wallet->announce_output_received(output); + } + } + else { + 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); + } + } + } +} + +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; + } + return false; +} + +PyMoneroWalletRpc::~PyMoneroWalletRpc() { +} + +boost::optional PyMoneroWalletRpc::get_rpc_connection() const { + if (m_rpc == nullptr) return boost::none; + return boost::optional(*m_rpc); +} + +PyMoneroWalletRpc* PyMoneroWalletRpc::open_wallet(const std::shared_ptr &config) { + 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); + PyMoneroJsonRequest request("open_wallet", params); + m_rpc->send_json_request(request); + clear(); + + if (config->m_connection_manager != boost::none) { + if (config->m_server != boost::none) throw std::runtime_error("Wallet can be opened with a server or connection manager but not both"); + set_connection_manager(config->m_connection_manager.get()); + } + else if (config->m_server != boost::none) { + set_daemon_connection(config->m_server); + } + + return this; +} + +PyMoneroWalletRpc* PyMoneroWalletRpc::open_wallet(const std::string& name, const std::string& password) { + auto config = std::make_shared(); + config->m_path = name; + config->m_password = password; + return open_wallet(config); +} + +PyMoneroWalletRpc* PyMoneroWalletRpc::create_wallet(const std::shared_ptr &config) { + 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)) { + throw std::runtime_error("Wallet can be initialized with a seed or keys but not both"); + } + if (config->m_account_lookahead != boost::none || config->m_subaddress_lookahead != boost::none) throw std::runtime_error("monero-wallet-rpc does not support creating wallets with subaddress lookahead over rpc"); + if (config->m_connection_manager != boost::none) { + if (config->m_server != boost::none) throw std::runtime_error("Wallet can be opened with a server or connection manager but not both"); + auto cm = config->m_connection_manager.value(); + if (cm != nullptr) { + auto connection = cm->get_connection(); + if (connection) { + config->m_server = *connection; + } + } + } + + if (config->m_seed != boost::none) create_wallet_from_seed(config); + else if (config->m_private_spend_key != boost::none || config->m_primary_address != boost::none) create_wallet_from_keys(config); + else create_wallet_random(config); + + if (config->m_connection_manager != boost::none) { + set_connection_manager(config->m_connection_manager.get()); + } + else if (config->m_server != boost::none) { + set_daemon_connection(config->m_server); + } + + return this; +} + +std::vector PyMoneroWalletRpc::get_seed_languages() const { + PyMoneroJsonRequest request("get_languages"); + std::shared_ptr response = m_rpc->send_json_request(request); + + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + std::vector languages; + + for (auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + + if (key == std::string("languages")) { + auto languages_node = it->second; + + for (auto it2 = languages_node.begin(); it2 != languages_node.end(); ++it2) { + languages.push_back(it2->second.data()); + } + } + } + + return languages; +} + +void PyMoneroWalletRpc::stop() { + PyMoneroJsonRequest request("stop_wallet"); + m_rpc->send_json_request(request); +} + +bool PyMoneroWalletRpc::is_view_only() const { + try { + std::string key = "mnemonic"; + query_key(key); + return false; + } + catch (const PyMoneroRpcError& e) { + if (e.code == -29) return true; + if (e.code == -1) return false; + throw; + } +} + +boost::optional PyMoneroWalletRpc::get_daemon_connection() const { + if (m_daemon_connection == nullptr) return boost::none; + return boost::optional(*m_daemon_connection); +} + +void PyMoneroWalletRpc::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 = ""; + params->m_password = ""; + } + else { + params->m_address = connection->m_uri; + params->m_username = connection->m_username; + params->m_password = connection->m_password; + } + + params->m_trusted = is_trusted; + params->m_ssl_support = "autodetect"; + + if (ssl_options != boost::none) { + params->m_ssl_private_key_path = ssl_options.get()->m_ssl_private_key_path; + params->m_ssl_certificate_path = ssl_options.get()->m_ssl_certificate_path; + params->m_ssl_ca_file = ssl_options.get()->m_ssl_ca_file; + params->m_ssl_allowed_fingerprints = ssl_options.get()->m_ssl_allowed_fingerprints; + params->m_ssl_allow_any_cert = ssl_options.get()->m_ssl_allow_any_cert; + } + + PyMoneroJsonRequest request("set_daemon", params); + m_rpc->send_json_request(request); + + if (connection == boost::none || connection->m_uri == boost::none || connection->m_uri->empty()) { + m_daemon_connection = nullptr; + } + else { + m_daemon_connection = std::make_shared(connection.get()); + } +} + +void PyMoneroWalletRpc::set_daemon_connection(const boost::optional& connection) { + set_daemon_connection(connection, false, boost::none); +} + +bool PyMoneroWalletRpc::is_connected_to_daemon() const { + try { + check_reserve_proof(get_primary_address(), "", ""); + return false; + } + catch (const PyMoneroRpcError& e) { + if (e.message == std::string("Failed to connect to daemon")) return false; + return true; + } +} + +monero::monero_version PyMoneroWalletRpc::get_version() const { + PyMoneroJsonRequest request("get_version"); + std::shared_ptr response = m_rpc->send_json_request(request); + + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto res = response->m_result.get(); + + std::shared_ptr info = std::make_shared(); + PyMoneroVersion::from_property_tree(res, info); + return *info; +} + +std::string PyMoneroWalletRpc::get_path() const { + return m_path; +} + +std::string PyMoneroWalletRpc::get_seed() const { + std::string key = "mnemonic"; + return query_key(key); +} + +std::string PyMoneroWalletRpc::get_seed_language() const { + throw std::runtime_error("MoneroWalletRpc::get_seed_language() not supported"); +} + +std::string PyMoneroWalletRpc::get_public_view_key() const { + std::string key = "public_view_key"; + return query_key(key); +} + +std::string PyMoneroWalletRpc::get_private_view_key() const { + std::string key = "view_key"; + return query_key(key); +} + +std::string PyMoneroWalletRpc::get_public_spend_key() const { + std::string key = "public_spend_key"; + return query_key(key); +} + +std::string PyMoneroWalletRpc::get_private_spend_key() const { + 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 { + auto it = m_address_cache.find(account_idx); + if (it == m_address_cache.end()) { + std::vector empty_indices; + get_subaddresses(account_idx, empty_indices, true); + return get_address(account_idx, subaddress_idx); + } + + auto subaddress_map = it->second; + auto it2 = subaddress_map.find(subaddress_idx); + + if (it2 == subaddress_map.end()) { + std::vector empty_indices; + get_subaddresses(account_idx, empty_indices, true); + auto it3 = m_address_cache.find(account_idx); + if (it3 == m_address_cache.end()) throw std::runtime_error("Could not find account address at index (" + std::to_string(account_idx) + ", " + std::to_string(subaddress_idx) + ")" ); + auto it4 = it3->second.find(subaddress_idx); + if (it4 == it3->second.end()) throw std::runtime_error("Could not find address at index (" + std::to_string(account_idx) + ", " + std::to_string(subaddress_idx) + ")" ); + return it4->second; + } + + return it2->second; +} + +monero_subaddress PyMoneroWalletRpc::get_address_index(const std::string& address) const { + auto params = std::make_shared(address); + PyMoneroJsonRequest request("get_address_index", params); + std::shared_ptr response = m_rpc->send_json_request(request); + + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto res = response->m_result.get(); + + 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); + PyMoneroJsonRequest request("make_integrated_address", params); + std::shared_ptr response = m_rpc->send_json_request(request); + + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto res = response->m_result.get(); + + 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); + PyMoneroJsonRequest request("split_integrated_address", params); + std::shared_ptr response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto res = response->m_result.get(); + auto tmplt = std::make_shared(); + PyMoneroIntegratedAddress::from_property_tree(res, tmplt); + tmplt->m_integrated_address = integrated_address; + return *tmplt; +} + +uint64_t PyMoneroWalletRpc::get_height() const { + PyMoneroJsonRequest request("get_height"); + std::shared_ptr response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto res = response->m_result.get(); + return PyMoneroWalletGetHeightResponse::from_property_tree(res); +} + +uint64_t PyMoneroWalletRpc::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 { + throw std::runtime_error("monero-wallet-rpc does not support getting a height by date"); +} + +monero_sync_result PyMoneroWalletRpc::sync() { + auto params = std::make_shared(); + boost::lock_guard lock(m_sync_mutex); + PyMoneroJsonRequest request("refresh", params); + poll(); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + monero_sync_result sync_result(0, false); + + for (auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + + if (key == std::string("blocks_fetched")) sync_result.m_num_blocks_fetched = it->second.get_value(); + else if (key == std::string("received_money")) sync_result.m_received_money = it->second.get_value(); + } + + return sync_result; +} + +monero_sync_result PyMoneroWalletRpc::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) { + 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); + boost::lock_guard lock(m_sync_mutex); + PyMoneroJsonRequest request("refresh", params); + auto response = m_rpc->send_json_request(request); + poll(); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + monero_sync_result sync_result(0, false); + + for (auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + + if (key == std::string("blocks_fetched")) sync_result.m_num_blocks_fetched = it->second.get_value(); + else if (key == std::string("received_money")) sync_result.m_received_money = it->second.get_value(); + } + + return sync_result; +} + +void PyMoneroWalletRpc::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); + PyMoneroJsonRequest request("auto_refresh", params); + auto response = m_rpc->send_json_request(request); + + // update sync period for poller + m_sync_period_in_ms = sync_period_in_seconds * 1000; + if (m_poller != nullptr) m_poller->set_period_in_ms(m_sync_period_in_ms.get()); + + // poll if listening + poll(); +} + +void PyMoneroWalletRpc::stop_syncing() { + auto params = std::make_shared(false); + PyMoneroJsonRequest request("auto_refresh", params); + m_rpc->send_json_request(request); +} + +void PyMoneroWalletRpc::scan_txs(const std::vector& tx_hashes) { + if (tx_hashes.empty()) throw std::runtime_error("No tx hashes given to scan"); + auto params = std::make_shared(tx_hashes); + PyMoneroJsonRequest request("scan_tx", params); + m_rpc->send_json_request(request); + poll(); +} + +void PyMoneroWalletRpc::rescan_spent() { + PyMoneroJsonRequest request("rescan_spent"); + m_rpc->send_json_request(request); +} + +void PyMoneroWalletRpc::rescan_blockchain() { + PyMoneroJsonRequest request("rescan_blockchain"); + m_rpc->send_json_request(request); +} + +uint64_t PyMoneroWalletRpc::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 { + 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 { + auto wallet_balance = get_balances(account_idx, subaddress_idx); + return wallet_balance->m_balance; +} + +uint64_t PyMoneroWalletRpc::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 { + 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 { + 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 { + 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 { + for(auto& account : monero::monero_wallet::get_accounts()) { + if (account.m_index.get() == account_idx) { + std::vector empty_indices; + if (include_subaddresses) account.m_subaddresses = get_subaddresses(account_idx, empty_indices, skip_balances); + return account; + } + } + 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 { + 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); + PyMoneroJsonRequest request("get_accounts", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + std::vector accounts; + PyMoneroAccount::from_property_tree(node, accounts); + if (include_subaddresses) { + + for (auto &account : accounts) { + std::vector empty_indices; + account.m_subaddresses = get_subaddresses(account.m_index.get(), empty_indices, true); + + if (!skip_balances) { + for (auto &subaddress : account.m_subaddresses) { + subaddress.m_balance = 0; + subaddress.m_unlocked_balance = 0; + subaddress.m_num_unspent_outputs = 0; + subaddress.m_num_blocks_to_unlock = 0; + } + } + } + + if (!skip_balances) { + auto params2 = std::make_shared(true); + PyMoneroJsonRequest request2("get_balance", params2); + auto response2 = m_rpc->send_json_request(request2); + if (response2->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node2 = response2->m_result.get(); + auto bal_res = std::make_shared(); + PyMoneroGetBalanceResponse::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()]; + if (account->m_index != subaddress->m_account_index) throw std::runtime_error("RPC accounts are out of order"); + auto tgt_subaddress = &account->m_subaddresses[subaddress->m_account_index.get()]; + if (tgt_subaddress->m_index != subaddress->m_index) throw std::runtime_error("RPC subaddresses are out of order"); + + if (subaddress->m_balance != boost::none) tgt_subaddress->m_balance = subaddress->m_balance; + if (subaddress->m_unlocked_balance != boost::none) tgt_subaddress->m_unlocked_balance = subaddress->m_unlocked_balance; + if (subaddress->m_num_unspent_outputs != boost::none) tgt_subaddress->m_num_unspent_outputs = subaddress->m_num_unspent_outputs; + if (subaddress->m_num_blocks_to_unlock != boost::none) tgt_subaddress->m_num_blocks_to_unlock = subaddress->m_num_blocks_to_unlock; + } + } + } + + return accounts; +} + +monero_account PyMoneroWalletRpc::create_account(const std::string& label) { + auto params = std::make_shared(label); + PyMoneroJsonRequest request("create_account", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + monero_account res; + res.m_balance = 0; + res.m_unlocked_balance = 0; + bool found_index = false; + bool address_found = false; + + for (auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + + if (key == std::string("account_index")) { + found_index = true; + res.m_index = it->second.get_value(); + } + else if (key == std::string("address")) { + address_found = true; + res.m_primary_address = it->second.data(); + } + } + + if (!found_index || !address_found) throw std::runtime_error("Could not create account"); + + return res; +} + +std::vector PyMoneroWalletRpc::get_subaddresses(const uint32_t account_idx, const std::vector& subaddress_indices, bool skip_balances) const { + // fetch subaddresses + auto params = std::make_shared(account_idx, subaddress_indices); + PyMoneroJsonRequest request("get_address", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + + std::vector subaddresses; + + // initialize subaddresses + for (auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + + if (key == std::string("addresses")) { + auto node2 = it->second; + for (auto it2 = node2.begin(); it2 != node2.end(); ++it2) { + auto subaddress = std::make_shared(); + PyMoneroSubaddress::from_rpc_property_tree(it2->second, subaddress); + subaddress->m_account_index = account_idx; + subaddresses.push_back(*subaddress); + } + break; + } + + } + + // fetch and initialize subaddress balances + if (!skip_balances) { + + // these fields are not initialized if subaddress is unused and therefore not returned from `get_balance` + for (auto &subaddress : subaddresses) { + subaddress.m_balance = 0; + subaddress.m_unlocked_balance = 0; + subaddress.m_num_unspent_outputs = 0; + subaddress.m_num_blocks_to_unlock = 0; + } + + // fetch and initialize balances + PyMoneroJsonRequest request2("get_balance", params); + auto response2 = m_rpc->send_json_request(request); + if (response2->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node2 = response2->m_result.get(); + + std::vector> subaddresses2; + PyMoneroSubaddress::from_rpc_property_tree(node2, subaddresses2); + + for (auto &tgt_subaddress: subaddresses) { + for (const auto &rpc_subaddress : subaddresses2) { + if (rpc_subaddress->m_index != tgt_subaddress.m_index) continue; // skip to subaddress with same index + if (rpc_subaddress->m_balance != boost::none) tgt_subaddress.m_balance = rpc_subaddress->m_balance; + if (rpc_subaddress->m_unlocked_balance != boost::none) tgt_subaddress.m_unlocked_balance = rpc_subaddress->m_unlocked_balance; + if (rpc_subaddress->m_num_unspent_outputs != boost::none) tgt_subaddress.m_num_unspent_outputs = rpc_subaddress->m_num_unspent_outputs; + if (rpc_subaddress->m_num_blocks_to_unlock != boost::none) tgt_subaddress.m_num_blocks_to_unlock = rpc_subaddress->m_num_blocks_to_unlock; + } + } + } + + // cache addresses + auto it = m_address_cache.find(account_idx); + if (it == m_address_cache.end()) { + m_address_cache[account_idx] = serializable_unordered_map(); + } + + for (const auto& subaddress : subaddresses) { + m_address_cache[account_idx][subaddress.m_index.get()] = subaddress.m_address.get(); + } + + // return results + return subaddresses; +} + +std::vector PyMoneroWalletRpc::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 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 { + std::vector subaddress_indices; + subaddress_indices.push_back(subaddress_idx); + auto subaddresses = get_subaddresses(account_idx, subaddress_indices); + if (subaddresses.empty()) throw std::runtime_error("Subaddress is not initialized"); + if (subaddresses.size() != 1) throw std::runtime_error("Only 1 subaddress should be returned"); + return subaddresses[0]; +} + +monero_subaddress PyMoneroWalletRpc::create_subaddress(uint32_t account_idx, const std::string& label) { + auto params = std::make_shared(account_idx, label); + PyMoneroJsonRequest request("create_address", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + monero_subaddress sub; + sub.m_account_index = account_idx; + if (!label.empty()) sub.m_label = label; + sub.m_balance = 0; + sub.m_unlocked_balance = 0; + sub.m_num_unspent_outputs = 0; + sub.m_is_used = false; + sub.m_num_blocks_to_unlock = 0; + + for(auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("address_index")) sub.m_index = it->second.get_value(); + else if (key == std::string("address")) sub.m_address = it->second.data(); + } + + 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); + PyMoneroJsonRequest request("label_address", params); + m_rpc->send_json_request(request); +} + +std::string PyMoneroWalletRpc::export_outputs(bool all) const { + auto params = std::make_shared(all); + PyMoneroJsonRequest request("export_outputs", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + + for (auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + + if (key == std::string("outputs_data_hex")) return it->second.data(); + } + + 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); + PyMoneroJsonRequest request("import_outputs", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + + for (auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + + if (key == std::string("num_imported")) return it->second.get_value(); + } + + return 0; +} + +std::vector> PyMoneroWalletRpc::export_key_images(bool all) const { + auto params = std::make_shared(all); + PyMoneroJsonRequest request("export_key_images", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + 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); + PyMoneroJsonRequest request("import_key_images", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + auto import_result = std::make_shared(); + PyMoneroKeyImageImportResult::from_property_tree(node, import_result); + return import_result; +} + +std::vector> PyMoneroWalletRpc::get_new_key_images_from_last_import() { + throw std::runtime_error("get_new_key_images_from_last_import(): not implemented"); +} + +void PyMoneroWalletRpc::freeze_output(const std::string& key_image) { + auto params = std::make_shared(key_image); + PyMoneroJsonRequest request("freeze", params); + m_rpc->send_json_request(request); +} + +void PyMoneroWalletRpc::thaw_output(const std::string& key_image) { + auto params = std::make_shared(key_image); + PyMoneroJsonRequest request("thaw", params); + m_rpc->send_json_request(request); +} + +bool PyMoneroWalletRpc::is_output_frozen(const std::string& key_image) { + auto params = std::make_shared(key_image); + PyMoneroJsonRequest request("frozen", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + + for(auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + + if (key == std::string("frozen")) return it->second.get_value(); + } + + throw std::runtime_error("Could not get output"); +} + +monero_tx_priority PyMoneroWalletRpc::get_default_fee_priority() const { + PyMoneroJsonRequest request("get_default_fee_priority"); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + + for(auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + + if (key == std::string("priority")) { + int priority = it->second.get_value(); + + if (priority == 0) return monero_tx_priority::DEFAULT; + else if (priority == 1) return monero_tx_priority::UNIMPORTANT; + else if (priority == 2) return monero_tx_priority::NORMAL; + else if (priority == 3) return monero_tx_priority::ELEVATED; + } + } + + throw std::runtime_error("Could not get default fee priority"); +} + +std::vector> PyMoneroWalletRpc::create_txs(const monero_tx_config& conf) { + // validate, copy, and normalize request + monero_tx_config config = conf; + if (config.m_destinations.empty()) throw std::runtime_error("Destinations cannot be empty"); + if (config.m_sweep_each_subaddress != boost::none) throw std::runtime_error("Sweep each subaddress not supported"); + if (config.m_below_amount != boost::none) throw std::runtime_error("Below amount not supported"); + + if (config.m_can_split == boost::none) { + config = config.copy(); + config.m_can_split = true; + } + if (config.m_relay == true && is_multisig()) throw std::runtime_error("Cannot relay multisig transaction until co-signed"); + + // determine account and subaddresses to send from + if (config.m_account_index == boost::none) throw std::runtime_error("Must specify the account index to send from"); + auto account_idx = config.m_account_index.get(); + + // cannot apply subtractFeeFrom with `transfer_split` call + if (config.m_can_split && config.m_subtract_fee_from.size() > 0) { + throw std::runtime_error("subtractfeefrom transfers cannot be split over multiple transactions yet"); + } + + // build request parameters + auto params = std::make_shared(config); + std::string request_path = "transfer"; + if (config.m_can_split) request_path = "transfer_split"; + + PyMoneroJsonRequest request(request_path, params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + + // pre-initialize txs iff present. multisig and view-only wallets will have tx set without transactions + std::vector> txs; + int num_txs = 0; + + for(auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + + if (config.m_can_split && key == std::string("fee_list")) { + auto fee_list_node = it->second; + + for(auto it2 = fee_list_node.begin(); it2 != fee_list_node.end(); ++it2) { + num_txs++; + } + } + } + + bool copy_destinations = num_txs == 1; + for (int i = 0; i < num_txs; i++) { + auto tx = std::make_shared(); + PyMoneroTxWallet::init_sent(config, tx, copy_destinations); + tx->m_outgoing_transfer.get()->m_account_index = account_idx; + + if (config.m_subaddress_indices.size() == 1) { + tx->m_outgoing_transfer.get()->m_subaddress_indices = config.m_subaddress_indices; + } + + txs.push_back(tx); + } + + // notify of changes + if (config.m_relay) poll(); + + // initialize tx set from rpc response with pre-initialized txs + auto tx_set = std::make_shared(); + if (config.m_can_split) { + PyMoneroTxSet::from_sent_txs(node, tx_set, txs, config); + } + else { + if (txs.empty()) { + auto __tx = std::make_shared(); + PyMoneroTxSet::from_tx(node, tx_set, __tx, true, config); + } + else { + PyMoneroTxSet::from_tx(node, tx_set, txs[0], true, config); + } + } + + return tx_set->m_txs; +} + +std::shared_ptr PyMoneroWalletRpc::sweep_output(const monero_tx_config& config) { + auto params = std::make_shared(config); + PyMoneroJsonRequest request("sweep_single", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + if (config.m_relay) poll(); + auto set = std::make_shared(); + auto tx = std::make_shared(); + PyMoneroTxWallet::init_sent(config, tx, true); + PyMoneroTxSet::from_tx(node, set, tx, true, config); + return tx; +} + +std::vector> PyMoneroWalletRpc::sweep_dust(bool relay) { + auto params = std::make_shared(relay); + PyMoneroJsonRequest request("sweep_dust", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + auto set = std::make_shared(); + PyMoneroTxSet::from_sent_txs(node, set); + return set->m_txs; +} + +std::vector PyMoneroWalletRpc::relay_txs(const std::vector& tx_metadatas) { + 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); + PyMoneroJsonRequest request("relay_tx", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + + for (auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("tx_hash")) tx_hashes.push_back(it->second.data()); + } + } + + return tx_hashes; +} + +monero_tx_set PyMoneroWalletRpc::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; + PyMoneroJsonRequest request("describe_transfer", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + auto set = std::make_shared(); + PyMoneroTxSet::from_describe_transfer(node, set); + return *set; +} + +monero_tx_set PyMoneroWalletRpc::sign_txs(const std::string& unsigned_tx_hex) { + auto params = std::make_shared(unsigned_tx_hex); + PyMoneroJsonRequest request("sign_transfer", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + auto 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); + PyMoneroJsonRequest request("submit_transfer", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + poll(); + std::vector hashes; + + for (auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + + if (key == std::string("tx_hash_list")) { + auto hashes_node = it->second; + + for (auto it2 = hashes_node.begin(); it2 != hashes_node.end(); ++it2) { + hashes.push_back(it2->second.data()); + } + } + } + + 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); + PyMoneroJsonRequest request("sign", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + return PyMoneroReserveProofSignature::from_property_tree(node); +} + +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); + PyMoneroJsonRequest request("verify", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + auto sig_result = std::make_shared(); + PyMoneroMessageSignatureResult::from_property_tree(node, sig_result); + return *sig_result; +} + +std::string PyMoneroWalletRpc::get_tx_key(const std::string& tx_hash) const { + auto params = std::make_shared(tx_hash); + PyMoneroJsonRequest request("get_tx_key", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + for (auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + + if (key == std::string("tx_key")) { + return it->second.data(); + } + } + + 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 { + auto params = std::make_shared(tx_hash, tx_key, address); + PyMoneroJsonRequest request("check_tx_key", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + auto check = std::make_shared(); + PyMoneroCheckTxProof::from_property_tree(node, check); + return check; +} + +std::string PyMoneroWalletRpc::get_tx_proof(const std::string& tx_hash, const std::string& address, const std::string& message) const { + auto params = std::make_shared(tx_hash, message); + params->m_address = address; + PyMoneroJsonRequest request("get_tx_proof", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + return PyMoneroReserveProofSignature::from_property_tree(node); +} + +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 { + auto params = std::make_shared(tx_hash, address, message, signature); + PyMoneroJsonRequest request("check_tx_proof", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + auto check = std::make_shared(); + PyMoneroCheckTxProof::from_property_tree(node, check); + return check; +} + +std::string PyMoneroWalletRpc::get_spend_proof(const std::string& tx_hash, const std::string& message) const { + auto params = std::make_shared(tx_hash, message); + PyMoneroJsonRequest request("get_spend_proof", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + return PyMoneroReserveProofSignature::from_property_tree(node); +} + +bool PyMoneroWalletRpc::check_spend_proof(const std::string& tx_hash, const std::string& message, const std::string& signature) const { + auto params = std::make_shared(tx_hash, message, signature); + PyMoneroJsonRequest request("check_spend_proof", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + auto proof = std::make_shared(); + PyMoneroCheckReserve::from_property_tree(node, proof); + return proof->m_is_good; +} + +std::string PyMoneroWalletRpc::get_reserve_proof_wallet(const std::string& message) const { + auto params = std::make_shared(message); + PyMoneroJsonRequest request("get_reserve_proof", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + return PyMoneroReserveProofSignature::from_property_tree(node); +} + +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); + PyMoneroJsonRequest request("get_reserve_proof", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + return PyMoneroReserveProofSignature::from_property_tree(node); +} + +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); + PyMoneroJsonRequest request("check_reserve_proof", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + auto proof = std::make_shared(); + PyMoneroCheckReserve::from_property_tree(node, proof); + return proof; +} + +std::vector PyMoneroWalletRpc::get_tx_notes(const std::vector& tx_hashes) const { + auto params = std::make_shared(tx_hashes); + PyMoneroJsonRequest request("get_tx_notes", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + std::vector notes; + + for (auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + + if (key == std::string("notes")) { + auto notes_node = it->second; + + for (auto it2 = notes_node.begin(); it2 != notes_node.end(); ++it2) { + notes.push_back(it2->second.data()); + } + } + } + + return notes; +} + +void PyMoneroWalletRpc::set_tx_notes(const std::vector& tx_hashes, const std::vector& notes) { + auto params = std::make_shared(tx_hashes, notes); + PyMoneroJsonRequest request("set_tx_notes", params); + m_rpc->send_json_request(request); +} + +std::vector PyMoneroWalletRpc::get_address_book_entries(const std::vector& indices) const { + auto params = std::make_shared(indices); + PyMoneroJsonRequest request("get_address_book", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + std::vector> entries_ptr; + PyMoneroAddressBookEntry::from_property_tree(node, entries_ptr); + std::vector entries; + + for (const auto &entry : entries_ptr) { + entries.push_back(*entry); + } + + return entries; +} + +uint64_t PyMoneroWalletRpc::add_address_book_entry(const std::string& address, const std::string& description) { + auto params = std::make_shared(address, description); + PyMoneroJsonRequest request("add_address_book", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + + for (auto it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + + if (key == std::string("index")) { + return it->second.get_value(); + } + } + + 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); + PyMoneroJsonRequest request("edit_address_book", params); + m_rpc->send_json_request(request); +} + +void PyMoneroWalletRpc::delete_address_book_entry(uint64_t index) { + auto params = std::make_shared(index); + PyMoneroJsonRequest request("delete_address_book", params); + m_rpc->send_json_request(request); +} + +void PyMoneroWalletRpc::tag_accounts(const std::string& tag, const std::vector& account_indices) { + auto params = std::make_shared(tag, account_indices); + PyMoneroJsonRequest request("tag_accounts", params); + m_rpc->send_json_request(request); +} + +void PyMoneroWalletRpc::untag_accounts(const std::vector& account_indices) { + auto params = std::make_shared(account_indices); + PyMoneroJsonRequest request("untag_accounts", params); + m_rpc->send_json_request(request); +} + +std::vector> PyMoneroWalletRpc::get_account_tags() { + PyMoneroJsonRequest request("get_account_tags"); + std::shared_ptr response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto res = response->m_result.get(); + std::vector> account_tags; + PyMoneroAccountTag::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); + PyMoneroJsonRequest request("set_account_tag_description", params); + m_rpc->send_json_request(request); +} + +void PyMoneroWalletRpc::set_account_label(uint32_t account_index, const std::string& label) { + set_subaddress_label(account_index, 0, label); +} + +std::string PyMoneroWalletRpc::get_payment_uri(const monero_tx_config& config) const { + auto params = std::make_shared(config); + PyMoneroJsonRequest request("make_uri", params); + std::shared_ptr response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto res = response->m_result.get(); + return PyMoneroGetPaymentUriResponse::from_property_tree(res); +} + +std::shared_ptr PyMoneroWalletRpc::parse_payment_uri(const std::string& uri) const { + auto params = std::make_shared(uri); + PyMoneroJsonRequest request("parse_uri", params); + std::shared_ptr response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto res = response->m_result.get(); + auto uri_response = std::make_shared(); + PyMoneroParsePaymentUriResponse::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); + PyMoneroJsonRequest request("set_attribute", params); + m_rpc->send_json_request(request); +} + +bool PyMoneroWalletRpc::get_attribute(const std::string& key, std::string& value) const { + try { + auto params = std::make_shared(key); + PyMoneroJsonRequest request("get_attribute", params); + std::shared_ptr response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto res = response->m_result.get(); + PyMoneroWalletAttributeParams::from_property_tree(res, params); + if (params->m_value == boost::none) return false; + value = params->m_value.get(); + return true; + } + catch (...) { + 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)); + PyMoneroJsonRequest request("start_mining", params); + auto response = m_rpc->send_json_request(request); + // TODO PyMoneroDaemonRpc::check_response_status(response); +} + +void PyMoneroWalletRpc::stop_mining() { + PyMoneroJsonRequest request("stop_mining"); + m_rpc->send_json_request(request); +} + +bool PyMoneroWalletRpc::is_multisig_import_needed() const { + PyMoneroJsonRequest request("get_balance"); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto res = response->m_result.get(); + auto balance = std::make_shared(); + PyMoneroGetBalanceResponse::from_property_tree(res, balance); + if (balance->m_multisig_import_needed) return true; + return false; +} + +monero_multisig_info PyMoneroWalletRpc::get_multisig_info() const { + PyMoneroJsonRequest request("is_multisig"); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto res = response->m_result.get(); + auto info = std::make_shared(); + PyMoneroMultisigInfo::from_property_tree(res, info); + return *info; +} + +std::string PyMoneroWalletRpc::prepare_multisig() { + auto params = std::make_shared(); + PyMoneroJsonRequest request("prepare_multisig", params); + auto response = m_rpc->send_json_request(request); + clear_address_cache(); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto res = response->m_result.get(); + return PyMoneroPrepareMakeMultisigResponse::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); + PyMoneroJsonRequest request("make_multisig", params); + auto response = m_rpc->send_json_request(request); + clear_address_cache(); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto res = response->m_result.get(); + return PyMoneroPrepareMakeMultisigResponse::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); + PyMoneroJsonRequest request("exchange_multisig_keys", params); + auto response = m_rpc->send_json_request(request); + clear_address_cache(); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto res = response->m_result.get(); + auto multisig_init = std::make_shared(); + PyMoneroMultisigInitResult::from_property_tree(res, multisig_init); + return *multisig_init; +} + +std::string PyMoneroWalletRpc::export_multisig_hex() { + PyMoneroJsonRequest request("export_multisig_info"); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto res = response->m_result.get(); + return PyMoneroExportMultisigHexResponse::from_property_tree(res); +} + +int PyMoneroWalletRpc::import_multisig_hex(const std::vector& multisig_hexes) { + auto params = std::make_shared(multisig_hexes); + PyMoneroJsonRequest request("import_multisig_info", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto res = response->m_result.get(); + return PyMoneroImportMultisigHexResponse::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); + PyMoneroJsonRequest request("sign_multisig", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto res = response->m_result.get(); + 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); + PyMoneroJsonRequest request("submit_multisig", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto res = response->m_result.get(); + return PyMoneroSubmitMultisigTxHexResponse::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); + PyMoneroJsonRequest request("change_wallet_password", params); + m_rpc->send_json_request(request); +} + +void PyMoneroWalletRpc::save() { + PyMoneroJsonRequest request("store"); + m_rpc->send_json_request(request); +} + +void PyMoneroWalletRpc::close(bool save) { + auto params = std::make_shared(save); + PyMoneroJsonRequest request("close_wallet", params); + m_rpc->send_json_request(request); +} + +std::shared_ptr PyMoneroWalletRpc::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"); + + auto accounts = monero::monero_wallet::get_accounts(); + + for(const auto &account : accounts) { + balance->m_balance += account.m_balance.get(); + balance->m_unlocked_balance += account.m_unlocked_balance.get(); + } + + return balance; + } + else { + auto params = std::make_shared(account_idx.get(), subaddress_idx); + PyMoneroJsonRequest request("get_balance", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto res = response->m_result.get(); + auto bal_res = std::make_shared(); + PyMoneroGetBalanceResponse::from_property_tree(res, bal_res); + + if (subaddress_idx == boost::none) { + balance->m_balance = bal_res->m_balance.get(); + balance->m_unlocked_balance = bal_res->m_unlocked_balance.get(); + return balance; + } + else if (bal_res->m_per_subaddress.size() > 0) { + auto sub = bal_res->m_per_subaddress[0]; + balance->m_balance = sub->m_balance.get(); + balance->m_unlocked_balance = sub->m_unlocked_balance.get(); + } + } + + return balance; +} + +PyMoneroWalletRpc* PyMoneroWalletRpc::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"); + if (config.m_restore_height != boost::none) throw std::runtime_error("Cannot specify restore height when creating random wallet"); + if (config.m_save_current != boost::none && config.m_save_current == false) throw std::runtime_error("Current wallet is saved automatically when creating random wallet"); + if (config.m_path == boost::none || config.m_path->empty()) throw std::runtime_error("Wallet name is not initialized"); + if (config.m_language == boost::none || config.m_language->empty()) config.m_language = "English"; + + // send request + std::string filename = config.m_path.get(); + std::string password = config.m_password.get(); + std::string language = config.m_language.get(); + + auto params = std::make_shared(filename, password, language); + PyMoneroJsonRequest request("create_wallet", params); + m_rpc->send_json_request(request); + clear(); + m_path = config.m_path.get(); + return this; +} + +PyMoneroWalletRpc* PyMoneroWalletRpc::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; + auto password = config.m_password; + auto seed = config.m_seed; + auto seed_offset = config.m_seed_offset; + auto restore_height = config.m_restore_height; + auto language = config.m_language; + bool autosave_current = false; + 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); + PyMoneroJsonRequest request("restore_deterministic_wallet", params); + m_rpc->send_json_request(request); + clear(); + m_path = config.m_path.get(); + return this; +} + +PyMoneroWalletRpc* PyMoneroWalletRpc::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(); + std::string password = config->m_password.get(); + std::string address = config->m_primary_address.get(); + std::string view_key = ""; + std::string spend_key = ""; + if (config->m_private_view_key != boost::none) view_key = config->m_private_view_key.get(); + if (config->m_private_spend_key != boost::none) spend_key = config->m_private_spend_key.get(); + 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); + PyMoneroJsonRequest request("generate_from_keys", params); + m_rpc->send_json_request(request); + 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); + PyMoneroJsonRequest request("query_key", params); + std::shared_ptr response = m_rpc->send_json_request(request); + + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + + for (boost::property_tree::ptree::const_iterator it = node.begin(); it != node.end(); ++it) { + std::string key = it->first; + if (key == std::string("key")) return it->second.data(); + } + + throw std::runtime_error(std::string("Cloud not query key: ") + key_type); +} + +std::vector> PyMoneroWalletRpc::sweep_account(const monero_tx_config &conf) { + auto config = conf.copy(); + if (config.m_account_index == boost::none) throw std::runtime_error("Must specify an account index to sweep from"); + if (config.m_destinations.size() != 1) throw std::runtime_error("Must specify exactly one destination to sweep to"); + if (config.m_destinations[0]->m_address == boost::none) throw std::runtime_error("Must specify destination address to sweep to"); + if (config.m_destinations[0]->m_amount != boost::none) throw std::runtime_error("Cannot specify amount in sweep request"); + if (config.m_key_image != boost::none) throw std::runtime_error("Key image defined; use sweepOutput() to sweep an output by its key image"); + //if (config.m_subaddress_indices.size() == 0) throw std::runtime_error("Empty list given for subaddresses indices to sweep"); + if (config.m_sweep_each_subaddress) throw std::runtime_error("Cannot sweep each subaddress with RPC `sweep_all`"); + if (config.m_subtract_fee_from.size() > 0) throw std::runtime_error("Sweeping output does not support subtracting fees from destinations"); + + // sweep from all subaddresses if not otherwise defined + if (config.m_subaddress_indices.empty()) { + uint32_t account_idx = config.m_account_index.get(); + auto subaddresses = get_subaddresses(account_idx); + for (const auto &subaddress : subaddresses) { + config.m_subaddress_indices.push_back(subaddress.m_index.get()); + } + } + 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); + PyMoneroJsonRequest request("sweep_all", params); + auto response = m_rpc->send_json_request(request); + if (response->m_result == boost::none) throw std::runtime_error("Invalid Monero JSONRPC response"); + auto node = response->m_result.get(); + if (config.m_relay) poll(); + std::vector> txs; + auto set = std::make_shared(); + PyMoneroTxSet::from_sent_txs(node, set, txs, config); + + for (auto &tx : set->m_txs) { + tx->m_is_locked = true; + tx->m_is_confirmed = false; + tx->m_num_confirmations = 0; + tx->m_relay = relay; + tx->m_in_tx_pool = relay; + tx->m_is_relayed = relay; + tx->m_is_miner_tx = false; + tx->m_is_failed = false; + tx->m_ring_size = monero_utils::RING_SIZE; + auto transfer = tx->m_outgoing_transfer.get(); + transfer->m_account_index = config.m_account_index; + if (config.m_subaddress_indices.size() == 1) + { + transfer->m_subaddress_indices = config.m_subaddress_indices; + } + auto destination = std::make_shared(); + destination->m_address = config.m_destinations[0]->m_address; + destination->m_amount = config.m_destinations[0]->m_amount; + std::vector> destinations; + destinations.push_back(destination); + transfer->m_destinations = destinations; + tx->m_payment_id = config.m_payment_id; + if (tx->m_unlock_time == boost::none) tx->m_unlock_time = 0; + if (tx->m_relay) { + if (tx->m_last_relayed_timestamp == boost::none) { + //tx.setLastRelayedTimestamp(System.currentTimeMillis()); // TODO (monero-wallet-rpc): provide timestamp on response; unconfirmed timestamps vary + } + if (tx->m_is_double_spend_seen == boost::none) tx->m_is_double_spend_seen = false; + } + } + + return set->m_txs; +} + +void PyMoneroWalletRpc::clear_address_cache() { + m_address_cache.clear(); +} + +void PyMoneroWalletRpc::refresh_listening() { + if (m_rpc->m_zmq_uri == boost::none) { + if (m_poller == nullptr && m_listeners.size() > 0) m_poller = std::make_shared(this); + if (m_poller != nullptr) m_poller->set_is_polling(m_listeners.size() > 0); + } + /* + else { + if (m_zmq_listener == nullptr && m_listeners.size() > 0) m_zmq_listener = std::make_shared(); + if (m_zmq_listener != nullptr) m_zmq_listener.set_is_polling(m_listeners.size() > 0); + } + */ +} + +void PyMoneroWalletRpc::poll() { + if (m_poller != nullptr && m_poller->is_polling()) m_poller->poll(); +} + +void PyMoneroWalletRpc::clear() { + m_listeners.clear(); + refresh_listening(); + clear_address_cache(); + m_path = ""; +} diff --git a/src/cpp/wallet/py_monero_wallet_rpc.h b/src/cpp/wallet/py_monero_wallet_rpc.h new file mode 100644 index 0000000..568670f --- /dev/null +++ b/src/cpp/wallet/py_monero_wallet_rpc.h @@ -0,0 +1,194 @@ +#pragma once + +#include "py_monero_wallet.h" + + +class PyMoneroWalletPoller { +public: + explicit PyMoneroWalletPoller(PyMoneroWallet *wallet) { + m_wallet = wallet; + m_is_polling = false; + m_num_polling = 0; + } + + ~PyMoneroWalletPoller(); + + 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 poll(); + +protected: + mutable boost::recursive_mutex m_mutex; + PyMoneroWallet *m_wallet; + std::atomic m_is_polling; + uint64_t m_poll_period_ms; + std::thread m_thread; + int 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 loop(); + void on_new_block(uint64_t height); + void notify_outputs(const std::shared_ptr &tx); + bool check_for_changed_balances(); +}; + +class PyMoneroWalletRpc : public PyMoneroWallet { +public: + + PyMoneroWalletRpc() { + m_rpc = std::make_shared(); + } + + PyMoneroWalletRpc(std::shared_ptr rpc_connection) { + m_rpc = rpc_connection; + if (!m_rpc->is_online() && !m_rpc->m_uri->empty()) m_rpc->check_connection(); + } + + PyMoneroWalletRpc(const std::string& uri = "", const std::string& username = "", const std::string& password = "") { + m_rpc = std::make_shared(uri, username, password); + if (!m_rpc->m_uri->empty()) m_rpc->check_connection(); + } + + ~PyMoneroWalletRpc(); + + 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); + boost::optional get_rpc_connection() const; + std::vector get_seed_languages() const; + void stop(); + 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) override; + bool is_connected_to_daemon() const override; + monero::monero_version get_version() const override; + std::string get_path() const override; + std::string get_seed() const override; + std::string get_seed_language() const override; + std::string get_public_view_key() const override; + std::string get_private_view_key() const override; + std::string get_public_spend_key() const override; + std::string get_private_spend_key() const override; + std::string get_address(const uint32_t account_idx, const uint32_t subaddress_idx) const override; + monero_subaddress get_address_index(const std::string& address) const override; + monero_integrated_address get_integrated_address(const std::string& standard_address = "", const std::string& payment_id = "") const override; + monero_integrated_address decode_integrated_address(const std::string& integrated_address) const override; + uint64_t get_height() const override; + uint64_t get_daemon_height() const override; + uint64_t get_height_by_date(uint16_t year, uint8_t month, uint8_t day) const override; + monero_sync_result sync() override; + monero_sync_result sync(monero_wallet_listener& listener) override; + monero_sync_result sync(uint64_t start_height, monero_wallet_listener& listener) override; + monero_sync_result sync(uint64_t start_height) override; + void start_syncing(uint64_t sync_period_in_ms = 10000) override; + void stop_syncing() override; + void scan_txs(const std::vector& tx_hashes) override; + void rescan_spent() override; + void rescan_blockchain() override; + uint64_t get_balance() const override; + uint64_t get_balance(uint32_t account_index) const override; + uint64_t get_balance(uint32_t account_idx, uint32_t subaddress_idx) const override; + uint64_t get_unlocked_balance() const override; + uint64_t get_unlocked_balance(uint32_t account_index) const override; + uint64_t get_unlocked_balance(uint32_t account_idx, uint32_t subaddress_idx) const override; + monero_account get_account(const uint32_t account_idx, bool include_subaddresses) const override; + monero_account get_account(const uint32_t account_idx, bool include_subaddresses, bool skip_balances) const; + std::vector get_accounts(bool include_subaddresses, const std::string& tag) const override; + std::vector get_accounts(bool include_subaddresses, const std::string& tag, bool skip_balances) const; + monero_account create_account(const std::string& label = "") override; + std::vector get_subaddresses(const uint32_t account_idx, const std::vector& subaddress_indices, bool skip_balances) const; + std::vector get_subaddresses(uint32_t account_idx, const std::vector& subaddress_indices) const override; + std::vector get_subaddresses(const uint32_t account_idx) const override; + monero_subaddress get_subaddress(const uint32_t account_idx, const uint32_t subaddress_idx) const override; + monero_subaddress create_subaddress(uint32_t account_idx, const std::string& label = "") override; + void set_subaddress_label(uint32_t account_idx, uint32_t subaddress_idx, const std::string& label = "") override; + std::string export_outputs(bool all = false) const override; + int import_outputs(const std::string& outputs_hex) override; + std::vector> export_key_images(bool all = false) const override; + std::shared_ptr import_key_images(const std::vector>& key_images) override; + std::vector> get_new_key_images_from_last_import() override; + void freeze_output(const std::string& key_image) override; + void thaw_output(const std::string& key_image) override; + bool is_output_frozen(const std::string& key_image) override; + monero_tx_priority get_default_fee_priority() const override; + std::vector> create_txs(const monero_tx_config& conf) override; + std::shared_ptr sweep_output(const monero_tx_config& config) override; + std::vector> sweep_dust(bool relay = false) override; + std::vector relay_txs(const std::vector& tx_metadatas) override; + monero_tx_set describe_tx_set(const monero_tx_set& tx_set) override; + monero_tx_set sign_txs(const std::string& unsigned_tx_hex) override; + std::vector submit_txs(const std::string& signed_tx_hex) override; + std::string sign_message(const std::string& msg, monero_message_signature_type signature_type, uint32_t account_idx = 0, uint32_t subaddress_idx = 0) const override; + monero_message_signature_result verify_message(const std::string& msg, const std::string& address, const std::string& signature) const override; + std::string get_tx_key(const std::string& tx_hash) const override; + std::shared_ptr check_tx_key(const std::string& tx_hash, const std::string& tx_key, const std::string& address) const override; + std::string get_tx_proof(const std::string& tx_hash, const std::string& address, const std::string& message) const override; + // TODO why no override ? + std::shared_ptr check_tx_proof(const std::string& tx_hash, const std::string& address, const std::string& message, const std::string& signature) const; + std::string get_spend_proof(const std::string& tx_hash, const std::string& message) const override; + bool check_spend_proof(const std::string& tx_hash, const std::string& message, const std::string& signature) const override; + std::string get_reserve_proof_wallet(const std::string& message) const override; + std::string get_reserve_proof_account(uint32_t account_idx, uint64_t amount, const std::string& message) const override; + std::shared_ptr check_reserve_proof(const std::string& address, const std::string& message, const std::string& signature) const override; + std::vector get_tx_notes(const std::vector& tx_hashes) const override; + void set_tx_notes(const std::vector& tx_hashes, const std::vector& notes) override; + std::vector get_address_book_entries(const std::vector& indices) const override; + uint64_t add_address_book_entry(const std::string& address, const std::string& description) override; + void edit_address_book_entry(uint64_t index, bool set_address, const std::string& address, bool set_description, const std::string& description) override; + 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; + void set_account_tag_label(const std::string& tag, const std::string& label) override; + void set_account_label(uint32_t account_index, const std::string& label) override; + std::string get_payment_uri(const monero_tx_config& config) const override; + // TODO why no override ? + std::shared_ptr parse_payment_uri(const std::string& uri) const; + void set_attribute(const std::string& key, const std::string& val) override; + bool get_attribute(const std::string& key, std::string& value) const override; + void start_mining(boost::optional num_threads, boost::optional background_mining, boost::optional ignore_battery) override; + void stop_mining() override; + bool is_multisig_import_needed() const override; + monero_multisig_info get_multisig_info() const override; + std::string prepare_multisig() override; + std::string make_multisig(const std::vector& multisig_hexes, int threshold, const std::string& password) override; + monero_multisig_init_result exchange_multisig_keys(const std::vector& multisig_hexes, const std::string& password); + std::string export_multisig_hex() override; + int import_multisig_hex(const std::vector& multisig_hexes) override; + monero_multisig_sign_result sign_multisig_tx_hex(const std::string& multisig_tx_hex) override; + std::vector submit_multisig_tx_hex(const std::string& signed_multisig_tx_hex); + void change_password(const std::string& old_password, const std::string& new_password) override; + void save() override; + void close(bool save = false) override; + std::shared_ptr get_balances(boost::optional account_idx, boost::optional subaddress_idx) const override; + +protected: + 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::shared_ptr m_poller; + + mutable boost::recursive_mutex m_sync_mutex; + mutable serializable_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); + + std::string query_key(const std::string& key_type) const; + std::vector> sweep_account(const monero_tx_config &conf); + void clear_address_cache(); + void refresh_listening(); + void poll(); + void clear(); +}; diff --git a/tests/test_monero_daemon_rpc.py b/tests/test_monero_daemon_rpc.py index d9dd0d2..2ec936e 100644 --- a/tests/test_monero_daemon_rpc.py +++ b/tests/test_monero_daemon_rpc.py @@ -685,14 +685,14 @@ def test_prune_blockchain(self): # Can check for an update @pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") - @pytest.mark.flaky(reruns=3, reruns_delay=2) + @pytest.mark.flaky(reruns=5, reruns_delay=2) def test_check_for_update(self): result: MoneroDaemonUpdateCheckResult = self._daemon.check_for_update() Utils.test_update_check_result(result) # Can download an update @pytest.mark.skipif(Utils.TEST_NON_RELAYS is False, reason="TEST_NON_RELAYS disabled") - @pytest.mark.flaky(reruns=3, reruns_delay=2) + @pytest.mark.flaky(reruns=5, reruns_delay=2) def test_download_update(self): # download to default path result: MoneroDaemonUpdateDownloadResult = self._daemon.download_update()