diff options
Diffstat (limited to 'src/wallet')
-rw-r--r-- | src/wallet/CMakeLists.txt | 3 | ||||
-rw-r--r-- | src/wallet/api/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/wallet/api/wallet.cpp | 138 | ||||
-rw-r--r-- | src/wallet/api/wallet.h | 8 | ||||
-rw-r--r-- | src/wallet/api/wallet2_api.h | 42 | ||||
-rw-r--r-- | src/wallet/api/wallet_manager.cpp | 44 | ||||
-rw-r--r-- | src/wallet/api/wallet_manager.h | 16 | ||||
-rw-r--r-- | src/wallet/node_rpc_proxy.cpp | 64 | ||||
-rw-r--r-- | src/wallet/ringdb.cpp | 445 | ||||
-rw-r--r-- | src/wallet/ringdb.h | 65 | ||||
-rw-r--r-- | src/wallet/wallet2.cpp | 981 | ||||
-rw-r--r-- | src/wallet/wallet2.h | 78 | ||||
-rw-r--r-- | src/wallet/wallet_errors.h | 12 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server.cpp | 10 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server.h | 2 |
15 files changed, 1667 insertions, 242 deletions
diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt index d82e1dace..a5a4c7f56 100644 --- a/src/wallet/CMakeLists.txt +++ b/src/wallet/CMakeLists.txt @@ -33,6 +33,7 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(wallet_sources wallet2.cpp wallet_args.cpp + ringdb.cpp node_rpc_proxy.cpp) set(wallet_private_headers @@ -42,6 +43,7 @@ set(wallet_private_headers wallet_rpc_server.h wallet_rpc_server_commands_defs.h wallet_rpc_server_error_codes.h + ringdb.h node_rpc_proxy.h) monero_private_headers(wallet @@ -55,6 +57,7 @@ target_link_libraries(wallet common cryptonote_core mnemonics + ${LMDB_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_SERIALIZATION_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} diff --git a/src/wallet/api/CMakeLists.txt b/src/wallet/api/CMakeLists.txt index 1e67495f1..d6f2bf6b7 100644 --- a/src/wallet/api/CMakeLists.txt +++ b/src/wallet/api/CMakeLists.txt @@ -69,6 +69,7 @@ target_link_libraries(wallet_api common cryptonote_core mnemonics + ${LMDB_LIBRARY} ${Boost_CHRONO_LIBRARY} ${Boost_SERIALIZATION_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index fb9e8b28b..367011eaa 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -60,7 +60,7 @@ namespace Monero { namespace { // copy-pasted from simplewallet - static const size_t DEFAULT_MIXIN = 4; + static const size_t DEFAULT_MIXIN = 6; static const int DEFAULT_REFRESH_INTERVAL_MILLIS = 1000 * 10; // limit maximum refresh interval as one minute static const int MAX_REFRESH_INTERVAL_MILLIS = 1000 * 60 * 1; @@ -68,6 +68,15 @@ namespace { static const int DEFAULT_REMOTE_NODE_REFRESH_INTERVAL_MILLIS = 1000 * 10; // Connection timeout 30 sec static const int DEFAULT_CONNECTION_TIMEOUT_MILLIS = 1000 * 30; + + std::string get_default_ringdb_path() + { + boost::filesystem::path dir = tools::get_default_data_dir(); + // remove .bitmonero, replace with .shared-ringdb + dir = dir.remove_filename(); + dir /= ".shared-ringdb"; + return dir.string(); + } } struct Wallet2CallbackImpl : public tools::i_wallet2_callback @@ -296,14 +305,14 @@ uint64_t Wallet::maximumAllowedAmount() return std::numeric_limits<uint64_t>::max(); } -void Wallet::init(const char *argv0, const char *default_log_base_name) { +void Wallet::init(const char *argv0, const char *default_log_base_name, const std::string &log_path, bool console) { #ifdef WIN32 // Activate UTF-8 support for Boost filesystem classes on Windows std::locale::global(boost::locale::generator().generate("")); boost::filesystem::path::imbue(std::locale()); #endif epee::string_tools::set_module_name_and_folder(argv0); - mlog_configure(mlog_get_default_log_path(default_log_base_name), true); + mlog_configure(log_path.empty() ? mlog_get_default_log_path(default_log_base_name) : log_path.c_str(), console); } void Wallet::debug(const std::string &category, const std::string &str) { @@ -590,6 +599,7 @@ bool WalletImpl::open(const std::string &path, const std::string &password) // Rebuilding wallet cache, using refresh height from .keys file m_rebuildWalletCache = true; } + m_wallet->set_ring_database(get_default_ringdb_path()); m_wallet->load(path, password); m_password = password; @@ -1777,6 +1787,7 @@ void WalletImpl::doRefresh() if (m_history->count() == 0) { m_history->refresh(); } + m_wallet->find_and_save_rings(false); } else { LOG_PRINT_L3(__FUNCTION__ << ": skipping refresh - daemon is not synced"); } @@ -1897,6 +1908,127 @@ bool WalletImpl::useForkRules(uint8_t version, int64_t early_blocks) const return m_wallet->use_fork_rules(version,early_blocks); } +bool WalletImpl::blackballOutputs(const std::vector<std::string> &pubkeys, bool add) +{ + std::vector<crypto::public_key> raw_pubkeys; + raw_pubkeys.reserve(pubkeys.size()); + for (const std::string &str: pubkeys) + { + crypto::public_key pkey; + if (!epee::string_tools::hex_to_pod(str, pkey)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse output public key"); + return false; + } + raw_pubkeys.push_back(pkey); + } + bool ret = m_wallet->set_blackballed_outputs(raw_pubkeys, add); + if (!ret) + { + m_status = Status_Error; + m_errorString = tr("Failed to set blackballed outputs"); + return false; + } + return true; +} + +bool WalletImpl::unblackballOutput(const std::string &pubkey) +{ + crypto::public_key raw_pubkey; + if (!epee::string_tools::hex_to_pod(pubkey, raw_pubkey)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse output public key"); + return false; + } + bool ret = m_wallet->unblackball_output(raw_pubkey); + if (!ret) + { + m_status = Status_Error; + m_errorString = tr("Failed to unblackball output"); + return false; + } + return true; +} + +bool WalletImpl::getRing(const std::string &key_image, std::vector<uint64_t> &ring) const +{ + crypto::key_image raw_key_image; + if (!epee::string_tools::hex_to_pod(key_image, raw_key_image)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse key image"); + return false; + } + bool ret = m_wallet->get_ring(raw_key_image, ring); + if (!ret) + { + m_status = Status_Error; + m_errorString = tr("Failed to get ring"); + return false; + } + return true; +} + +bool WalletImpl::getRings(const std::string &txid, std::vector<std::pair<std::string, std::vector<uint64_t>>> &rings) const +{ + crypto::hash raw_txid; + if (!epee::string_tools::hex_to_pod(txid, raw_txid)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse txid"); + return false; + } + std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> raw_rings; + bool ret = m_wallet->get_rings(raw_txid, raw_rings); + if (!ret) + { + m_status = Status_Error; + m_errorString = tr("Failed to get rings"); + return false; + } + for (const auto &r: raw_rings) + { + rings.push_back(std::make_pair(epee::string_tools::pod_to_hex(r.first), r.second)); + } + return true; +} + +bool WalletImpl::setRing(const std::string &key_image, const std::vector<uint64_t> &ring, bool relative) +{ + crypto::key_image raw_key_image; + if (!epee::string_tools::hex_to_pod(key_image, raw_key_image)) + { + m_status = Status_Error; + m_errorString = tr("Failed to parse key image"); + return false; + } + bool ret = m_wallet->set_ring(raw_key_image, ring, relative); + if (!ret) + { + m_status = Status_Error; + m_errorString = tr("Failed to set ring"); + return false; + } + return true; +} + +void WalletImpl::segregatePreForkOutputs(bool segregate) +{ + m_wallet->segregate_pre_fork_outputs(segregate); +} + +void WalletImpl::segregationHeight(uint64_t height) +{ + m_wallet->segregation_height(height); +} + +void WalletImpl::keyReuseMitigation2(bool mitigation) +{ + m_wallet->key_reuse_mitigation2(mitigation); +} + } // namespace namespace Bitmonero = Monero; diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 9b4a0cc12..4929c9673 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -163,6 +163,14 @@ public: virtual std::string getDefaultDataDir() const; virtual bool lightWalletLogin(bool &isNewWallet) const; virtual bool lightWalletImportWalletRequest(std::string &payment_id, uint64_t &fee, bool &new_request, bool &request_fulfilled, std::string &payment_address, std::string &status); + virtual bool blackballOutputs(const std::vector<std::string> &pubkeys, bool add); + virtual bool unblackballOutput(const std::string &pubkey); + virtual bool getRing(const std::string &key_image, std::vector<uint64_t> &ring) const; + virtual bool getRings(const std::string &txid, std::vector<std::pair<std::string, std::vector<uint64_t>>> &rings) const; + virtual bool setRing(const std::string &key_image, const std::vector<uint64_t> &ring, bool relative); + virtual void segregatePreForkOutputs(bool segregate); + virtual void segregationHeight(uint64_t height); + virtual void keyReuseMitigation2(bool mitigation); private: void clearStatus() const; diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index 87c1cccfa..4fbc7298a 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -33,6 +33,7 @@ #include <string> #include <vector> +#include <list> #include <set> #include <ctime> #include <iostream> @@ -555,7 +556,8 @@ struct Wallet } static uint64_t maximumAllowedAmount(); // Easylogger wrapper - static void init(const char *argv0, const char *default_log_base_name); + static void init(const char *argv0, const char *default_log_base_name) { init(argv0, default_log_base_name, "", true); } + static void init(const char *argv0, const char *default_log_base_name, const std::string &log_path, bool console); static void debug(const std::string &category, const std::string &str); static void info(const std::string &category, const std::string &str); static void warning(const std::string &category, const std::string &str); @@ -756,6 +758,30 @@ struct Wallet */ virtual bool rescanSpent() = 0; + //! blackballs a set of outputs + virtual bool blackballOutputs(const std::vector<std::string> &pubkeys, bool add) = 0; + + //! unblackballs an output + virtual bool unblackballOutput(const std::string &pubkey) = 0; + + //! gets the ring used for a key image, if any + virtual bool getRing(const std::string &key_image, std::vector<uint64_t> &ring) const = 0; + + //! gets the rings used for a txid, if any + virtual bool getRings(const std::string &txid, std::vector<std::pair<std::string, std::vector<uint64_t>>> &rings) const = 0; + + //! sets the ring used for a key image + virtual bool setRing(const std::string &key_image, const std::vector<uint64_t> &ring, bool relative) = 0; + + //! sets whether pre-fork outs are to be segregated + virtual void segregatePreForkOutputs(bool segregate) = 0; + + //! sets the height where segregation should occur + virtual void segregationHeight(uint64_t height) = 0; + + //! secondary key reuse mitigation + virtual void keyReuseMitigation2(bool mitigation) = 0; + //! Light wallet authenticate and login virtual bool lightWalletLogin(bool &isNewWallet) const = 0; @@ -931,25 +957,25 @@ struct WalletManager virtual void setDaemonAddress(const std::string &address) = 0; //! returns whether the daemon can be reached, and its version number - virtual bool connected(uint32_t *version = NULL) const = 0; + virtual bool connected(uint32_t *version = NULL) = 0; //! returns current blockchain height - virtual uint64_t blockchainHeight() const = 0; + virtual uint64_t blockchainHeight() = 0; //! returns current blockchain target height - virtual uint64_t blockchainTargetHeight() const = 0; + virtual uint64_t blockchainTargetHeight() = 0; //! returns current network difficulty - virtual uint64_t networkDifficulty() const = 0; + virtual uint64_t networkDifficulty() = 0; //! returns current mining hash rate (0 if not mining) - virtual double miningHashRate() const = 0; + virtual double miningHashRate() = 0; //! returns current block target - virtual uint64_t blockTarget() const = 0; + virtual uint64_t blockTarget() = 0; //! returns true iff mining - virtual bool isMining() const = 0; + virtual bool isMining() = 0; //! starts mining with the set number of threads virtual bool startMining(const std::string &address, uint32_t threads = 1, bool background_mining = false, bool ignore_battery = true) = 0; diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp index 80f5780b5..a63716576 100644 --- a/src/wallet/api/wallet_manager.cpp +++ b/src/wallet/api/wallet_manager.cpp @@ -47,15 +47,6 @@ namespace epee { unsigned int g_test_dbg_lock_sleep = 0; } -namespace { - template<typename Request, typename Response> - bool connect_and_invoke(const std::string& address, const std::string& path, const Request& request, Response& response) - { - epee::net_utils::http::http_simple_client client{}; - return client.set_server(address, boost::none) && epee::net_utils::invoke_http_json(path, request, response, client); - } -} - namespace Monero { Wallet *WalletManagerImpl::createWallet(const std::string &path, const std::string &password, @@ -193,16 +184,19 @@ std::string WalletManagerImpl::errorString() const void WalletManagerImpl::setDaemonAddress(const std::string &address) { m_daemonAddress = address; + if(m_http_client.is_connected()) + m_http_client.disconnect(); + m_http_client.set_server(address, boost::none); } -bool WalletManagerImpl::connected(uint32_t *version) const +bool WalletManagerImpl::connected(uint32_t *version) { epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_VERSION::request> req_t = AUTO_VAL_INIT(req_t); epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_VERSION::response, std::string> resp_t = AUTO_VAL_INIT(resp_t); req_t.jsonrpc = "2.0"; req_t.id = epee::serialization::storage_entry(0); req_t.method = "get_version"; - if (!connect_and_invoke(m_daemonAddress, "/json_rpc", req_t, resp_t)) + if (!epee::net_utils::invoke_http_json("/json_rpc", req_t, resp_t, m_http_client)) return false; if (version) @@ -210,65 +204,65 @@ bool WalletManagerImpl::connected(uint32_t *version) const return true; } -uint64_t WalletManagerImpl::blockchainHeight() const +uint64_t WalletManagerImpl::blockchainHeight() { cryptonote::COMMAND_RPC_GET_INFO::request ireq; cryptonote::COMMAND_RPC_GET_INFO::response ires; - if (!connect_and_invoke(m_daemonAddress, "/getinfo", ireq, ires)) + if (!epee::net_utils::invoke_http_json("/getinfo", ireq, ires, m_http_client)) return 0; return ires.height; } -uint64_t WalletManagerImpl::blockchainTargetHeight() const +uint64_t WalletManagerImpl::blockchainTargetHeight() { cryptonote::COMMAND_RPC_GET_INFO::request ireq; cryptonote::COMMAND_RPC_GET_INFO::response ires; - if (!connect_and_invoke(m_daemonAddress, "/getinfo", ireq, ires)) + if (!epee::net_utils::invoke_http_json("/getinfo", ireq, ires, m_http_client)) return 0; return ires.target_height >= ires.height ? ires.target_height : ires.height; } -uint64_t WalletManagerImpl::networkDifficulty() const +uint64_t WalletManagerImpl::networkDifficulty() { cryptonote::COMMAND_RPC_GET_INFO::request ireq; cryptonote::COMMAND_RPC_GET_INFO::response ires; - if (!connect_and_invoke(m_daemonAddress, "/getinfo", ireq, ires)) + if (!epee::net_utils::invoke_http_json("/getinfo", ireq, ires, m_http_client)) return 0; return ires.difficulty; } -double WalletManagerImpl::miningHashRate() const +double WalletManagerImpl::miningHashRate() { cryptonote::COMMAND_RPC_MINING_STATUS::request mreq; cryptonote::COMMAND_RPC_MINING_STATUS::response mres; epee::net_utils::http::http_simple_client http_client; - if (!connect_and_invoke(m_daemonAddress, "/mining_status", mreq, mres)) + if (!epee::net_utils::invoke_http_json("/mining_status", mreq, mres, m_http_client)) return 0.0; if (!mres.active) return 0.0; return mres.speed; } -uint64_t WalletManagerImpl::blockTarget() const +uint64_t WalletManagerImpl::blockTarget() { cryptonote::COMMAND_RPC_GET_INFO::request ireq; cryptonote::COMMAND_RPC_GET_INFO::response ires; - if (!connect_and_invoke(m_daemonAddress, "/getinfo", ireq, ires)) + if (!epee::net_utils::invoke_http_json("/getinfo", ireq, ires, m_http_client)) return 0; return ires.target; } -bool WalletManagerImpl::isMining() const +bool WalletManagerImpl::isMining() { cryptonote::COMMAND_RPC_MINING_STATUS::request mreq; cryptonote::COMMAND_RPC_MINING_STATUS::response mres; - if (!connect_and_invoke(m_daemonAddress, "/mining_status", mreq, mres)) + if (!epee::net_utils::invoke_http_json("/mining_status", mreq, mres, m_http_client)) return false; return mres.active; } @@ -283,7 +277,7 @@ bool WalletManagerImpl::startMining(const std::string &address, uint32_t threads mreq.ignore_battery = ignore_battery; mreq.do_background_mining = background_mining; - if (!connect_and_invoke(m_daemonAddress, "/start_mining", mreq, mres)) + if (!epee::net_utils::invoke_http_json("/start_mining", mreq, mres, m_http_client)) return false; return mres.status == CORE_RPC_STATUS_OK; } @@ -293,7 +287,7 @@ bool WalletManagerImpl::stopMining() cryptonote::COMMAND_RPC_STOP_MINING::request mreq; cryptonote::COMMAND_RPC_STOP_MINING::response mres; - if (!connect_and_invoke(m_daemonAddress, "/stop_mining", mreq, mres)) + if (!epee::net_utils::invoke_http_json("/stop_mining", mreq, mres, m_http_client)) return false; return mres.status == CORE_RPC_STATUS_OK; } diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h index 409a6d499..26238b658 100644 --- a/src/wallet/api/wallet_manager.h +++ b/src/wallet/api/wallet_manager.h @@ -30,6 +30,7 @@ #include "wallet/api/wallet2_api.h" +#include "net/http_client.h" #include <string> namespace Monero { @@ -69,13 +70,13 @@ public: std::vector<std::string> findWallets(const std::string &path); std::string errorString() const; void setDaemonAddress(const std::string &address); - bool connected(uint32_t *version = NULL) const; - uint64_t blockchainHeight() const; - uint64_t blockchainTargetHeight() const; - uint64_t networkDifficulty() const; - double miningHashRate() const; - uint64_t blockTarget() const; - bool isMining() const; + bool connected(uint32_t *version = NULL); + uint64_t blockchainHeight(); + uint64_t blockchainTargetHeight(); + uint64_t networkDifficulty(); + double miningHashRate(); + uint64_t blockTarget(); + bool isMining(); bool startMining(const std::string &address, uint32_t threads = 1, bool background_mining = false, bool ignore_battery = true); bool stopMining(); std::string resolveOpenAlias(const std::string &address, bool &dnssec_valid) const; @@ -84,6 +85,7 @@ private: WalletManagerImpl() {} friend struct WalletManagerFactory; std::string m_daemonAddress; + epee::net_utils::http::http_simple_client m_http_client; std::string m_errorString; }; diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index f11ff9295..c5d869354 100644 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -70,18 +70,15 @@ boost::optional<std::string> NodeRPCProxy::get_rpc_version(uint32_t &rpc_version { if (m_rpc_version == 0) { - epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_VERSION::request> req_t = AUTO_VAL_INIT(req_t); - epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_VERSION::response, std::string> resp_t = AUTO_VAL_INIT(resp_t); - req_t.jsonrpc = "2.0"; - req_t.id = epee::serialization::storage_entry(0); - req_t.method = "get_version"; + cryptonote::COMMAND_RPC_GET_VERSION::request req_t = AUTO_VAL_INIT(req_t); + cryptonote::COMMAND_RPC_GET_VERSION::response resp_t = AUTO_VAL_INIT(resp_t); m_daemon_rpc_mutex.lock(); - bool r = net_utils::invoke_http_json("/json_rpc", req_t, resp_t, m_http_client, rpc_timeout); + bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_version", req_t, resp_t, m_http_client, rpc_timeout); m_daemon_rpc_mutex.unlock(); CHECK_AND_ASSERT_MES(r, std::string(), "Failed to connect to daemon"); - CHECK_AND_ASSERT_MES(resp_t.result.status != CORE_RPC_STATUS_BUSY, resp_t.result.status, "Failed to connect to daemon"); - CHECK_AND_ASSERT_MES(resp_t.result.status == CORE_RPC_STATUS_OK, resp_t.result.status, "Failed to get daemon RPC version"); - m_rpc_version = resp_t.result.version; + CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon"); + CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get daemon RPC version"); + m_rpc_version = resp_t.version; } rpc_version = m_rpc_version; return boost::optional<std::string>(); @@ -118,20 +115,17 @@ boost::optional<std::string> NodeRPCProxy::get_target_height(uint64_t &height) c const time_t now = time(NULL); if (m_target_height == 0 || now >= m_target_height_time + 30) // re-cache every 30 seconds { - epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_INFO::request> req_t = AUTO_VAL_INIT(req_t); - epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_INFO::response, std::string> resp_t = AUTO_VAL_INIT(resp_t); + cryptonote::COMMAND_RPC_GET_INFO::request req_t = AUTO_VAL_INIT(req_t); + cryptonote::COMMAND_RPC_GET_INFO::response resp_t = AUTO_VAL_INIT(resp_t); - req_t.jsonrpc = "2.0"; - req_t.id = epee::serialization::storage_entry(0); - req_t.method = "get_info"; m_daemon_rpc_mutex.lock(); - bool r = net_utils::invoke_http_json("/json_rpc", req_t, resp_t, m_http_client, rpc_timeout); + bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_info", req_t, resp_t, m_http_client, rpc_timeout); m_daemon_rpc_mutex.unlock(); CHECK_AND_ASSERT_MES(r, std::string(), "Failed to connect to daemon"); - CHECK_AND_ASSERT_MES(resp_t.result.status != CORE_RPC_STATUS_BUSY, resp_t.result.status, "Failed to connect to daemon"); - CHECK_AND_ASSERT_MES(resp_t.result.status == CORE_RPC_STATUS_OK, resp_t.result.status, "Failed to get target blockchain height"); - m_target_height = resp_t.result.target_height; + CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon"); + CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get target blockchain height"); + m_target_height = resp_t.target_height; m_target_height_time = now; } height = m_target_height; @@ -142,20 +136,17 @@ boost::optional<std::string> NodeRPCProxy::get_earliest_height(uint8_t version, { if (m_earliest_height[version] == 0) { - epee::json_rpc::request<cryptonote::COMMAND_RPC_HARD_FORK_INFO::request> req_t = AUTO_VAL_INIT(req_t); - epee::json_rpc::response<cryptonote::COMMAND_RPC_HARD_FORK_INFO::response, std::string> resp_t = AUTO_VAL_INIT(resp_t); + cryptonote::COMMAND_RPC_HARD_FORK_INFO::request req_t = AUTO_VAL_INIT(req_t); + cryptonote::COMMAND_RPC_HARD_FORK_INFO::response resp_t = AUTO_VAL_INIT(resp_t); m_daemon_rpc_mutex.lock(); - req_t.jsonrpc = "2.0"; - req_t.id = epee::serialization::storage_entry(0); - req_t.method = "hard_fork_info"; - req_t.params.version = version; - bool r = net_utils::invoke_http_json("/json_rpc", req_t, resp_t, m_http_client, rpc_timeout); + req_t.version = version; + bool r = net_utils::invoke_http_json_rpc("/json_rpc", "hard_fork_info", req_t, resp_t, m_http_client, rpc_timeout); m_daemon_rpc_mutex.unlock(); CHECK_AND_ASSERT_MES(r, std::string(), "Failed to connect to daemon"); - CHECK_AND_ASSERT_MES(resp_t.result.status != CORE_RPC_STATUS_BUSY, resp_t.result.status, "Failed to connect to daemon"); - CHECK_AND_ASSERT_MES(resp_t.result.status == CORE_RPC_STATUS_OK, resp_t.result.status, "Failed to get hard fork status"); - m_earliest_height[version] = resp_t.result.enabled ? resp_t.result.earliest_height : std::numeric_limits<uint64_t>::max(); + CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon"); + CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get hard fork status"); + m_earliest_height[version] = resp_t.enabled ? resp_t.earliest_height : std::numeric_limits<uint64_t>::max(); } earliest_height = m_earliest_height[version]; @@ -172,20 +163,17 @@ boost::optional<std::string> NodeRPCProxy::get_dynamic_per_kb_fee_estimate(uint6 if (m_dynamic_per_kb_fee_estimate_cached_height != height || m_dynamic_per_kb_fee_estimate_grace_blocks != grace_blocks) { - epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE::request> req_t = AUTO_VAL_INIT(req_t); - epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE::response, std::string> resp_t = AUTO_VAL_INIT(resp_t); + cryptonote::COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE::request req_t = AUTO_VAL_INIT(req_t); + cryptonote::COMMAND_RPC_GET_PER_KB_FEE_ESTIMATE::response resp_t = AUTO_VAL_INIT(resp_t); m_daemon_rpc_mutex.lock(); - req_t.jsonrpc = "2.0"; - req_t.id = epee::serialization::storage_entry(0); - req_t.method = "get_fee_estimate"; - req_t.params.grace_blocks = grace_blocks; - bool r = net_utils::invoke_http_json("/json_rpc", req_t, resp_t, m_http_client, rpc_timeout); + req_t.grace_blocks = grace_blocks; + bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_fee_estimate", req_t, resp_t, m_http_client, rpc_timeout); m_daemon_rpc_mutex.unlock(); CHECK_AND_ASSERT_MES(r, std::string(), "Failed to connect to daemon"); - CHECK_AND_ASSERT_MES(resp_t.result.status != CORE_RPC_STATUS_BUSY, resp_t.result.status, "Failed to connect to daemon"); - CHECK_AND_ASSERT_MES(resp_t.result.status == CORE_RPC_STATUS_OK, resp_t.result.status, "Failed to get fee estimate"); - m_dynamic_per_kb_fee_estimate = resp_t.result.fee; + CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon"); + CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get fee estimate"); + m_dynamic_per_kb_fee_estimate = resp_t.fee; m_dynamic_per_kb_fee_estimate_cached_height = height; m_dynamic_per_kb_fee_estimate_grace_blocks = grace_blocks; } diff --git a/src/wallet/ringdb.cpp b/src/wallet/ringdb.cpp new file mode 100644 index 000000000..44992520f --- /dev/null +++ b/src/wallet/ringdb.cpp @@ -0,0 +1,445 @@ +// Copyright (c) 2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <lmdb.h> +#include <boost/algorithm/string.hpp> +#include <boost/range/adaptor/transformed.hpp> +#include <boost/filesystem.hpp> +#include "misc_log_ex.h" +#include "misc_language.h" +#include "wallet_errors.h" +#include "ringdb.h" + +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "wallet.ringdb" + +static const char zerokey[8] = {0}; +static const MDB_val zerokeyval = { sizeof(zerokey), (void *)zerokey }; + +static int compare_hash32(const MDB_val *a, const MDB_val *b) +{ + uint32_t *va = (uint32_t*) a->mv_data; + uint32_t *vb = (uint32_t*) b->mv_data; + for (int n = 7; n >= 0; n--) + { + if (va[n] == vb[n]) + continue; + return va[n] < vb[n] ? -1 : 1; + } + + return 0; +} + +static std::string compress_ring(const std::vector<uint64_t> &ring) +{ + std::string s; + for (uint64_t out: ring) + s += tools::get_varint_data(out); + return s; +} + +static std::vector<uint64_t> decompress_ring(const std::string &s) +{ + std::vector<uint64_t> ring; + int read = 0; + for (std::string::const_iterator i = s.begin(); i != s.cend(); std::advance(i, read)) + { + uint64_t out; + std::string tmp(i, s.cend()); + read = tools::read_varint(tmp.begin(), tmp.end(), out); + THROW_WALLET_EXCEPTION_IF(read <= 0 || read > 256, tools::error::wallet_internal_error, "Internal error decompressing ring"); + ring.push_back(out); + } + return ring; +} + +std::string get_rings_filename(boost::filesystem::path filename) +{ + if (!boost::filesystem::is_directory(filename)) + filename.remove_filename(); + return filename.string(); +} + +static crypto::chacha_iv make_iv(const crypto::key_image &key_image, const crypto::chacha_key &key) +{ + static const char salt[] = "ringdsb"; + + uint8_t buffer[sizeof(key_image) + sizeof(key) + sizeof(salt)]; + memcpy(buffer, &key_image, sizeof(key_image)); + memcpy(buffer + sizeof(key_image), &key, sizeof(key)); + memcpy(buffer + sizeof(key_image) + sizeof(key), salt, sizeof(salt)); + crypto::hash hash; + crypto::cn_fast_hash(buffer, sizeof(buffer), hash.data); + static_assert(sizeof(hash) >= CHACHA_IV_SIZE, "Incompatible hash and chacha IV sizes"); + crypto::chacha_iv iv; + memcpy(&iv, &hash, CHACHA_IV_SIZE); + return iv; +} + +static std::string encrypt(const std::string &plaintext, const crypto::key_image &key_image, const crypto::chacha_key &key) +{ + const crypto::chacha_iv iv = make_iv(key_image, key); + std::string ciphertext; + ciphertext.resize(plaintext.size() + sizeof(iv)); + crypto::chacha20(plaintext.data(), plaintext.size(), key, iv, &ciphertext[sizeof(iv)]); + memcpy(&ciphertext[0], &iv, sizeof(iv)); + return ciphertext; +} + +static std::string encrypt(const crypto::key_image &key_image, const crypto::chacha_key &key) +{ + return encrypt(std::string((const char*)&key_image, sizeof(key_image)), key_image, key); +} + +static std::string decrypt(const std::string &ciphertext, const crypto::key_image &key_image, const crypto::chacha_key &key) +{ + const crypto::chacha_iv iv = make_iv(key_image, key); + std::string plaintext; + THROW_WALLET_EXCEPTION_IF(ciphertext.size() < sizeof(iv), tools::error::wallet_internal_error, "Bad ciphertext text"); + plaintext.resize(ciphertext.size() - sizeof(iv)); + crypto::chacha20(ciphertext.data() + sizeof(iv), ciphertext.size() - sizeof(iv), key, iv, &plaintext[0]); + return plaintext; +} + +static void store_relative_ring(MDB_txn *txn, MDB_dbi &dbi, const crypto::key_image &key_image, const std::vector<uint64_t> &relative_ring, const crypto::chacha_key &chacha_key) +{ + MDB_val key, data; + std::string key_ciphertext = encrypt(key_image, chacha_key); + key.mv_data = (void*)key_ciphertext.data(); + key.mv_size = key_ciphertext.size(); + std::string compressed_ring = compress_ring(relative_ring); + std::string data_ciphertext = encrypt(compressed_ring, key_image, chacha_key); + data.mv_size = data_ciphertext.size(); + data.mv_data = (void*)data_ciphertext.c_str(); + int dbr = mdb_put(txn, dbi, &key, &data, 0); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set ring for key image in LMDB table: " + std::string(mdb_strerror(dbr))); +} + +static int resize_env(MDB_env *env, const char *db_path, size_t needed) +{ + MDB_envinfo mei; + MDB_stat mst; + int ret; + + needed = std::max(needed, (size_t)(2ul * 1024 * 1024)); // at least 2 MB + + ret = mdb_env_info(env, &mei); + if (ret) + return ret; + ret = mdb_env_stat(env, &mst); + if (ret) + return ret; + uint64_t size_used = mst.ms_psize * mei.me_last_pgno; + uint64_t mapsize = mei.me_mapsize; + if (size_used + needed > mei.me_mapsize) + { + try + { + boost::filesystem::path path(db_path); + boost::filesystem::space_info si = boost::filesystem::space(path); + if(si.available < needed) + { + MERROR("!! WARNING: Insufficient free space to extend database !!: " << (si.available >> 20L) << " MB available"); + return ENOSPC; + } + } + catch(...) + { + // print something but proceed. + MWARNING("Unable to query free disk space."); + } + + mapsize += needed; + } + return mdb_env_set_mapsize(env, mapsize); +} + +static size_t get_ring_data_size(size_t n_entries) +{ + return n_entries * (32 + 1024); // highball 1kB for the ring data to make sure +} + +enum { BLACKBALL_BLACKBALL, BLACKBALL_UNBLACKBALL, BLACKBALL_QUERY, BLACKBALL_CLEAR}; + +namespace tools +{ + +ringdb::ringdb(std::string filename, const std::string &genesis): + filename(filename) +{ + MDB_txn *txn; + bool tx_active = false; + int dbr; + + tools::create_directories_if_necessary(filename); + + dbr = mdb_env_create(&env); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LDMB environment: " + std::string(mdb_strerror(dbr))); + dbr = mdb_env_set_maxdbs(env, 2); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set max env dbs: " + std::string(mdb_strerror(dbr))); + const std::string actual_filename = get_rings_filename(filename); + dbr = mdb_env_open(env, actual_filename.c_str(), 0, 0664); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to open rings database file '" + + actual_filename + "': " + std::string(mdb_strerror(dbr))); + + dbr = mdb_txn_begin(env, NULL, 0, &txn); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr))); + epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);}); + tx_active = true; + + dbr = mdb_dbi_open(txn, ("rings-" + genesis).c_str(), MDB_CREATE, &dbi_rings); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr))); + mdb_set_compare(txn, dbi_rings, compare_hash32); + + dbr = mdb_dbi_open(txn, ("blackballs-" + genesis).c_str(), MDB_CREATE | MDB_INTEGERKEY | MDB_DUPSORT | MDB_DUPFIXED, &dbi_blackballs); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to open LMDB dbi: " + std::string(mdb_strerror(dbr))); + mdb_set_dupsort(txn, dbi_blackballs, compare_hash32); + + dbr = mdb_txn_commit(txn); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to commit txn creating/opening database: " + std::string(mdb_strerror(dbr))); + tx_active = false; +} + +ringdb::~ringdb() +{ + mdb_dbi_close(env, dbi_rings); + mdb_dbi_close(env, dbi_blackballs); + mdb_env_close(env); +} + +bool ringdb::add_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx) +{ + MDB_txn *txn; + int dbr; + bool tx_active = false; + + dbr = resize_env(env, filename.c_str(), get_ring_data_size(tx.vin.size())); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set env map size"); + dbr = mdb_txn_begin(env, NULL, 0, &txn); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr))); + epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);}); + tx_active = true; + + for (const auto &in: tx.vin) + { + if (in.type() != typeid(cryptonote::txin_to_key)) + continue; + const auto &txin = boost::get<cryptonote::txin_to_key>(in); + const uint32_t ring_size = txin.key_offsets.size(); + if (ring_size == 1) + continue; + + store_relative_ring(txn, dbi_rings, txin.k_image, txin.key_offsets, chacha_key); + } + + dbr = mdb_txn_commit(txn); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to commit txn adding ring to database: " + std::string(mdb_strerror(dbr))); + tx_active = false; + return true; +} + +bool ringdb::remove_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx) +{ + MDB_txn *txn; + int dbr; + bool tx_active = false; + + dbr = resize_env(env, filename.c_str(), 0); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set env map size"); + dbr = mdb_txn_begin(env, NULL, 0, &txn); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr))); + epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);}); + tx_active = true; + + for (const auto &in: tx.vin) + { + if (in.type() != typeid(cryptonote::txin_to_key)) + continue; + const auto &txin = boost::get<cryptonote::txin_to_key>(in); + const uint32_t ring_size = txin.key_offsets.size(); + if (ring_size == 1) + continue; + + MDB_val key, data; + std::string key_ciphertext = encrypt(txin.k_image, chacha_key); + key.mv_data = (void*)key_ciphertext.data(); + key.mv_size = key_ciphertext.size(); + + dbr = mdb_get(txn, dbi_rings, &key, &data); + THROW_WALLET_EXCEPTION_IF(dbr && dbr != MDB_NOTFOUND, tools::error::wallet_internal_error, "Failed to look for key image in LMDB table: " + std::string(mdb_strerror(dbr))); + if (dbr == MDB_NOTFOUND) + continue; + THROW_WALLET_EXCEPTION_IF(data.mv_size <= 0, tools::error::wallet_internal_error, "Invalid ring data size"); + + MDEBUG("Removing ring data for key image " << txin.k_image); + dbr = mdb_del(txn, dbi_rings, &key, NULL); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to remove ring to database: " + std::string(mdb_strerror(dbr))); + } + + dbr = mdb_txn_commit(txn); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to commit txn removing ring to database: " + std::string(mdb_strerror(dbr))); + tx_active = false; + return true; +} + +bool ringdb::get_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector<uint64_t> &outs) +{ + MDB_txn *txn; + int dbr; + bool tx_active = false; + + dbr = resize_env(env, filename.c_str(), 0); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set env map size: " + std::string(mdb_strerror(dbr))); + dbr = mdb_txn_begin(env, NULL, 0, &txn); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr))); + epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);}); + tx_active = true; + + MDB_val key, data; + std::string key_ciphertext = encrypt(key_image, chacha_key); + key.mv_data = (void*)key_ciphertext.data(); + key.mv_size = key_ciphertext.size(); + dbr = mdb_get(txn, dbi_rings, &key, &data); + THROW_WALLET_EXCEPTION_IF(dbr && dbr != MDB_NOTFOUND, tools::error::wallet_internal_error, "Failed to look for key image in LMDB table: " + std::string(mdb_strerror(dbr))); + if (dbr == MDB_NOTFOUND) + return false; + THROW_WALLET_EXCEPTION_IF(data.mv_size <= 0, tools::error::wallet_internal_error, "Invalid ring data size"); + + std::string data_plaintext = decrypt(std::string((const char*)data.mv_data, data.mv_size), key_image, chacha_key); + outs = decompress_ring(data_plaintext); + MDEBUG("Found ring for key image " << key_image << ":"); + MDEBUG("Relative: " << boost::join(outs | boost::adaptors::transformed([](uint64_t out){return std::to_string(out);}), " ")); + outs = cryptonote::relative_output_offsets_to_absolute(outs); + MDEBUG("Absolute: " << boost::join(outs | boost::adaptors::transformed([](uint64_t out){return std::to_string(out);}), " ")); + + dbr = mdb_txn_commit(txn); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to commit txn getting ring from database: " + std::string(mdb_strerror(dbr))); + tx_active = false; + return true; +} + +bool ringdb::set_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, const std::vector<uint64_t> &outs, bool relative) +{ + MDB_txn *txn; + int dbr; + bool tx_active = false; + + dbr = resize_env(env, filename.c_str(), outs.size() * 64); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set env map size: " + std::string(mdb_strerror(dbr))); + dbr = mdb_txn_begin(env, NULL, 0, &txn); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr))); + epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);}); + tx_active = true; + + store_relative_ring(txn, dbi_rings, key_image, relative ? outs : cryptonote::absolute_output_offsets_to_relative(outs), chacha_key); + + dbr = mdb_txn_commit(txn); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to commit txn setting ring to database: " + std::string(mdb_strerror(dbr))); + tx_active = false; + return true; +} + +bool ringdb::blackball_worker(const crypto::public_key &output, int op) +{ + MDB_txn *txn; + MDB_cursor *cursor; + int dbr; + bool tx_active = false; + bool ret = true; + + dbr = resize_env(env, filename.c_str(), 32 * 2); // a pubkey, and some slack + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to set env map size: " + std::string(mdb_strerror(dbr))); + dbr = mdb_txn_begin(env, NULL, 0, &txn); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create LMDB transaction: " + std::string(mdb_strerror(dbr))); + epee::misc_utils::auto_scope_leave_caller txn_dtor = epee::misc_utils::create_scope_leave_handler([&](){if (tx_active) mdb_txn_abort(txn);}); + tx_active = true; + + MDB_val key = zerokeyval; + MDB_val data; + data.mv_data = (void*)&output; + data.mv_size = sizeof(output); + + switch (op) + { + case BLACKBALL_BLACKBALL: + MDEBUG("Blackballing output " << output); + dbr = mdb_put(txn, dbi_blackballs, &key, &data, MDB_NODUPDATA); + if (dbr == MDB_KEYEXIST) + dbr = 0; + break; + case BLACKBALL_UNBLACKBALL: + MDEBUG("Unblackballing output " << output); + dbr = mdb_del(txn, dbi_blackballs, &key, &data); + if (dbr == MDB_NOTFOUND) + dbr = 0; + break; + case BLACKBALL_QUERY: + dbr = mdb_cursor_open(txn, dbi_blackballs, &cursor); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to create cursor for blackballs table: " + std::string(mdb_strerror(dbr))); + dbr = mdb_cursor_get(cursor, &key, &data, MDB_GET_BOTH); + THROW_WALLET_EXCEPTION_IF(dbr && dbr != MDB_NOTFOUND, tools::error::wallet_internal_error, "Failed to lookup in blackballs table: " + std::string(mdb_strerror(dbr))); + ret = dbr != MDB_NOTFOUND; + if (dbr == MDB_NOTFOUND) + dbr = 0; + mdb_cursor_close(cursor); + break; + case BLACKBALL_CLEAR: + dbr = mdb_drop(txn, dbi_blackballs, 0); + break; + default: + THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, "Invalid blackball op"); + } + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to query blackballs table: " + std::string(mdb_strerror(dbr))); + + dbr = mdb_txn_commit(txn); + THROW_WALLET_EXCEPTION_IF(dbr, tools::error::wallet_internal_error, "Failed to commit txn blackballing output to database: " + std::string(mdb_strerror(dbr))); + tx_active = false; + return ret; +} + +bool ringdb::blackball(const crypto::public_key &output) +{ + return blackball_worker(output, BLACKBALL_BLACKBALL); +} + +bool ringdb::unblackball(const crypto::public_key &output) +{ + return blackball_worker(output, BLACKBALL_UNBLACKBALL); +} + +bool ringdb::blackballed(const crypto::public_key &output) +{ + return blackball_worker(output, BLACKBALL_QUERY); +} + +bool ringdb::clear_blackballs() +{ + return blackball_worker(crypto::public_key(), BLACKBALL_CLEAR); +} + +} diff --git a/src/wallet/ringdb.h b/src/wallet/ringdb.h new file mode 100644 index 000000000..2bd1ac149 --- /dev/null +++ b/src/wallet/ringdb.h @@ -0,0 +1,65 @@ +// Copyright (c) 2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include <string> +#include <vector> +#include <lmdb.h> +#include "wipeable_string.h" +#include "crypto/crypto.h" +#include "cryptonote_basic/cryptonote_basic.h" + +namespace tools +{ + class ringdb + { + public: + ringdb(std::string filename, const std::string &genesis); + ~ringdb(); + + bool add_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx); + bool remove_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx); + bool get_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, std::vector<uint64_t> &outs); + bool set_ring(const crypto::chacha_key &chacha_key, const crypto::key_image &key_image, const std::vector<uint64_t> &outs, bool relative); + + bool blackball(const crypto::public_key &output); + bool unblackball(const crypto::public_key &output); + bool blackballed(const crypto::public_key &output); + bool clear_blackballs(); + + private: + bool blackball_worker(const crypto::public_key &output, int op); + + private: + std::string filename; + MDB_env *env; + MDB_dbi dbi_rings; + MDB_dbi dbi_blackballs; + }; +} diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index f574068b6..d11c99378 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -65,7 +65,9 @@ using namespace epee; #include "common/json_util.h" #include "memwipe.h" #include "common/base58.h" +#include "common/dns_utils.h" #include "ringct/rctSigs.h" +#include "ringdb.h" extern "C" { @@ -93,7 +95,9 @@ using namespace cryptonote; #define MULTISIG_UNSIGNED_TX_PREFIX "Monero multisig unsigned tx set\001" #define RECENT_OUTPUT_RATIO (0.5) // 50% of outputs are from the recent zone -#define RECENT_OUTPUT_ZONE ((time_t)(1.8 * 86400)) // last 1.8 day makes up the recent zone (taken from monerolink.pdf, Miller et al) +#define RECENT_OUTPUT_DAYS (1.8) // last 1.8 day makes up the recent zone (taken from monerolink.pdf, Miller et al) +#define RECENT_OUTPUT_ZONE ((time_t)(RECENT_OUTPUT_DAYS * 86400)) +#define RECENT_OUTPUT_BLOCKS (RECENT_OUTPUT_DAYS * 720) #define FEE_ESTIMATE_GRACE_BLOCKS 10 // estimate fee valid for that many blocks @@ -106,6 +110,24 @@ using namespace cryptonote; #define MULTISIG_EXPORT_FILE_MAGIC "Monero multisig export\001" +#define SEGREGATION_FORK_HEIGHT 1546000 +#define TESTNET_SEGREGATION_FORK_HEIGHT 1000000 +#define STAGENET_SEGREGATION_FORK_HEIGHT 1000000 +#define SEGREGATION_FORK_VICINITY 1500 /* blocks */ + + +namespace +{ + std::string get_default_ringdb_path() + { + boost::filesystem::path dir = tools::get_default_data_dir(); + // remove .bitmonero, replace with .shared-ringdb + dir = dir.remove_filename(); + dir /= ".shared-ringdb"; + return dir.string(); + } +} + namespace { // Create on-demand to prevent static initialization order fiasco issues. @@ -119,6 +141,16 @@ struct options { const command_line::arg_descriptor<bool> testnet = {"testnet", tools::wallet2::tr("For testnet. Daemon must also be launched with --testnet flag"), false}; const command_line::arg_descriptor<bool> stagenet = {"stagenet", tools::wallet2::tr("For stagenet. Daemon must also be launched with --stagenet flag"), false}; const command_line::arg_descriptor<bool> restricted = {"restricted-rpc", tools::wallet2::tr("Restricts to view-only commands"), false}; + const command_line::arg_descriptor<std::string, false, true> shared_ringdb_dir = { + "shared-ringdb-dir", tools::wallet2::tr("Set shared ring database path"), + get_default_ringdb_path(), + testnet, + [](bool testnet, bool defaulted, std::string val)->std::string { + if (testnet) + return (boost::filesystem::path(val) / "testnet").string(); + return val; + } + }; }; void do_prepare_file_names(const std::string& file_path, std::string& keys_file, std::string& wallet_file) @@ -196,6 +228,8 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl std::unique_ptr<tools::wallet2> wallet(new tools::wallet2(testnet ? TESTNET : stagenet ? STAGENET : MAINNET, restricted)); wallet->init(std::move(daemon_address), std::move(login)); + boost::filesystem::path ringdb_path = command_line::get_arg(vm, opts.shared_ringdb_dir); + wallet->set_ring_database(ringdb_path.string()); return wallet; } @@ -619,6 +653,7 @@ wallet2::wallet2(network_type nettype, bool restricted): m_refresh_from_block_height(0), m_explicit_refresh_from_block_height(true), m_confirm_missing_payment_id(true), + m_confirm_non_default_ring_size(true), m_ask_password(true), m_min_output_count(0), m_min_output_value(0), @@ -627,6 +662,9 @@ wallet2::wallet2(network_type nettype, bool restricted): m_confirm_backlog_threshold(0), m_confirm_export_overwrite(true), m_auto_low_priority(true), + m_segregate_pre_fork_outputs(true), + m_key_reuse_mitigation2(true), + m_segregation_height(0), m_is_initialized(false), m_restricted(restricted), is_old_file_format(false), @@ -639,7 +677,13 @@ wallet2::wallet2(network_type nettype, bool restricted): m_light_wallet_connected(false), m_light_wallet_balance(0), m_light_wallet_unlocked_balance(0), - m_key_on_device(false) + m_key_on_device(false), + m_ring_history_saved(false), + m_ringdb() +{ +} + +wallet2::~wallet2() { } @@ -665,6 +709,7 @@ void wallet2::init_options(boost::program_options::options_description& desc_par command_line::add_arg(desc_params, opts.testnet); command_line::add_arg(desc_params, opts.stagenet); command_line::add_arg(desc_params, opts.restricted); + command_line::add_arg(desc_params, opts.shared_ringdb_dir); } std::unique_ptr<wallet2> wallet2::make_from_json(const boost::program_options::variables_map& vm, const std::string& json_file, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) @@ -963,13 +1008,16 @@ void wallet2::set_unspent(size_t idx) //---------------------------------------------------------------------------------------------------- void wallet2::check_acc_out_precomp(const tx_out &o, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, size_t i, tx_scan_info_t &tx_scan_info) const { + hw::device &hwdev = m_account.get_device(); + boost::unique_lock<hw::device> hwdev_lock (hwdev); + hwdev.set_mode(hw::device::TRANSACTION_PARSE); if (o.target.type() != typeid(txout_to_key)) { tx_scan_info.error = true; LOG_ERROR("wrong type id in transaction out"); return; } - tx_scan_info.received = is_out_to_acc_precomp(m_subaddresses, boost::get<txout_to_key>(o.target).key, derivation, additional_derivations, i, m_account.get_device()); + tx_scan_info.received = is_out_to_acc_precomp(m_subaddresses, boost::get<txout_to_key>(o.target).key, derivation, additional_derivations, i, hwdev); if(tx_scan_info.received) { tx_scan_info.money_transfered = o.amount; // may be 0 for ringct outputs @@ -1036,9 +1084,15 @@ void wallet2::scan_output(const cryptonote::transaction &tx, const crypto::publi //---------------------------------------------------------------------------------------------------- void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen) { - // In this function, tx (probably) only contains the base information - // (that is, the prunable stuff may or may not be included) + //ensure device is let in NONE mode in any case + hw::device &hwdev = m_account.get_device(); + + boost::unique_lock<hw::device> hwdev_lock (hwdev); + hw::reset_mode rst(hwdev); + hwdev_lock.unlock(); + // In this function, tx (probably) only contains the base information + // (that is, the prunable stuff may or may not be included) if (!miner_tx && !pool) process_unconfirmed(txid, tx, height); std::vector<size_t> outs; @@ -1075,8 +1129,10 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote tools::threadpool& tpool = tools::threadpool::getInstance(); tools::threadpool::waiter waiter; const cryptonote::account_keys& keys = m_account.get_keys(); - hw::device &hwdev = m_account.get_device(); crypto::key_derivation derivation; + + hwdev_lock.lock(); + hwdev.set_mode(hw::device::TRANSACTION_PARSE); if (!hwdev.generate_key_derivation(tx_pub_key, keys.m_view_secret_key, derivation)) { MWARNING("Failed to generate key derivation from tx pubkey, skipping"); @@ -1096,6 +1152,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote additional_derivations.pop_back(); } } + hwdev_lock.unlock(); if (miner_tx && m_refresh_type == RefreshNoCoinbase) { @@ -1117,16 +1174,19 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote std::ref(tx_scan_info[i]))); } waiter.wait(); - // then scan all outputs from 0 + hwdev_lock.lock(); + hwdev.set_mode(hw::device::NONE); for (size_t i = 0; i < tx.vout.size(); ++i) { THROW_WALLET_EXCEPTION_IF(tx_scan_info[i].error, error::acc_outs_lookup_error, tx, tx_pub_key, m_account.get_keys()); if (tx_scan_info[i].received) { + hwdev.conceal_derivation(tx_scan_info[i].received->derivation, tx_pub_key, additional_tx_pub_keys, derivation, additional_derivations); scan_output(tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs); } } + hwdev_lock.unlock(); } } else if (tx.vout.size() > 1 && tools::threadpool::getInstance().get_max_concurrency() > 1) @@ -1137,14 +1197,19 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote std::ref(tx_scan_info[i]))); } waiter.wait(); + + hwdev_lock.lock(); + hwdev.set_mode(hw::device::NONE); for (size_t i = 0; i < tx.vout.size(); ++i) { THROW_WALLET_EXCEPTION_IF(tx_scan_info[i].error, error::acc_outs_lookup_error, tx, tx_pub_key, m_account.get_keys()); if (tx_scan_info[i].received) { + hwdev.conceal_derivation(tx_scan_info[i].received->derivation, tx_pub_key, additional_tx_pub_keys, derivation, additional_derivations); scan_output(tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs); } } + hwdev_lock.unlock(); } else { @@ -1154,7 +1219,11 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote THROW_WALLET_EXCEPTION_IF(tx_scan_info[i].error, error::acc_outs_lookup_error, tx, tx_pub_key, m_account.get_keys()); if (tx_scan_info[i].received) { + hwdev_lock.lock(); + hwdev.set_mode(hw::device::NONE); + hwdev.conceal_derivation(tx_scan_info[i].received->derivation, tx_pub_key, additional_tx_pub_keys, derivation, additional_derivations); scan_output(tx, tx_pub_key, i, tx_scan_info[i], num_vouts_received, tx_money_got_in_outs, outs); + hwdev_lock.unlock(); } } } @@ -1479,9 +1548,19 @@ void wallet2::process_outgoing(const crypto::hash &txid, const cryptonote::trans entry.first->second.m_subaddr_account = subaddr_account; entry.first->second.m_subaddr_indices = subaddr_indices; } + + for (const auto &in: tx.vin) + { + if (in.type() != typeid(cryptonote::txin_to_key)) + continue; + const auto &txin = boost::get<cryptonote::txin_to_key>(in); + entry.first->second.m_rings.push_back(std::make_pair(txin.k_image, txin.key_offsets)); + } entry.first->second.m_block_height = height; entry.first->second.m_timestamp = ts; entry.first->second.m_unlock_time = tx.unlock_time; + + add_rings(tx); } //---------------------------------------------------------------------------------------------------- void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const crypto::hash& bl_id, uint64_t height, const cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices &o_indices) @@ -1580,7 +1659,7 @@ void wallet2::pull_blocks(uint64_t start_height, uint64_t &blocks_start_height, THROW_WALLET_EXCEPTION_IF(*result == CORE_RPC_STATUS_BUSY, tools::error::daemon_busy, "getversion"); if (*result != CORE_RPC_STATUS_OK) { - MDEBUG("Cannot determined daemon RPC version, not asking for pruned blocks"); + MDEBUG("Cannot determine daemon RPC version, not asking for pruned blocks"); req.prune = false; // old daemon } } @@ -1852,6 +1931,7 @@ void wallet2::update_pool_state(bool refreshed) pit->second.m_state = wallet2::unconfirmed_transfer_details::failed; // the inputs aren't spent anymore, since the tx failed + remove_rings(pit->second.m_tx); for (size_t vini = 0; vini < pit->second.m_tx.vin.size(); ++vini) { if (pit->second.m_tx.vin[vini].type() == typeid(txin_to_key)) @@ -1955,6 +2035,7 @@ void wallet2::update_pool_state(bool refreshed) req.txs_hashes.push_back(epee::string_tools::pod_to_hex(p.first)); MDEBUG("asking for " << txids.size() << " transactions"); req.decode_as_json = false; + req.prune = false; m_daemon_rpc_mutex.lock(); bool r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout); m_daemon_rpc_mutex.unlock(); @@ -2266,6 +2347,73 @@ bool wallet2::refresh(uint64_t & blocks_fetched, bool& received_money, bool& ok) return ok; } //---------------------------------------------------------------------------------------------------- +bool wallet2::get_output_distribution(uint64_t &start_height, std::vector<uint64_t> &distribution) +{ + uint32_t rpc_version; + boost::optional<std::string> result = m_node_rpc_proxy.get_rpc_version(rpc_version); + // no error + if (!!result) + { + // empty string -> not connection + THROW_WALLET_EXCEPTION_IF(result->empty(), tools::error::no_connection_to_daemon, "getversion"); + THROW_WALLET_EXCEPTION_IF(*result == CORE_RPC_STATUS_BUSY, tools::error::daemon_busy, "getversion"); + if (*result != CORE_RPC_STATUS_OK) + { + MDEBUG("Cannot determine daemon RPC version, not requesting rct distribution"); + return false; + } + } + else + { + if (rpc_version >= MAKE_CORE_RPC_VERSION(1, 19)) + { + MDEBUG("Daemon is recent enough, requesting rct distribution"); + } + else + { + MDEBUG("Daemon is too old, not requesting rct distribution"); + return false; + } + } + + cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::request req = AUTO_VAL_INIT(req); + cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response res = AUTO_VAL_INIT(res); + req.amounts.push_back(0); + req.from_height = 0; + req.cumulative = true; + m_daemon_rpc_mutex.lock(); + bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_distribution", req, res, m_http_client, rpc_timeout); + m_daemon_rpc_mutex.unlock(); + if (!r) + { + MWARNING("Failed to request output distribution: no connection to daemon"); + return false; + } + if (res.status == CORE_RPC_STATUS_BUSY) + { + MWARNING("Failed to request output distribution: daemon is busy"); + return false; + } + if (res.status != CORE_RPC_STATUS_OK) + { + MWARNING("Failed to request output distribution: " << res.status); + return false; + } + if (res.distributions.size() != 1) + { + MWARNING("Failed to request output distribution: not the expected single result"); + return false; + } + if (res.distributions[0].amount != 0) + { + MWARNING("Failed to request output distribution: results are not for amount 0"); + return false; + } + start_height = res.distributions[0].start_height; + distribution = std::move(res.distributions[0].distribution); + return true; +} +//---------------------------------------------------------------------------------------------------- void wallet2::detach_blockchain(uint64_t height) { LOG_PRINT_L0("Detaching blockchain on height " << height); @@ -2273,7 +2421,7 @@ void wallet2::detach_blockchain(uint64_t height) // size 1 2 3 4 5 6 7 8 9 // block 0 1 2 3 4 5 6 7 8 // C - THROW_WALLET_EXCEPTION_IF(height <= m_checkpoints.get_max_height() && m_blockchain.size() > m_checkpoints.get_max_height(), + THROW_WALLET_EXCEPTION_IF(height < m_blockchain.offset() && m_blockchain.size() > m_blockchain.offset(), error::wallet_internal_error, "Daemon claims reorg below last checkpoint"); size_t transfers_detached = 0; @@ -2438,6 +2586,9 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable value2.SetInt(m_confirm_missing_payment_id ? 1 :0); json.AddMember("confirm_missing_payment_id", value2, json.GetAllocator()); + value2.SetInt(m_confirm_non_default_ring_size ? 1 :0); + json.AddMember("confirm_non_default_ring_size", value2, json.GetAllocator()); + value2.SetInt(m_ask_password ? 1 :0); json.AddMember("ask_password", value2, json.GetAllocator()); @@ -2468,6 +2619,21 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable value2.SetUint(m_nettype); json.AddMember("nettype", value2, json.GetAllocator()); + value2.SetInt(m_segregate_pre_fork_outputs ? 1 : 0); + json.AddMember("segregate_pre_fork_outputs", value2, json.GetAllocator()); + + value2.SetInt(m_key_reuse_mitigation2 ? 1 : 0); + json.AddMember("key_reuse_mitigation2", value2, json.GetAllocator()); + + value2.SetUint(m_segregation_height); + json.AddMember("segregation_height", value2, json.GetAllocator()); + + value2.SetUint(m_subaddress_lookahead_major); + json.AddMember("subaddress_lookahead_major", value2, json.GetAllocator()); + + value2.SetUint(m_subaddress_lookahead_minor); + json.AddMember("subaddress_lookahead_minor", value2, json.GetAllocator()); + // Serialize the JSON object rapidjson::StringBuffer buffer; rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); @@ -2530,6 +2696,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_auto_refresh = true; m_refresh_type = RefreshType::RefreshDefault; m_confirm_missing_payment_id = true; + m_confirm_non_default_ring_size = true; m_ask_password = true; m_min_output_count = 0; m_min_output_value = 0; @@ -2538,6 +2705,11 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_confirm_backlog_threshold = 0; m_confirm_export_overwrite = true; m_auto_low_priority = true; + m_segregate_pre_fork_outputs = true; + m_key_reuse_mitigation2 = true; + m_segregation_height = 0; + m_subaddress_lookahead_major = SUBADDRESS_LOOKAHEAD_MAJOR; + m_subaddress_lookahead_minor = SUBADDRESS_LOOKAHEAD_MINOR; m_key_on_device = false; } else if(json.IsObject()) @@ -2630,6 +2802,8 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ m_refresh_from_block_height = field_refresh_height; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, confirm_missing_payment_id, int, Int, false, true); m_confirm_missing_payment_id = field_confirm_missing_payment_id; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, confirm_non_default_ring_size, int, Int, false, true); + m_confirm_non_default_ring_size = field_confirm_non_default_ring_size; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, ask_password, int, Int, false, true); m_ask_password = field_ask_password; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, default_decimal_point, int, Int, false, CRYPTONOTE_DISPLAY_DECIMAL_POINT); @@ -2651,9 +2825,19 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, nettype, uint8_t, Uint, false, static_cast<uint8_t>(m_nettype)); // The network type given in the program argument is inconsistent with the network type saved in the wallet THROW_WALLET_EXCEPTION_IF(static_cast<uint8_t>(m_nettype) != field_nettype, error::wallet_internal_error, - (boost::format("%s wallet can not be opened as %s wallet") - % (field_nettype == 0 ? "Mainnet" : field_nettype == 1 ? "Testnet" : "Stagenet") - % (m_nettype == MAINNET ? "mainnet" : m_nettype == TESTNET ? "testnet" : "stagenet")).str()); + (boost::format("%s wallet cannot be opened as %s wallet") + % (field_nettype == 0 ? "Mainnet" : field_nettype == 1 ? "Testnet" : "Stagenet") + % (m_nettype == MAINNET ? "mainnet" : m_nettype == TESTNET ? "testnet" : "stagenet")).str()); + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, segregate_pre_fork_outputs, int, Int, false, true); + m_segregate_pre_fork_outputs = field_segregate_pre_fork_outputs; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, key_reuse_mitigation2, int, Int, false, true); + m_key_reuse_mitigation2 = field_key_reuse_mitigation2; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, segregation_height, int, Uint, false, 0); + m_segregation_height = field_segregation_height; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, subaddress_lookahead_major, uint32_t, Uint, false, SUBADDRESS_LOOKAHEAD_MAJOR); + m_subaddress_lookahead_major = field_subaddress_lookahead_major; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, subaddress_lookahead_minor, uint32_t, Uint, false, SUBADDRESS_LOOKAHEAD_MINOR); + m_subaddress_lookahead_minor = field_subaddress_lookahead_minor; } else { @@ -3548,20 +3732,17 @@ bool wallet2::check_connection(uint32_t *version, uint32_t timeout) if (version) { - epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_VERSION::request> req_t = AUTO_VAL_INIT(req_t); - epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_VERSION::response, std::string> resp_t = AUTO_VAL_INIT(resp_t); - req_t.jsonrpc = "2.0"; - req_t.id = epee::serialization::storage_entry(0); - req_t.method = "get_version"; - bool r = net_utils::invoke_http_json("/json_rpc", req_t, resp_t, m_http_client); + cryptonote::COMMAND_RPC_GET_VERSION::request req_t = AUTO_VAL_INIT(req_t); + cryptonote::COMMAND_RPC_GET_VERSION::response resp_t = AUTO_VAL_INIT(resp_t); + bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_version", req_t, resp_t, m_http_client); if(!r) { *version = 0; return false; } - if (resp_t.result.status != CORE_RPC_STATUS_OK) + if (resp_t.status != CORE_RPC_STATUS_OK) *version = 0; else - *version = resp_t.result.version; + *version = resp_t.version; } return true; @@ -3688,27 +3869,38 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass add_subaddress_account(tr("Primary account")); m_local_bc_height = m_blockchain.size(); + + try + { + find_and_save_rings(false); + } + catch (const std::exception &e) + { + MERROR("Failed to save rings, will try again next time"); + } } //---------------------------------------------------------------------------------------------------- void wallet2::trim_hashchain() { uint64_t height = m_checkpoints.get_max_height(); + + for (const transfer_details &td: m_transfers) + if (td.m_block_height < height) + height = td.m_block_height; + if (!m_blockchain.empty() && m_blockchain.size() == m_blockchain.offset()) { MINFO("Fixing empty hashchain"); - epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::request> req = AUTO_VAL_INIT(req); - epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::response, std::string> res = AUTO_VAL_INIT(res); + cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::request req = AUTO_VAL_INIT(req); + cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::response res = AUTO_VAL_INIT(res); m_daemon_rpc_mutex.lock(); - req.jsonrpc = "2.0"; - req.id = epee::serialization::storage_entry(0); - req.method = "getblockheaderbyheight"; - req.params.height = m_blockchain.size() - 1; - bool r = net_utils::invoke_http_json("/json_rpc", req, res, m_http_client, rpc_timeout); + req.height = m_blockchain.size() - 1; + bool r = net_utils::invoke_http_json_rpc("/json_rpc", "getblockheaderbyheight", req, res, m_http_client, rpc_timeout); m_daemon_rpc_mutex.unlock(); - if (r && res.result.status == CORE_RPC_STATUS_OK) + if (r && res.status == CORE_RPC_STATUS_OK) { crypto::hash hash; - epee::string_tools::hex_to_pod(res.result.block_header.hash, hash); + epee::string_tools::hex_to_pod(res.block_header.hash, hash); m_blockchain.refill(hash); } else @@ -4277,6 +4469,13 @@ void wallet2::add_unconfirmed_tx(const cryptonote::transaction& tx, uint64_t amo utd.m_timestamp = time(NULL); utd.m_subaddr_account = subaddr_account; utd.m_subaddr_indices = subaddr_indices; + for (const auto &in: tx.vin) + { + if (in.type() != typeid(cryptonote::txin_to_key)) + continue; + const auto &txin = boost::get<cryptonote::txin_to_key>(in); + utd.m_rings.push_back(std::make_pair(txin.k_image, txin.key_offsets)); + } } //---------------------------------------------------------------------------------------------------- @@ -4964,7 +5163,7 @@ bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector<crypto rct::multisig_out msout = ptx.multisig_sigs.front().msout; auto sources = sd.sources; const bool bulletproof = sd.use_rct && (ptx.tx.rct_signatures.type == rct::RCTTypeFullBulletproof || ptx.tx.rct_signatures.type == rct::RCTTypeSimpleBulletproof); - bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources, sd.splitted_dsts, ptx.change_dts.addr, sd.extra, tx, sd.unlock_time, ptx.tx_key, ptx.additional_tx_keys, sd.use_rct, bulletproof, &msout); + bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources, sd.splitted_dsts, ptx.change_dts.addr, sd.extra, tx, sd.unlock_time, ptx.tx_key, ptx.additional_tx_keys, sd.use_rct, bulletproof, &msout, false); THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sd.sources, sd.splitted_dsts, sd.unlock_time, m_nettype); THROW_WALLET_EXCEPTION_IF(get_transaction_prefix_hash (tx) != get_transaction_prefix_hash(ptx.tx), @@ -5168,18 +5367,15 @@ uint32_t wallet2::adjust_priority(uint32_t priority) } // get the current full reward zone - epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_INFO::request> getinfo_req = AUTO_VAL_INIT(getinfo_req); - epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_INFO::response, std::string> getinfo_res = AUTO_VAL_INIT(getinfo_res); + cryptonote::COMMAND_RPC_GET_INFO::request getinfo_req = AUTO_VAL_INIT(getinfo_req); + cryptonote::COMMAND_RPC_GET_INFO::response getinfo_res = AUTO_VAL_INIT(getinfo_res); m_daemon_rpc_mutex.lock(); - getinfo_req.jsonrpc = "2.0"; - getinfo_req.id = epee::serialization::storage_entry(0); - getinfo_req.method = "get_info"; - bool r = net_utils::invoke_http_json("/json_rpc", getinfo_req, getinfo_res, m_http_client); + bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_info", getinfo_req, getinfo_res, m_http_client); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_info"); - THROW_WALLET_EXCEPTION_IF(getinfo_res.result.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_info"); - THROW_WALLET_EXCEPTION_IF(getinfo_res.result.status != CORE_RPC_STATUS_OK, error::get_tx_pool_error); - const uint64_t full_reward_zone = getinfo_res.result.block_size_limit / 2; + THROW_WALLET_EXCEPTION_IF(getinfo_res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_info"); + THROW_WALLET_EXCEPTION_IF(getinfo_res.status != CORE_RPC_STATUS_OK, error::get_tx_pool_error); + const uint64_t full_reward_zone = getinfo_res.block_size_limit / 2; // get the last N block headers and sum the block sizes const size_t N = 10; @@ -5188,26 +5384,23 @@ uint32_t wallet2::adjust_priority(uint32_t priority) MERROR("The blockchain is too short"); return priority; } - epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::request> getbh_req = AUTO_VAL_INIT(getbh_req); - epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::response, std::string> getbh_res = AUTO_VAL_INIT(getbh_res); + cryptonote::COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::request getbh_req = AUTO_VAL_INIT(getbh_req); + cryptonote::COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::response getbh_res = AUTO_VAL_INIT(getbh_res); m_daemon_rpc_mutex.lock(); - getbh_req.jsonrpc = "2.0"; - getbh_req.id = epee::serialization::storage_entry(0); - getbh_req.method = "getblockheadersrange"; - getbh_req.params.start_height = m_blockchain.size() - N; - getbh_req.params.end_height = m_blockchain.size() - 1; - r = net_utils::invoke_http_json("/json_rpc", getbh_req, getbh_res, m_http_client, rpc_timeout); + getbh_req.start_height = m_blockchain.size() - N; + getbh_req.end_height = m_blockchain.size() - 1; + r = net_utils::invoke_http_json_rpc("/json_rpc", "getblockheadersrange", getbh_req, getbh_res, m_http_client, rpc_timeout); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "getblockheadersrange"); - THROW_WALLET_EXCEPTION_IF(getbh_res.result.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "getblockheadersrange"); - THROW_WALLET_EXCEPTION_IF(getbh_res.result.status != CORE_RPC_STATUS_OK, error::get_blocks_error, getbh_res.result.status); - if (getbh_res.result.headers.size() != N) + THROW_WALLET_EXCEPTION_IF(getbh_res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "getblockheadersrange"); + THROW_WALLET_EXCEPTION_IF(getbh_res.status != CORE_RPC_STATUS_OK, error::get_blocks_error, getbh_res.status); + if (getbh_res.headers.size() != N) { MERROR("Bad blockheaders size"); return priority; } size_t block_size_sum = 0; - for (const cryptonote::block_header_response &i : getbh_res.result.headers) + for (const cryptonote::block_header_response &i : getbh_res.headers) { block_size_sum += i.block_size; } @@ -5340,17 +5533,231 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto } } +bool wallet2::set_ring_database(const std::string &filename) +{ + m_ring_database = filename; + MINFO("ringdb path set to " << filename); + m_ringdb.reset(); + if (!m_ring_database.empty()) + { + try + { + cryptonote::block b; + generate_genesis(b); + m_ringdb.reset(new tools::ringdb(m_ring_database, epee::string_tools::pod_to_hex(get_block_hash(b)))); + } + catch (const std::exception &e) + { + MERROR("Failed to initialize ringdb: " << e.what()); + m_ring_database = ""; + return false; + } + } + return true; +} + +bool wallet2::add_rings(const crypto::chacha_key &key, const cryptonote::transaction_prefix &tx) +{ + if (!m_ringdb) + return false; + try { return m_ringdb->add_rings(key, tx); } + catch (const std::exception &e) { return false; } +} + +bool wallet2::add_rings(const cryptonote::transaction_prefix &tx) +{ + crypto::chacha_key key; + generate_chacha_key_from_secret_keys(key); + try { return add_rings(key, tx); } + catch (const std::exception &e) { return false; } +} + +bool wallet2::remove_rings(const cryptonote::transaction_prefix &tx) +{ + if (!m_ringdb) + return false; + crypto::chacha_key key; + generate_chacha_key_from_secret_keys(key); + try { return m_ringdb->remove_rings(key, tx); } + catch (const std::exception &e) { return false; } +} + +bool wallet2::get_ring(const crypto::chacha_key &key, const crypto::key_image &key_image, std::vector<uint64_t> &outs) +{ + if (!m_ringdb) + return false; + try { return m_ringdb->get_ring(key, key_image, outs); } + catch (const std::exception &e) { return false; } +} + +bool wallet2::get_rings(const crypto::hash &txid, std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> &outs) +{ + for (auto i: m_confirmed_txs) + { + if (txid == i.first) + { + for (const auto &x: i.second.m_rings) + outs.push_back({x.first, cryptonote::relative_output_offsets_to_absolute(x.second)}); + return true; + } + } + for (auto i: m_unconfirmed_txs) + { + if (txid == i.first) + { + for (const auto &x: i.second.m_rings) + outs.push_back({x.first, cryptonote::relative_output_offsets_to_absolute(x.second)}); + return true; + } + } + return false; +} + +bool wallet2::get_ring(const crypto::key_image &key_image, std::vector<uint64_t> &outs) +{ + crypto::chacha_key key; + generate_chacha_key_from_secret_keys(key); + + try { return get_ring(key, key_image, outs); } + catch (const std::exception &e) { return false; } +} + +bool wallet2::set_ring(const crypto::key_image &key_image, const std::vector<uint64_t> &outs, bool relative) +{ + if (!m_ringdb) + return false; + + crypto::chacha_key key; + generate_chacha_key_from_secret_keys(key); + + try { return m_ringdb->set_ring(key, key_image, outs, relative); } + catch (const std::exception &e) { return false; } +} + +bool wallet2::find_and_save_rings(bool force) +{ + if (!force && m_ring_history_saved) + return true; + if (!m_ringdb) + return false; + + COMMAND_RPC_GET_TRANSACTIONS::request req = AUTO_VAL_INIT(req); + COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res); + + MDEBUG("Finding and saving rings..."); + + // get payments we made + std::vector<crypto::hash> txs_hashes; + std::list<std::pair<crypto::hash,wallet2::confirmed_transfer_details>> payments; + get_payments_out(payments, 0, std::numeric_limits<uint64_t>::max(), boost::none, std::set<uint32_t>()); + for (const std::pair<crypto::hash,wallet2::confirmed_transfer_details> &entry: payments) + { + const crypto::hash &txid = entry.first; + txs_hashes.push_back(txid); + } + + MDEBUG("Found " << std::to_string(txs_hashes.size()) << " transactions"); + + crypto::chacha_key key; + generate_chacha_key_from_secret_keys(key); + + // get those transactions from the daemon + static const size_t SLICE_SIZE = 200; + for (size_t slice = 0; slice < txs_hashes.size(); slice += SLICE_SIZE) + { + req.decode_as_json = false; + req.prune = true; + req.txs_hashes.clear(); + size_t ntxes = slice + SLICE_SIZE > txs_hashes.size() ? txs_hashes.size() - slice : SLICE_SIZE; + for (size_t s = slice; s < slice + ntxes; ++s) + req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txs_hashes[s])); + bool r; + { + const boost::lock_guard<boost::mutex> lock{m_daemon_rpc_mutex}; + r = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client, rpc_timeout); + } + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::wallet_internal_error, "gettransactions"); + THROW_WALLET_EXCEPTION_IF(res.txs.size() != req.txs_hashes.size(), error::wallet_internal_error, + "daemon returned wrong response for gettransactions, wrong txs count = " + + std::to_string(res.txs.size()) + ", expected " + std::to_string(req.txs_hashes.size())); + + MDEBUG("Scanning " << res.txs.size() << " transactions"); + THROW_WALLET_EXCEPTION_IF(slice + res.txs.size() > txs_hashes.size(), error::wallet_internal_error, "Unexpected tx array size"); + auto it = req.txs_hashes.begin(); + for (size_t i = 0; i < res.txs.size(); ++i, ++it) + { + const auto &tx_info = res.txs[i]; + THROW_WALLET_EXCEPTION_IF(tx_info.tx_hash != epee::string_tools::pod_to_hex(txs_hashes[slice + i]), error::wallet_internal_error, "Wrong txid received"); + THROW_WALLET_EXCEPTION_IF(tx_info.tx_hash != *it, error::wallet_internal_error, "Wrong txid received"); + cryptonote::blobdata bd; + THROW_WALLET_EXCEPTION_IF(!epee::string_tools::parse_hexstr_to_binbuff(tx_info.as_hex, bd), error::wallet_internal_error, "failed to parse tx from hexstr"); + cryptonote::transaction tx; + crypto::hash tx_hash, tx_prefix_hash; + THROW_WALLET_EXCEPTION_IF(!cryptonote::parse_and_validate_tx_from_blob(bd, tx, tx_hash, tx_prefix_hash), error::wallet_internal_error, "failed to parse tx from blob"); + THROW_WALLET_EXCEPTION_IF(epee::string_tools::pod_to_hex(tx_hash) != tx_info.tx_hash, error::wallet_internal_error, "txid mismatch"); + THROW_WALLET_EXCEPTION_IF(!add_rings(key, tx), error::wallet_internal_error, "Failed to save ring"); + } + } + + MINFO("Found and saved rings for " << txs_hashes.size() << " transactions"); + m_ring_history_saved = true; + return true; +} + +bool wallet2::blackball_output(const crypto::public_key &output) +{ + if (!m_ringdb) + return false; + try { return m_ringdb->blackball(output); } + catch (const std::exception &e) { return false; } +} -bool wallet2::tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& tx_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const +bool wallet2::set_blackballed_outputs(const std::vector<crypto::public_key> &outputs, bool add) +{ + if (!m_ringdb) + return false; + try + { + bool ret = true; + if (!add) + ret &= m_ringdb->clear_blackballs(); + for (const auto &output: outputs) + ret &= m_ringdb->blackball(output); + return ret; + } + catch (const std::exception &e) { return false; } +} + +bool wallet2::unblackball_output(const crypto::public_key &output) +{ + if (!m_ringdb) + return false; + try { return m_ringdb->unblackball(output); } + catch (const std::exception &e) { return false; } +} + +bool wallet2::is_output_blackballed(const crypto::public_key &output) const +{ + if (!m_ringdb) + return false; + try { return m_ringdb->blackballed(output); } + catch (const std::exception &e) { return false; } +} + +bool wallet2::tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& output_public_key, const rct::key& mask, uint64_t real_index, bool unlocked) const { if (!unlocked) // don't add locked outs return false; if (global_index == real_index) // don't re-add real one return false; - auto item = std::make_tuple(global_index, tx_public_key, mask); + auto item = std::make_tuple(global_index, output_public_key, mask); CHECK_AND_ASSERT_MES(!outs.empty(), false, "internal error: outs is empty"); if (std::find(outs.back().begin(), outs.back().end(), item) != outs.back().end()) // don't add duplicates return false; + if (is_output_blackballed(output_public_key)) // don't add blackballed outputs + return false; outs.back().push_back(item); return true; } @@ -5469,27 +5876,81 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> return; } + crypto::chacha_key key; + generate_chacha_key_from_secret_keys(key); + if (fake_outputs_count > 0) { + uint64_t segregation_fork_height = get_segregation_fork_height(); + // check whether we're shortly after the fork + uint64_t height; + boost::optional<std::string> result = m_node_rpc_proxy.get_height(height); + throw_on_rpc_response_error(result, "get_info"); + bool is_shortly_after_segregation_fork = height >= segregation_fork_height && height < segregation_fork_height + SEGREGATION_FORK_VICINITY; + bool is_after_segregation_fork = height >= segregation_fork_height; + // get histogram for the amounts we need - epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request> req_t = AUTO_VAL_INIT(req_t); - epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response, std::string> resp_t = AUTO_VAL_INIT(resp_t); + cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request req_t = AUTO_VAL_INIT(req_t); + cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response resp_t = AUTO_VAL_INIT(resp_t); m_daemon_rpc_mutex.lock(); - req_t.jsonrpc = "2.0"; - req_t.id = epee::serialization::storage_entry(0); - req_t.method = "get_output_histogram"; for(size_t idx: selected_transfers) - req_t.params.amounts.push_back(m_transfers[idx].is_rct() ? 0 : m_transfers[idx].amount()); - std::sort(req_t.params.amounts.begin(), req_t.params.amounts.end()); - auto end = std::unique(req_t.params.amounts.begin(), req_t.params.amounts.end()); - req_t.params.amounts.resize(std::distance(req_t.params.amounts.begin(), end)); - req_t.params.unlocked = true; - req_t.params.recent_cutoff = time(NULL) - RECENT_OUTPUT_ZONE; - bool r = net_utils::invoke_http_json("/json_rpc", req_t, resp_t, m_http_client, rpc_timeout); + req_t.amounts.push_back(m_transfers[idx].is_rct() ? 0 : m_transfers[idx].amount()); + std::sort(req_t.amounts.begin(), req_t.amounts.end()); + auto end = std::unique(req_t.amounts.begin(), req_t.amounts.end()); + req_t.amounts.resize(std::distance(req_t.amounts.begin(), end)); + req_t.unlocked = true; + req_t.recent_cutoff = time(NULL) - RECENT_OUTPUT_ZONE; + bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, m_http_client, rpc_timeout); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "transfer_selected"); - THROW_WALLET_EXCEPTION_IF(resp_t.result.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram"); - THROW_WALLET_EXCEPTION_IF(resp_t.result.status != CORE_RPC_STATUS_OK, error::get_histogram_error, resp_t.result.status); + THROW_WALLET_EXCEPTION_IF(resp_t.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram"); + THROW_WALLET_EXCEPTION_IF(resp_t.status != CORE_RPC_STATUS_OK, error::get_histogram_error, resp_t.status); + + // if we want to segregate fake outs pre or post fork, get distribution + std::unordered_map<uint64_t, std::pair<uint64_t, uint64_t>> segregation_limit; + if (is_after_segregation_fork && (m_segregate_pre_fork_outputs || m_key_reuse_mitigation2)) + { + cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::request req_t = AUTO_VAL_INIT(req_t); + cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response resp_t = AUTO_VAL_INIT(resp_t); + for(size_t idx: selected_transfers) + req_t.amounts.push_back(m_transfers[idx].is_rct() ? 0 : m_transfers[idx].amount()); + std::sort(req_t.amounts.begin(), req_t.amounts.end()); + auto end = std::unique(req_t.amounts.begin(), req_t.amounts.end()); + req_t.amounts.resize(std::distance(req_t.amounts.begin(), end)); + req_t.from_height = std::max<uint64_t>(segregation_fork_height, RECENT_OUTPUT_BLOCKS) - RECENT_OUTPUT_BLOCKS; + req_t.to_height = segregation_fork_height + 1; + req_t.cumulative = true; + m_daemon_rpc_mutex.lock(); + bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_distribution", req_t, resp_t, m_http_client, rpc_timeout * 1000); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "transfer_selected"); + THROW_WALLET_EXCEPTION_IF(resp_t.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_distribution"); + THROW_WALLET_EXCEPTION_IF(resp_t.status != CORE_RPC_STATUS_OK, error::get_output_distribution, resp_t.status); + + // check we got all data + for(size_t idx: selected_transfers) + { + const uint64_t amount = m_transfers[idx].is_rct() ? 0 : m_transfers[idx].amount(); + bool found = false; + for (const auto &d: resp_t.distributions) + { + if (d.amount == amount) + { + THROW_WALLET_EXCEPTION_IF(d.start_height > segregation_fork_height, error::get_output_distribution, "Distribution start_height too high"); + THROW_WALLET_EXCEPTION_IF(segregation_fork_height - d.start_height >= d.distribution.size(), error::get_output_distribution, "Distribution size too small"); + THROW_WALLET_EXCEPTION_IF(segregation_fork_height - RECENT_OUTPUT_BLOCKS - d.start_height >= d.distribution.size(), error::get_output_distribution, "Distribution size too small"); + THROW_WALLET_EXCEPTION_IF(segregation_fork_height <= RECENT_OUTPUT_BLOCKS, error::wallet_internal_error, "Fork height too low"); + THROW_WALLET_EXCEPTION_IF(segregation_fork_height - RECENT_OUTPUT_BLOCKS < d.start_height, error::get_output_distribution, "Bad start height"); + uint64_t till_fork = d.distribution[segregation_fork_height - d.start_height]; + uint64_t recent = till_fork - d.distribution[segregation_fork_height - RECENT_OUTPUT_BLOCKS - d.start_height]; + segregation_limit[amount] = std::make_pair(till_fork, recent); + found = true; + break; + } + } + THROW_WALLET_EXCEPTION_IF(!found, error::get_output_distribution, "Requested amount not found in response"); + } + } // we ask for more, to have spares if some outputs are still locked size_t base_requested_outputs_count = (size_t)((fake_outputs_count + 1) * 1.5 + 1); @@ -5510,37 +5971,126 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0); size_t start = req.outputs.size(); - // if there are just enough outputs to mix with, use all of them. - // Eventually this should become impossible. + const bool output_is_pre_fork = td.m_block_height < segregation_fork_height; uint64_t num_outs = 0, num_recent_outs = 0; - for (const auto &he: resp_t.result.histogram) + uint64_t num_post_fork_outs = 0; + float pre_fork_num_out_ratio = 0.0f; + float post_fork_num_out_ratio = 0.0f; + + if (is_after_segregation_fork && m_segregate_pre_fork_outputs && output_is_pre_fork) { - if (he.amount == amount) + num_outs = segregation_limit[amount].first; + num_recent_outs = segregation_limit[amount].second; + } + else + { + // if there are just enough outputs to mix with, use all of them. + // Eventually this should become impossible. + for (const auto &he: resp_t.histogram) { - LOG_PRINT_L2("Found " << print_money(amount) << ": " << he.total_instances << " total, " - << he.unlocked_instances << " unlocked, " << he.recent_instances << " recent"); - num_outs = he.unlocked_instances; - num_recent_outs = he.recent_instances; - break; + if (he.amount == amount) + { + LOG_PRINT_L2("Found " << print_money(amount) << ": " << he.total_instances << " total, " + << he.unlocked_instances << " unlocked, " << he.recent_instances << " recent"); + num_outs = he.unlocked_instances; + num_recent_outs = he.recent_instances; + break; + } + } + if (is_after_segregation_fork && m_key_reuse_mitigation2) + { + if (output_is_pre_fork) + { + if (is_shortly_after_segregation_fork) + { + pre_fork_num_out_ratio = 33.4/100.0f * (1.0f - RECENT_OUTPUT_RATIO); + } + else + { + pre_fork_num_out_ratio = 33.4/100.0f * (1.0f - RECENT_OUTPUT_RATIO); + post_fork_num_out_ratio = 33.4/100.0f * (1.0f - RECENT_OUTPUT_RATIO); + } + } + else + { + if (is_shortly_after_segregation_fork) + { + } + else + { + post_fork_num_out_ratio = 67.8/100.0f * (1.0f - RECENT_OUTPUT_RATIO); + } + } } + num_post_fork_outs = num_outs - segregation_limit[amount].first; } + LOG_PRINT_L1("" << num_outs << " unlocked outputs of size " << print_money(amount)); THROW_WALLET_EXCEPTION_IF(num_outs == 0, error::wallet_internal_error, "histogram reports no unlocked outputs for " + boost::lexical_cast<std::string>(amount) + ", not even ours"); THROW_WALLET_EXCEPTION_IF(num_recent_outs > num_outs, error::wallet_internal_error, "histogram reports more recent outs than outs for " + boost::lexical_cast<std::string>(amount)); + // how many fake outs to draw on a pre-fork triangular distribution + size_t pre_fork_outputs_count = requested_outputs_count * pre_fork_num_out_ratio; + size_t post_fork_outputs_count = requested_outputs_count * post_fork_num_out_ratio; + // how many fake outs to draw otherwise + size_t normal_output_count = requested_outputs_count - pre_fork_outputs_count - post_fork_outputs_count; + // X% of those outs are to be taken from recent outputs - size_t recent_outputs_count = requested_outputs_count * RECENT_OUTPUT_RATIO; + size_t recent_outputs_count = normal_output_count * RECENT_OUTPUT_RATIO; if (recent_outputs_count == 0) recent_outputs_count = 1; // ensure we have at least one, if possible if (recent_outputs_count > num_recent_outs) recent_outputs_count = num_recent_outs; if (td.m_global_output_index >= num_outs - num_recent_outs && recent_outputs_count > 0) --recent_outputs_count; // if the real out is recent, pick one less recent fake out - LOG_PRINT_L1("Using " << recent_outputs_count << " recent outputs"); + LOG_PRINT_L1("Fake output makeup: " << requested_outputs_count << " requested: " << recent_outputs_count << " recent, " << + pre_fork_outputs_count << " pre-fork, " << post_fork_outputs_count << " post-fork, " << + (requested_outputs_count - recent_outputs_count - pre_fork_outputs_count - post_fork_outputs_count) << " full-chain"); + + uint64_t num_found = 0; + + // if we have a known ring, use it + bool existing_ring_found = false; + if (td.m_key_image_known && !td.m_key_image_partial) + { + std::vector<uint64_t> ring; + if (get_ring(key, td.m_key_image, ring)) + { + MINFO("This output has a known ring, reusing (size " << ring.size() << ")"); + THROW_WALLET_EXCEPTION_IF(ring.size() > fake_outputs_count + 1, error::wallet_internal_error, + "An output in this transaction was previously spent on another chain with ring size " + + std::to_string(ring.size()) + ", it cannot be spent now with ring size " + + std::to_string(fake_outputs_count + 1) + " as it is smaller: use a higher ring size"); + bool own_found = false; + existing_ring_found = true; + for (const auto &out: ring) + { + MINFO("Ring has output " << out); + if (out < num_outs) + { + MINFO("Using it"); + req.outputs.push_back({amount, out}); + ++num_found; + seen_indices.emplace(out); + if (out == td.m_global_output_index) + { + MINFO("This is the real output"); + own_found = true; + } + } + else + { + MINFO("Ignoring output " << out << ", too recent"); + } + } + THROW_WALLET_EXCEPTION_IF(!own_found, error::wallet_internal_error, + "Known ring does not include the spent output: " + std::to_string(td.m_global_output_index)); + } + } - if (num_outs <= requested_outputs_count) + if (num_outs <= requested_outputs_count && !existing_ring_found) { for (uint64_t i = 0; i < num_outs; i++) req.outputs.push_back({amount, i}); @@ -5553,10 +6103,13 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> else { // start with real one - uint64_t num_found = 1; - seen_indices.emplace(td.m_global_output_index); - req.outputs.push_back({amount, td.m_global_output_index}); - LOG_PRINT_L1("Selecting real output: " << td.m_global_output_index << " for " << print_money(amount)); + if (num_found == 0) + { + num_found = 1; + seen_indices.emplace(td.m_global_output_index); + req.outputs.push_back({amount, td.m_global_output_index}); + LOG_PRINT_L1("Selecting real output: " << td.m_global_output_index << " for " << print_money(amount)); + } // while we still need more mixins while (num_found < requested_outputs_count) @@ -5570,6 +6123,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> // list of output indices we've seen. uint64_t i; + const char *type = ""; if (num_found - 1 < recent_outputs_count) // -1 to account for the real one we seeded with { // triangular distribution over [a,b) with a=0, mode c=b=up_index_limit @@ -5579,7 +6133,29 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> // just in case rounding up to 1 occurs after calc if (i == num_outs) --i; - LOG_PRINT_L2("picking " << i << " as recent"); + type = "recent"; + } + else if (num_found -1 < recent_outputs_count + pre_fork_outputs_count) + { + // triangular distribution over [a,b) with a=0, mode c=b=up_index_limit + uint64_t r = crypto::rand<uint64_t>() % ((uint64_t)1 << 53); + double frac = std::sqrt((double)r / ((uint64_t)1 << 53)); + i = (uint64_t)(frac*segregation_limit[amount].first); + // just in case rounding up to 1 occurs after calc + if (i == num_outs) + --i; + type = " pre-fork"; + } + else if (num_found -1 < recent_outputs_count + pre_fork_outputs_count + post_fork_outputs_count) + { + // triangular distribution over [a,b) with a=0, mode c=b=up_index_limit + uint64_t r = crypto::rand<uint64_t>() % ((uint64_t)1 << 53); + double frac = std::sqrt((double)r / ((uint64_t)1 << 53)); + i = (uint64_t)(frac*num_post_fork_outs) + segregation_limit[amount].first; + // just in case rounding up to 1 occurs after calc + if (i == num_post_fork_outs+segregation_limit[amount].first) + --i; + type = "post-fork"; } else { @@ -5590,13 +6166,14 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> // just in case rounding up to 1 occurs after calc if (i == num_outs) --i; - LOG_PRINT_L2("picking " << i << " as triangular"); + type = "triangular"; } if (seen_indices.count(i)) continue; seen_indices.emplace(i); + LOG_PRINT_L2("picking " << i << " as " << type); req.outputs.push_back({amount, i}); ++num_found; } @@ -5632,6 +6209,20 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> outs.back().reserve(fake_outputs_count + 1); const rct::key mask = td.is_rct() ? rct::commit(td.amount(), td.m_mask) : rct::zeroCommit(td.amount()); + uint64_t num_outs = 0; + const uint64_t amount = td.is_rct() ? 0 : td.amount(); + const bool output_is_pre_fork = td.m_block_height < segregation_fork_height; + if (is_after_segregation_fork && m_segregate_pre_fork_outputs && output_is_pre_fork) + num_outs = segregation_limit[amount].first; + else for (const auto &he: resp_t.histogram) + { + if (he.amount == amount) + { + num_outs = he.unlocked_instances; + break; + } + } + // make sure the real outputs we asked for are really included, along // with the correct key and mask: this guards against an active attack // where the node sends dummy data for all outputs, and we then send @@ -5652,6 +6243,38 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> // pick real out first (it will be sorted when done) outs.back().push_back(std::make_tuple(td.m_global_output_index, boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key, mask)); + // then pick outs from an existing ring, if any + bool existing_ring_found = false; + if (td.m_key_image_known && !td.m_key_image_partial) + { + std::vector<uint64_t> ring; + if (get_ring(key, td.m_key_image, ring)) + { + for (uint64_t out: ring) + { + if (out < num_outs) + { + if (out != td.m_global_output_index) + { + bool found = false; + for (size_t o = 0; o < requested_outputs_count; ++o) + { + size_t i = base + o; + if (req.outputs[i].index == out) + { + LOG_PRINT_L2("Index " << i << "/" << requested_outputs_count << ": idx " << req.outputs[i].index << " (real " << td.m_global_output_index << "), unlocked " << daemon_resp.outs[i].unlocked << ", key " << daemon_resp.outs[i].key << " (from existing ring)"); + tx_add_fake_output(outs, req.outputs[i].index, daemon_resp.outs[i].key, daemon_resp.outs[i].mask, td.m_global_output_index, daemon_resp.outs[i].unlocked); + found = true; + break; + } + } + THROW_WALLET_EXCEPTION_IF(!found, error::wallet_internal_error, "Falied to find existing ring output in daemon out data"); + } + } + } + } + } + // then pick others in random order till we reach the required number // since we use an equiprobable pick here, we don't upset the triangular distribution std::vector<size_t> order; @@ -6055,7 +6678,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry LOG_PRINT_L2("Creating supplementary multisig transaction"); cryptonote::transaction ms_tx; auto sources_copy_copy = sources_copy; - bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources_copy_copy, splitted_dsts, change_dts.addr, extra, ms_tx, unlock_time,tx_key, additional_tx_keys, true, bulletproof, &msout); + bool r = cryptonote::construct_tx_with_tx_key(m_account.get_keys(), m_subaddresses, sources_copy_copy, splitted_dsts, change_dts.addr, extra, ms_tx, unlock_time,tx_key, additional_tx_keys, true, bulletproof, &msout, false); LOG_PRINT_L2("constructed tx, r="<<r); THROW_WALLET_EXCEPTION_IF(!r, error::tx_not_constructed, sources, splitted_dsts, unlock_time, m_nettype); THROW_WALLET_EXCEPTION_IF(upper_transaction_size_limit <= get_object_blobsize(tx), error::tx_too_big, tx, upper_transaction_size_limit); @@ -6706,6 +7329,11 @@ bool wallet2::light_wallet_key_image_is_ours(const crypto::key_image& key_image, // usable balance. std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, bool trusted_daemon) { + //ensure device is let in NONE mode in any case + hw::device &hwdev = m_account.get_device(); + boost::unique_lock<hw::device> hwdev_lock (hwdev); + hw::reset_mode rst(hwdev); + if(m_light_wallet) { // Populate m_transfers light_wallet_get_unspent_outs(); @@ -6923,8 +7551,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp unsigned int original_output_index = 0; std::vector<size_t>* unused_transfers_indices = &unused_transfers_indices_per_subaddr[0].second; std::vector<size_t>* unused_dust_indices = &unused_dust_indices_per_subaddr[0].second; - hw::device &hwdev = m_account.get_device(); - hwdev.set_signature_mode(hw::device::SIGNATURE_FAKE); + + hwdev.set_mode(hw::device::TRANSACTION_CREATE_FAKE); while ((!dsts.empty() && dsts[0].amount > 0) || adding_fee || !preferred_inputs.empty() || should_pick_a_second_output(use_rct, txes.back().selected_transfers.size(), *unused_transfers_indices, *unused_dust_indices)) { TX &tx = txes.back(); @@ -7153,7 +7781,7 @@ skip_tx: LOG_PRINT_L1("Done creating " << txes.size() << " transactions, " << print_money(accumulated_fee) << " total fee, " << print_money(accumulated_change) << " total change"); - hwdev.set_signature_mode(hw::device::SIGNATURE_REAL); + hwdev.set_mode(hw::device::TRANSACTION_CREATE_REAL); for (std::vector<TX>::iterator i = txes.begin(); i != txes.end(); ++i) { TX &tx = *i; @@ -7284,6 +7912,11 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_single(const crypt std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, bool trusted_daemon) { + //ensure device is let in NONE mode in any case + hw::device &hwdev = m_account.get_device(); + boost::unique_lock<hw::device> hwdev_lock (hwdev); + hw::reset_mode rst(hwdev); + uint64_t accumulated_fee, accumulated_outputs, accumulated_change; struct TX { std::vector<size_t> selected_transfers; @@ -7316,8 +7949,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton needed_fee = 0; // while we have something to send - hw::device &hwdev = m_account.get_device(); - hwdev.set_signature_mode(hw::device::SIGNATURE_FAKE); + hwdev.set_mode(hw::device::TRANSACTION_CREATE_FAKE); while (!unused_dust_indices.empty() || !unused_transfers_indices.empty()) { TX &tx = txes.back(); @@ -7403,7 +8035,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton LOG_PRINT_L1("Done creating " << txes.size() << " transactions, " << print_money(accumulated_fee) << " total fee, " << print_money(accumulated_change) << " total change"); - hwdev.set_signature_mode(hw::device::SIGNATURE_REAL); + hwdev.set_mode(hw::device::TRANSACTION_CREATE_REAL); for (std::vector<TX>::iterator i = txes.begin(); i != txes.end(); ++i) { TX &tx = *i; @@ -7510,25 +8142,23 @@ std::vector<uint64_t> wallet2::get_unspent_amounts_vector() const //---------------------------------------------------------------------------------------------------- std::vector<size_t> wallet2::select_available_outputs_from_histogram(uint64_t count, bool atleast, bool unlocked, bool allow_rct, bool trusted_daemon) { - epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request> req_t = AUTO_VAL_INIT(req_t); - epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response, std::string> resp_t = AUTO_VAL_INIT(resp_t); + cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request req_t = AUTO_VAL_INIT(req_t); + cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response resp_t = AUTO_VAL_INIT(resp_t); m_daemon_rpc_mutex.lock(); - req_t.jsonrpc = "2.0"; - req_t.id = epee::serialization::storage_entry(0); - req_t.method = "get_output_histogram"; if (trusted_daemon) - req_t.params.amounts = get_unspent_amounts_vector(); - req_t.params.min_count = count; - req_t.params.max_count = 0; - req_t.params.unlocked = unlocked; - bool r = net_utils::invoke_http_json("/json_rpc", req_t, resp_t, m_http_client, rpc_timeout); + req_t.amounts = get_unspent_amounts_vector(); + req_t.min_count = count; + req_t.max_count = 0; + req_t.unlocked = unlocked; + req_t.recent_cutoff = 0; + bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, m_http_client, rpc_timeout); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "select_available_outputs_from_histogram"); - THROW_WALLET_EXCEPTION_IF(resp_t.result.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram"); - THROW_WALLET_EXCEPTION_IF(resp_t.result.status != CORE_RPC_STATUS_OK, error::get_histogram_error, resp_t.result.status); + THROW_WALLET_EXCEPTION_IF(resp_t.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram"); + THROW_WALLET_EXCEPTION_IF(resp_t.status != CORE_RPC_STATUS_OK, error::get_histogram_error, resp_t.status); std::set<uint64_t> mixable; - for (const auto &i: resp_t.result.histogram) + for (const auto &i: resp_t.histogram) { mixable.insert(i.amount); } @@ -7551,24 +8181,23 @@ std::vector<size_t> wallet2::select_available_outputs_from_histogram(uint64_t co //---------------------------------------------------------------------------------------------------- uint64_t wallet2::get_num_rct_outputs() { - epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request> req_t = AUTO_VAL_INIT(req_t); - epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response, std::string> resp_t = AUTO_VAL_INIT(resp_t); + cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request req_t = AUTO_VAL_INIT(req_t); + cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response resp_t = AUTO_VAL_INIT(resp_t); m_daemon_rpc_mutex.lock(); - req_t.jsonrpc = "2.0"; - req_t.id = epee::serialization::storage_entry(0); - req_t.method = "get_output_histogram"; - req_t.params.amounts.push_back(0); - req_t.params.min_count = 0; - req_t.params.max_count = 0; - bool r = net_utils::invoke_http_json("/json_rpc", req_t, resp_t, m_http_client, rpc_timeout); + req_t.amounts.push_back(0); + req_t.min_count = 0; + req_t.max_count = 0; + req_t.unlocked = true; + req_t.recent_cutoff = 0; + bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, m_http_client, rpc_timeout); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_num_rct_outputs"); - THROW_WALLET_EXCEPTION_IF(resp_t.result.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram"); - THROW_WALLET_EXCEPTION_IF(resp_t.result.status != CORE_RPC_STATUS_OK, error::get_histogram_error, resp_t.result.status); - THROW_WALLET_EXCEPTION_IF(resp_t.result.histogram.size() != 1, error::get_histogram_error, "Expected exactly one response"); - THROW_WALLET_EXCEPTION_IF(resp_t.result.histogram[0].amount != 0, error::get_histogram_error, "Expected 0 amount"); + THROW_WALLET_EXCEPTION_IF(resp_t.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram"); + THROW_WALLET_EXCEPTION_IF(resp_t.status != CORE_RPC_STATUS_OK, error::get_histogram_error, resp_t.status); + THROW_WALLET_EXCEPTION_IF(resp_t.histogram.size() != 1, error::get_histogram_error, "Expected exactly one response"); + THROW_WALLET_EXCEPTION_IF(resp_t.histogram[0].amount != 0, error::get_histogram_error, "Expected 0 amount"); - return resp_t.result.histogram[0].total_instances; + return resp_t.histogram[0].total_instances; } //---------------------------------------------------------------------------------------------------- const wallet2::transfer_details &wallet2::get_transfer_details(size_t idx) const @@ -7580,14 +8209,14 @@ const wallet2::transfer_details &wallet2::get_transfer_details(size_t idx) const std::vector<size_t> wallet2::select_available_unmixable_outputs(bool trusted_daemon) { // request all outputs with less than 3 instances - const size_t min_mixin = use_fork_rules(6, 10) ? 4 : 2; // v6 increases min mixin from 2 to 4 + const size_t min_mixin = use_fork_rules(7, 10) ? 6 : use_fork_rules(6, 10) ? 4 : 2; // v6 increases min mixin from 2 to 4, v7 to 6 return select_available_outputs_from_histogram(min_mixin + 1, false, true, false, trusted_daemon); } //---------------------------------------------------------------------------------------------------- std::vector<size_t> wallet2::select_available_mixable_outputs(bool trusted_daemon) { // request all outputs with at least 3 instances, so we can use mixin 2 with - const size_t min_mixin = use_fork_rules(6, 10) ? 4 : 2; // v6 increases min mixin from 2 to 4 + const size_t min_mixin = use_fork_rules(7, 10) ? 6 : use_fork_rules(6, 10) ? 4 : 2; // v6 increases min mixin from 2 to 4, v7 to 6 return select_available_outputs_from_histogram(min_mixin + 1, true, true, true, trusted_daemon); } //---------------------------------------------------------------------------------------------------- @@ -7643,6 +8272,7 @@ std::string wallet2::get_spend_proof(const crypto::hash &txid, const std::string COMMAND_RPC_GET_TRANSACTIONS::request req = AUTO_VAL_INIT(req); req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); req.decode_as_json = false; + req.prune = false; COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res); bool r; { @@ -7762,6 +8392,7 @@ bool wallet2::check_spend_proof(const crypto::hash &txid, const std::string &mes COMMAND_RPC_GET_TRANSACTIONS::request req = AUTO_VAL_INIT(req); req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); req.decode_as_json = false; + req.prune = false; COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res); bool r; { @@ -7884,6 +8515,8 @@ void wallet2::check_tx_key_helper(const crypto::hash &txid, const crypto::key_de COMMAND_RPC_GET_TRANSACTIONS::request req; COMMAND_RPC_GET_TRANSACTIONS::response res; req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + req.decode_as_json = false; + req.prune = false; m_daemon_rpc_mutex.lock(); bool ok = epee::net_utils::invoke_http_json("/gettransactions", req, res, m_http_client); m_daemon_rpc_mutex.unlock(); @@ -8020,6 +8653,8 @@ std::string wallet2::get_tx_proof(const crypto::hash &txid, const cryptonote::ac COMMAND_RPC_GET_TRANSACTIONS::request req; COMMAND_RPC_GET_TRANSACTIONS::response res; req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + req.decode_as_json = false; + req.prune = false; m_daemon_rpc_mutex.lock(); bool ok = net_utils::invoke_http_json("/gettransactions", req, res, m_http_client); m_daemon_rpc_mutex.unlock(); @@ -8130,6 +8765,8 @@ bool wallet2::check_tx_proof(const crypto::hash &txid, const cryptonote::account COMMAND_RPC_GET_TRANSACTIONS::request req; COMMAND_RPC_GET_TRANSACTIONS::response res; req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + req.decode_as_json = false; + req.prune = false; m_daemon_rpc_mutex.lock(); bool ok = net_utils::invoke_http_json("/gettransactions", req, res, m_http_client); m_daemon_rpc_mutex.unlock(); @@ -8363,6 +9000,8 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr COMMAND_RPC_GET_TRANSACTIONS::response gettx_res; for (size_t i = 0; i < proofs.size(); ++i) gettx_req.txs_hashes.push_back(epee::string_tools::pod_to_hex(proofs[i].txid)); + gettx_req.decode_as_json = false; + gettx_req.prune = false; m_daemon_rpc_mutex.lock(); bool ok = net_utils::invoke_http_json("/gettransactions", gettx_req, gettx_res, m_http_client); m_daemon_rpc_mutex.unlock(); @@ -8484,23 +9123,20 @@ uint64_t wallet2::get_daemon_blockchain_height(string &err) const uint64_t wallet2::get_daemon_blockchain_target_height(string &err) { - epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_INFO::request> req_t = AUTO_VAL_INIT(req_t); - epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_INFO::response, std::string> resp_t = AUTO_VAL_INIT(resp_t); + cryptonote::COMMAND_RPC_GET_INFO::request req_t = AUTO_VAL_INIT(req_t); + cryptonote::COMMAND_RPC_GET_INFO::response resp_t = AUTO_VAL_INIT(resp_t); m_daemon_rpc_mutex.lock(); - req_t.jsonrpc = "2.0"; - req_t.id = epee::serialization::storage_entry(0); - req_t.method = "get_info"; - bool ok = net_utils::invoke_http_json("/json_rpc", req_t, resp_t, m_http_client); + bool ok = net_utils::invoke_http_json_rpc("/json_rpc", "get_info", req_t, resp_t, m_http_client); m_daemon_rpc_mutex.unlock(); if (ok) { - if (resp_t.result.status == CORE_RPC_STATUS_BUSY) + if (resp_t.status == CORE_RPC_STATUS_BUSY) { err = "daemon is busy. Please try again later."; } - else if (resp_t.result.status != CORE_RPC_STATUS_OK) + else if (resp_t.status != CORE_RPC_STATUS_OK) { - err = resp_t.result.status; + err = resp_t.status; } else // success, cleaning up error message { @@ -8511,7 +9147,7 @@ uint64_t wallet2::get_daemon_blockchain_target_height(string &err) { err = "possibly lost connection to daemon"; } - return resp_t.result.target_height; + return resp_t.target_height; } uint64_t wallet2::get_approximate_blockchain_height() const @@ -8940,6 +9576,7 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag COMMAND_RPC_GET_TRANSACTIONS::request gettxs_req; COMMAND_RPC_GET_TRANSACTIONS::response gettxs_res; gettxs_req.decode_as_json = false; + gettxs_req.prune = false; for (const crypto::hash& spent_txid : spent_txids) gettxs_req.txs_hashes.push_back(epee::string_tools::pod_to_hex(spent_txid)); m_daemon_rpc_mutex.lock(); @@ -9747,30 +10384,24 @@ std::vector<std::pair<uint64_t, uint64_t>> wallet2::estimate_backlog(const std:: } // get txpool backlog - epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::request> req = AUTO_VAL_INIT(req); - epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::response, std::string> res = AUTO_VAL_INIT(res); + cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::request req = AUTO_VAL_INIT(req); + cryptonote::COMMAND_RPC_GET_TRANSACTION_POOL_BACKLOG::response res = AUTO_VAL_INIT(res); m_daemon_rpc_mutex.lock(); - req.jsonrpc = "2.0"; - req.id = epee::serialization::storage_entry(0); - req.method = "get_txpool_backlog"; - bool r = net_utils::invoke_http_json("/json_rpc", req, res, m_http_client, rpc_timeout); + bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_txpool_backlog", req, res, m_http_client, rpc_timeout); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "Failed to connect to daemon"); - THROW_WALLET_EXCEPTION_IF(res.result.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_txpool_backlog"); - THROW_WALLET_EXCEPTION_IF(res.result.status != CORE_RPC_STATUS_OK, error::get_tx_pool_error); + THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_txpool_backlog"); + THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, error::get_tx_pool_error); - epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_INFO::request> req_t = AUTO_VAL_INIT(req_t); - epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_INFO::response, std::string> resp_t = AUTO_VAL_INIT(resp_t); + cryptonote::COMMAND_RPC_GET_INFO::request req_t = AUTO_VAL_INIT(req_t); + cryptonote::COMMAND_RPC_GET_INFO::response resp_t = AUTO_VAL_INIT(resp_t); m_daemon_rpc_mutex.lock(); - req_t.jsonrpc = "2.0"; - req_t.id = epee::serialization::storage_entry(0); - req_t.method = "get_info"; - r = net_utils::invoke_http_json("/json_rpc", req_t, resp_t, m_http_client); + r = net_utils::invoke_http_json_rpc("/json_rpc", "get_info", req_t, resp_t, m_http_client); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_info"); - THROW_WALLET_EXCEPTION_IF(resp_t.result.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_info"); - THROW_WALLET_EXCEPTION_IF(resp_t.result.status != CORE_RPC_STATUS_OK, error::get_tx_pool_error); - uint64_t full_reward_zone = resp_t.result.block_size_limit / 2; + THROW_WALLET_EXCEPTION_IF(resp_t.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_info"); + THROW_WALLET_EXCEPTION_IF(resp_t.status != CORE_RPC_STATUS_OK, error::get_tx_pool_error); + uint64_t full_reward_zone = resp_t.block_size_limit / 2; std::vector<std::pair<uint64_t, uint64_t>> blocks; for (const auto &fee_level: fee_levels) @@ -9778,7 +10409,7 @@ std::vector<std::pair<uint64_t, uint64_t>> wallet2::estimate_backlog(const std:: const double our_fee_byte_min = fee_level.first; const double our_fee_byte_max = fee_level.second; uint64_t priority_size_min = 0, priority_size_max = 0; - for (const auto &i: res.result.backlog) + for (const auto &i: res.backlog) { if (i.blob_size == 0) { @@ -9819,6 +10450,58 @@ std::vector<std::pair<uint64_t, uint64_t>> wallet2::estimate_backlog(uint64_t mi return estimate_backlog(fee_levels); } //---------------------------------------------------------------------------------------------------- +uint64_t wallet2::get_segregation_fork_height() const +{ + if (m_nettype == TESTNET) + return TESTNET_SEGREGATION_FORK_HEIGHT; + if (m_nettype == STAGENET) + return STAGENET_SEGREGATION_FORK_HEIGHT; + THROW_WALLET_EXCEPTION_IF(m_nettype != MAINNET, tools::error::wallet_internal_error, "Invalid network type"); + + if (m_segregation_height > 0) + return m_segregation_height; + + static const bool use_dns = true; + if (use_dns) + { + // All four MoneroPulse domains have DNSSEC on and valid + static const std::vector<std::string> dns_urls = { + "segheights.moneropulse.org", + "segheights.moneropulse.net", + "segheights.moneropulse.co", + "segheights.moneropulse.se" + }; + + const uint64_t current_height = get_blockchain_current_height(); + uint64_t best_diff = std::numeric_limits<uint64_t>::max(), best_height = 0; + std::vector<std::string> records; + if (tools::dns_utils::load_txt_records_from_dns(records, dns_urls)) + { + for (const auto& record : records) + { + std::vector<std::string> fields; + boost::split(fields, record, boost::is_any_of(":")); + if (fields.size() != 2) + continue; + uint64_t height; + if (!string_tools::get_xtype_from_string(height, fields[1])) + continue; + + MINFO("Found segregation height via DNS: " << fields[0] << " fork height at " << height); + uint64_t diff = height > current_height ? height - current_height : current_height - height; + if (diff < best_diff) + { + best_diff = diff; + best_height = height; + } + } + if (best_height) + return best_height; + } + } + return SEGREGATION_FORK_HEIGHT; +} +//---------------------------------------------------------------------------------------------------- void wallet2::generate_genesis(cryptonote::block& b) const { if (m_nettype == TESTNET) { diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 64ecb3329..61e6927bc 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -66,6 +66,8 @@ class Serialization_portability_wallet_Test; namespace tools { + class ringdb; + class i_wallet2_callback { public: @@ -166,6 +168,7 @@ namespace tools static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev); wallet2(cryptonote::network_type nettype = cryptonote::MAINNET, bool restricted = false); + ~wallet2(); struct multisig_info { @@ -286,6 +289,7 @@ namespace tools uint64_t m_timestamp; uint32_t m_subaddr_account; // subaddress account of your wallet to be used in this transfer std::set<uint32_t> m_subaddr_indices; // set of address indices used as inputs in this transfer + std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> m_rings; // relative }; struct confirmed_transfer_details @@ -300,10 +304,11 @@ namespace tools uint64_t m_unlock_time; uint32_t m_subaddr_account; // subaddress account of your wallet to be used in this transfer std::set<uint32_t> m_subaddr_indices; // set of address indices used as inputs in this transfer + std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> m_rings; // relative confirmed_transfer_details(): m_amount_in(0), m_amount_out(0), m_change((uint64_t)-1), m_block_height(0), m_payment_id(crypto::null_hash), m_timestamp(0), m_unlock_time(0), m_subaddr_account((uint32_t)-1) {} confirmed_transfer_details(const unconfirmed_transfer_details &utd, uint64_t height): - m_amount_in(utd.m_amount_in), m_amount_out(utd.m_amount_out), m_change(utd.m_change), m_block_height(height), m_dests(utd.m_dests), m_payment_id(utd.m_payment_id), m_timestamp(utd.m_timestamp), m_unlock_time(utd.m_tx.unlock_time), m_subaddr_account(utd.m_subaddr_account), m_subaddr_indices(utd.m_subaddr_indices) {} + m_amount_in(utd.m_amount_in), m_amount_out(utd.m_amount_out), m_change(utd.m_change), m_block_height(height), m_dests(utd.m_dests), m_payment_id(utd.m_payment_id), m_timestamp(utd.m_timestamp), m_unlock_time(utd.m_tx.unlock_time), m_subaddr_account(utd.m_subaddr_account), m_subaddr_indices(utd.m_subaddr_indices), m_rings(utd.m_rings) {} }; struct tx_construction_data @@ -632,6 +637,7 @@ namespace tools std::string get_subaddress_label(const cryptonote::subaddress_index& index) const; void set_subaddress_label(const cryptonote::subaddress_index &index, const std::string &label); void set_subaddress_lookahead(size_t major, size_t minor); + std::pair<size_t, size_t> get_subaddress_lookahead() const { return {m_subaddress_lookahead_major, m_subaddress_lookahead_minor}; } /*! * \brief Tells if the wallet file is deprecated. */ @@ -812,6 +818,9 @@ namespace tools if(ver < 23) return; a & m_account_tags; + if(ver < 24) + return; + a & m_ring_history_saved; } /*! @@ -861,6 +870,14 @@ namespace tools void confirm_export_overwrite(bool always) { m_confirm_export_overwrite = always; } bool auto_low_priority() const { return m_auto_low_priority; } void auto_low_priority(bool value) { m_auto_low_priority = value; } + bool segregate_pre_fork_outputs() const { return m_segregate_pre_fork_outputs; } + void segregate_pre_fork_outputs(bool value) { m_segregate_pre_fork_outputs = value; } + bool key_reuse_mitigation2() const { return m_key_reuse_mitigation2; } + void key_reuse_mitigation2(bool value) { m_key_reuse_mitigation2 = value; } + uint64_t segregation_height() const { return m_segregation_height; } + void segregation_height(uint64_t height) { m_segregation_height = height; } + bool confirm_non_default_ring_size() const { return m_confirm_non_default_ring_size; } + void confirm_non_default_ring_size(bool always) { m_confirm_non_default_ring_size = always; } bool get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys) const; void check_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, const cryptonote::account_public_address &address, uint64_t &received, bool &in_pool, uint64_t &confirmations); @@ -1029,6 +1046,37 @@ namespace tools crypto::public_key get_multisig_signing_public_key(size_t idx) const; crypto::public_key get_multisig_signing_public_key(const crypto::secret_key &skey) const; + template<class t_request, class t_response> + inline bool invoke_http_json(const boost::string_ref uri, const t_request& req, t_response& res, std::chrono::milliseconds timeout = std::chrono::seconds(15), const boost::string_ref http_method = "GET") + { + boost::lock_guard<boost::mutex> lock(m_daemon_rpc_mutex); + return epee::net_utils::invoke_http_json(uri, req, res, m_http_client, timeout, http_method); + } + template<class t_request, class t_response> + inline bool invoke_http_bin(const boost::string_ref uri, const t_request& req, t_response& res, std::chrono::milliseconds timeout = std::chrono::seconds(15), const boost::string_ref http_method = "GET") + { + boost::lock_guard<boost::mutex> lock(m_daemon_rpc_mutex); + return epee::net_utils::invoke_http_bin(uri, req, res, m_http_client, timeout, http_method); + } + template<class t_request, class t_response> + inline bool invoke_http_json_rpc(const boost::string_ref uri, const std::string& method_name, const t_request& req, t_response& res, std::chrono::milliseconds timeout = std::chrono::seconds(15), const boost::string_ref http_method = "GET", const std::string& req_id = "0") + { + boost::lock_guard<boost::mutex> lock(m_daemon_rpc_mutex); + return epee::net_utils::invoke_http_json_rpc(uri, method_name, req, res, m_http_client, timeout, http_method, req_id); + } + + bool set_ring_database(const std::string &filename); + const std::string get_ring_database() const { return m_ring_database; } + bool get_ring(const crypto::key_image &key_image, std::vector<uint64_t> &outs); + bool get_rings(const crypto::hash &txid, std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> &outs); + bool set_ring(const crypto::key_image &key_image, const std::vector<uint64_t> &outs, bool relative); + bool find_and_save_rings(bool force = true); + + bool blackball_output(const crypto::public_key &output); + bool set_blackballed_outputs(const std::vector<crypto::public_key> &outputs, bool add = false); + bool unblackball_output(const crypto::public_key &output); + bool is_output_blackballed(const crypto::public_key &output) const; + private: /*! * \brief Stores wallet information to wallet file. @@ -1085,6 +1133,14 @@ namespace tools rct::multisig_kLRki get_multisig_kLRki(size_t n, const rct::key &k) const; rct::key get_multisig_k(size_t idx, const std::unordered_set<rct::key> &used_L) const; void update_multisig_rescan_info(const std::vector<std::vector<rct::key>> &multisig_k, const std::vector<std::vector<tools::wallet2::multisig_info>> &info, size_t n); + bool add_rings(const crypto::chacha_key &key, const cryptonote::transaction_prefix &tx); + bool add_rings(const cryptonote::transaction_prefix &tx); + bool remove_rings(const cryptonote::transaction_prefix &tx); + bool get_ring(const crypto::chacha_key &key, const crypto::key_image &key_image, std::vector<uint64_t> &outs); + + bool get_output_distribution(uint64_t &start_height, std::vector<uint64_t> &distribution); + + uint64_t get_segregation_fork_height() const; cryptonote::account_base m_account; boost::optional<epee::net_utils::http::login> m_daemon_login; @@ -1142,6 +1198,7 @@ namespace tools // m_refresh_from_block_height was defaulted to zero.*/ bool m_explicit_refresh_from_block_height; bool m_confirm_missing_payment_id; + bool m_confirm_non_default_ring_size; bool m_ask_password; uint32_t m_min_output_count; uint64_t m_min_output_value; @@ -1150,6 +1207,9 @@ namespace tools uint32_t m_confirm_backlog_threshold; bool m_confirm_export_overwrite; bool m_auto_low_priority; + bool m_segregate_pre_fork_outputs; + bool m_key_reuse_mitigation2; + uint64_t m_segregation_height; bool m_is_initialized; NodeRPCProxy m_node_rpc_proxy; std::unordered_set<crypto::hash> m_scanned_pool_txs[2]; @@ -1168,17 +1228,21 @@ namespace tools std::unordered_map<crypto::hash, address_tx> m_light_wallet_address_txs; // store calculated key image for faster lookup std::unordered_map<crypto::public_key, std::map<uint64_t, crypto::key_image> > m_key_image_cache; + + std::string m_ring_database; + bool m_ring_history_saved; + std::unique_ptr<ringdb> m_ringdb; }; } -BOOST_CLASS_VERSION(tools::wallet2, 23) +BOOST_CLASS_VERSION(tools::wallet2, 24) BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 9) BOOST_CLASS_VERSION(tools::wallet2::multisig_info, 1) BOOST_CLASS_VERSION(tools::wallet2::multisig_info::LR, 0) BOOST_CLASS_VERSION(tools::wallet2::multisig_tx_set, 1) BOOST_CLASS_VERSION(tools::wallet2::payment_details, 3) BOOST_CLASS_VERSION(tools::wallet2::pool_payment_details, 1) -BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 7) -BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 5) +BOOST_CLASS_VERSION(tools::wallet2::unconfirmed_transfer_details, 8) +BOOST_CLASS_VERSION(tools::wallet2::confirmed_transfer_details, 6) BOOST_CLASS_VERSION(tools::wallet2::address_book_row, 17) BOOST_CLASS_VERSION(tools::wallet2::reserve_proof_entry, 0) BOOST_CLASS_VERSION(tools::wallet2::unsigned_tx_set, 0) @@ -1378,6 +1442,9 @@ namespace boost } a & x.m_subaddr_account; a & x.m_subaddr_indices; + if (ver < 8) + return; + a & x.m_rings; } template <class Archive> @@ -1422,6 +1489,9 @@ namespace boost } a & x.m_subaddr_account; a & x.m_subaddr_indices; + if (ver < 6) + return; + a & x.m_rings; } template <class Archive> diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index 32a0231b1..214d51cde 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -36,6 +36,7 @@ #include <vector> #include "cryptonote_basic/cryptonote_format_utils.h" +#include "cryptonote_core/cryptonote_tx_utils.h" #include "rpc/core_rpc_server_commands_defs.h" #include "include_base_utils.h" @@ -85,6 +86,7 @@ namespace tools // no_connection_to_daemon // is_key_image_spent_error // get_histogram_error + // get_output_distribution // wallet_files_doesnt_correspond // // * - class with protected ctor @@ -389,7 +391,7 @@ namespace tools struct get_tx_pool_error : public refresh_error { explicit get_tx_pool_error(std::string&& loc) - : refresh_error(std::move(loc), "error getting tranaction pool") + : refresh_error(std::move(loc), "error getting transaction pool") { } @@ -757,6 +759,14 @@ namespace tools } }; //---------------------------------------------------------------------------------------------------- + struct get_output_distribution : public wallet_rpc_error + { + explicit get_output_distribution(std::string&& loc, const std::string& request) + : wallet_rpc_error(std::move(loc), "failed to get output distribution", request) + { + } + }; + //---------------------------------------------------------------------------------------------------- struct wallet_files_doesnt_correspond : public wallet_logic_error { explicit wallet_files_doesnt_correspond(std::string&& loc, const std::string& keys_file, const std::string& wallet_file) diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 220dbbc58..a9d211532 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -229,8 +229,6 @@ namespace tools assert(bool(http_login)); } // end auth enabled - m_http_client.set_server(walvars->get_daemon_address(), walvars->get_daemon_login()); - m_net_server.set_threads_prefix("RPC"); auto rng = [](size_t len, uint8_t *ptr) { return crypto::rand(len, ptr); }; return epee::http_server_impl_base<wallet_rpc_server, connection_context>::init( @@ -2257,7 +2255,7 @@ namespace tools daemon_req.ignore_battery = req.ignore_battery; cryptonote::COMMAND_RPC_START_MINING::response daemon_res; - bool r = net_utils::invoke_http_json("/start_mining", daemon_req, daemon_res, m_http_client); + bool r = m_wallet->invoke_http_json("/start_mining", daemon_req, daemon_res); if (!r || daemon_res.status != CORE_RPC_STATUS_OK) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; @@ -2271,7 +2269,7 @@ namespace tools { cryptonote::COMMAND_RPC_STOP_MINING::request daemon_req; cryptonote::COMMAND_RPC_STOP_MINING::response daemon_res; - bool r = net_utils::invoke_http_json("/stop_mining", daemon_req, daemon_res, m_http_client); + bool r = m_wallet->invoke_http_json("/stop_mining", daemon_req, daemon_res); if (!r || daemon_res.status != CORE_RPC_STATUS_OK) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; @@ -2351,7 +2349,7 @@ namespace tools cryptonote::COMMAND_RPC_GET_HEIGHT::request hreq; cryptonote::COMMAND_RPC_GET_HEIGHT::response hres; hres.height = 0; - bool r = net_utils::invoke_http_json("/getheight", hreq, hres, m_http_client); + bool r = wal->invoke_http_json("/getheight", hreq, hres); wal->set_refresh_from_block_height(hres.height); crypto::secret_key dummy_key; try { @@ -2686,7 +2684,7 @@ namespace tools } else { - er.message = "Success, but cannot update spent status after import multisig info as dameon is untrusted"; + er.message = "Success, but cannot update spent status after import multisig info as daemon is untrusted"; } return true; diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index bb458aa99..2ec53cc80 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -85,6 +85,7 @@ namespace tools MAP_JON_RPC_WE("transfer", on_transfer, wallet_rpc::COMMAND_RPC_TRANSFER) MAP_JON_RPC_WE("transfer_split", on_transfer_split, wallet_rpc::COMMAND_RPC_TRANSFER_SPLIT) MAP_JON_RPC_WE("sweep_dust", on_sweep_dust, wallet_rpc::COMMAND_RPC_SWEEP_DUST) + MAP_JON_RPC_WE("sweep_unmixable", on_sweep_dust, wallet_rpc::COMMAND_RPC_SWEEP_DUST) MAP_JON_RPC_WE("sweep_all", on_sweep_all, wallet_rpc::COMMAND_RPC_SWEEP_ALL) MAP_JON_RPC_WE("sweep_single", on_sweep_single, wallet_rpc::COMMAND_RPC_SWEEP_SINGLE) MAP_JON_RPC_WE("relay_tx", on_relay_tx, wallet_rpc::COMMAND_RPC_RELAY_TX) @@ -224,7 +225,6 @@ namespace tools tools::private_file rpc_login_file; std::atomic<bool> m_stop; bool m_trusted_daemon; - epee::net_utils::http::http_simple_client m_http_client; const boost::program_options::variables_map *m_vm; }; } |