diff options
Diffstat (limited to 'src/wallet')
-rw-r--r-- | src/wallet/api/pending_transaction.cpp | 53 | ||||
-rw-r--r-- | src/wallet/api/pending_transaction.h | 5 | ||||
-rw-r--r-- | src/wallet/api/wallet.cpp | 589 | ||||
-rw-r--r-- | src/wallet/api/wallet.h | 15 | ||||
-rw-r--r-- | src/wallet/api/wallet2_api.h | 98 | ||||
-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/ringdb.cpp | 18 | ||||
-rw-r--r-- | src/wallet/ringdb.h | 1 | ||||
-rw-r--r-- | src/wallet/wallet2.cpp | 326 | ||||
-rw-r--r-- | src/wallet/wallet2.h | 34 | ||||
-rw-r--r-- | src/wallet/wallet_args.cpp | 16 | ||||
-rw-r--r-- | src/wallet/wallet_args.h | 7 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server.cpp | 14 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server.h | 2 | ||||
-rw-r--r-- | src/wallet/wallet_rpc_server_commands_defs.h | 29 |
16 files changed, 846 insertions, 421 deletions
diff --git a/src/wallet/api/pending_transaction.cpp b/src/wallet/api/pending_transaction.cpp index ff4619f0f..8d200220d 100644 --- a/src/wallet/api/pending_transaction.cpp +++ b/src/wallet/api/pending_transaction.cpp @@ -34,6 +34,7 @@ #include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_basic/cryptonote_basic_impl.h" +#include "common/base58.h" #include <memory> #include <vector> @@ -102,6 +103,11 @@ bool PendingTransactionImpl::commit(const std::string &filename, bool overwrite) } // Commit tx else { + auto multisigState = m_wallet.multisig(); + if (multisigState.isMultisig && m_signers.size() < multisigState.threshold) { + throw runtime_error("Not enough signers to send multisig transaction"); + } + m_wallet.pauseRefresh(); while (!m_pending_tx.empty()) { auto & ptx = m_pending_tx.back(); @@ -188,6 +194,53 @@ std::vector<std::set<uint32_t>> PendingTransactionImpl::subaddrIndices() const return result; } +std::string PendingTransactionImpl::multisigSignData() { + try { + if (!m_wallet.multisig().isMultisig) { + throw std::runtime_error("wallet is not multisig"); + } + + auto cipher = m_wallet.m_wallet->save_multisig_tx(m_pending_tx); + return epee::string_tools::buff_to_hex_nodelimer(cipher); + } catch (const std::exception& e) { + m_status = Status_Error; + m_errorString = std::string(tr("Couldn't multisig sign data: ")) + e.what(); + } + + return std::string(); +} + +void PendingTransactionImpl::signMultisigTx() { + try { + std::vector<crypto::hash> ignore; + + tools::wallet2::multisig_tx_set txSet; + txSet.m_ptx = m_pending_tx; + txSet.m_signers = m_signers; + + if (!m_wallet.m_wallet->sign_multisig_tx(txSet, ignore)) { + throw std::runtime_error("couldn't sign multisig transaction"); + } + + std::swap(m_pending_tx, txSet.m_ptx); + std::swap(m_signers, txSet.m_signers); + } catch (const std::exception& e) { + m_status = Status_Error; + m_errorString = std::string(tr("Couldn't sign multisig transaction: ")) + e.what(); + } +} + +std::vector<std::string> PendingTransactionImpl::signersKeys() const { + std::vector<std::string> keys; + keys.reserve(m_signers.size()); + + for (const auto& signer: m_signers) { + keys.emplace_back(tools::base58::encode(cryptonote::t_serializable_object_to_blob(signer))); + } + + return keys; +} + } namespace Bitmonero = Monero; diff --git a/src/wallet/api/pending_transaction.h b/src/wallet/api/pending_transaction.h index d0bd66eb5..4f963c134 100644 --- a/src/wallet/api/pending_transaction.h +++ b/src/wallet/api/pending_transaction.h @@ -55,6 +55,10 @@ public: std::vector<std::set<uint32_t>> subaddrIndices() const; // TODO: continue with interface; + std::string multisigSignData(); + void signMultisigTx(); + std::vector<std::string> signersKeys() const; + private: friend class WalletImpl; WalletImpl &m_wallet; @@ -62,6 +66,7 @@ private: int m_status; std::string m_errorString; std::vector<tools::wallet2::pending_tx> m_pending_tx; + std::unordered_set<crypto::public_key> m_signers; }; diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index b02884f67..fdecacd8f 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; @@ -69,14 +69,48 @@ namespace { // Connection timeout 30 sec static const int DEFAULT_CONNECTION_TIMEOUT_MILLIS = 1000 * 30; - std::string get_default_ringdb_path() + std::string get_default_ringdb_path(cryptonote::network_type nettype) { boost::filesystem::path dir = tools::get_default_data_dir(); // remove .bitmonero, replace with .shared-ringdb dir = dir.remove_filename(); dir /= ".shared-ringdb"; + if (nettype == cryptonote::TESTNET) + dir /= "testnet"; + else if (nettype == cryptonote::STAGENET) + dir /= "stagenet"; return dir.string(); } + + void checkMultisigWalletReady(const tools::wallet2* wallet) { + if (!wallet) { + throw runtime_error("Wallet is not initialized yet"); + } + + bool ready; + if (!wallet->multisig(&ready)) { + throw runtime_error("Wallet is not multisig"); + } + + if (!ready) { + throw runtime_error("Multisig wallet is not finalized yet"); + } + } + + void checkMultisigWalletNotReady(const tools::wallet2* wallet) { + if (!wallet) { + throw runtime_error("Wallet is not initialized yet"); + } + + bool ready; + if (!wallet->multisig(&ready)) { + throw runtime_error("Wallet is not multisig"); + } + + if (ready) { + throw runtime_error("Multisig wallet is already finalized"); + } + } } struct Wallet2CallbackImpl : public tools::i_wallet2_callback @@ -305,14 +339,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) { @@ -395,9 +429,9 @@ bool WalletImpl::create(const std::string &path, const std::string &password, co // add logic to error out if new wallet requested but named wallet file exists if (keys_file_exists || wallet_file_exists) { - m_errorString = "attempting to generate or restore wallet, but specified file(s) exist. Exiting to not risk overwriting."; - LOG_ERROR(m_errorString); - m_status = Status_Critical; + std::string error = "attempting to generate or restore wallet, but specified file(s) exist. Exiting to not risk overwriting."; + LOG_ERROR(error); + setStatusCritical(error); return false; } // TODO: validate language @@ -406,11 +440,10 @@ bool WalletImpl::create(const std::string &path, const std::string &password, co try { recovery_val = m_wallet->generate(path, password, secret_key, false, false); m_password = password; - m_status = Status_Ok; + clearStatus(); } catch (const std::exception &e) { LOG_ERROR("Error creating wallet: " << e.what()); - m_status = Status_Critical; - m_errorString = e.what(); + setStatusCritical(e.what()); return false; } @@ -434,9 +467,9 @@ bool WalletImpl::createWatchOnly(const std::string &path, const std::string &pas // add logic to error out if new wallet requested but named wallet file exists if (keys_file_exists || wallet_file_exists) { - m_errorString = "attempting to generate view only wallet, but specified file(s) exist. Exiting to not risk overwriting."; - LOG_ERROR(m_errorString); - m_status = Status_Error; + std::string error = "attempting to generate view only wallet, but specified file(s) exist. Exiting to not risk overwriting."; + LOG_ERROR(error); + setStatusError(error); return false; } // TODO: validate language @@ -472,11 +505,10 @@ bool WalletImpl::createWatchOnly(const std::string &path, const std::string &pas uint64_t spent = 0; uint64_t unspent = 0; view_wallet->import_key_images(key_images,spent,unspent,false); - m_status = Status_Ok; + clearStatus(); } catch (const std::exception &e) { LOG_ERROR("Error creating view only wallet: " << e.what()); - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return false; } // Store wallet @@ -503,8 +535,7 @@ bool WalletImpl::recoverFromKeysWithPassword(const std::string &path, cryptonote::address_parse_info info; if(!get_account_address_from_str(info, m_wallet->nettype(), address_string)) { - m_errorString = tr("failed to parse address"); - m_status = Status_Error; + setStatusError(tr("failed to parse address")); return false; } @@ -515,8 +546,7 @@ bool WalletImpl::recoverFromKeysWithPassword(const std::string &path, cryptonote::blobdata spendkey_data; if(!epee::string_tools::parse_hexstr_to_binbuff(spendkey_string, spendkey_data) || spendkey_data.size() != sizeof(crypto::secret_key)) { - m_errorString = tr("failed to parse secret spend key"); - m_status = Status_Error; + setStatusError(tr("failed to parse secret spend key")); return false; } has_spendkey = true; @@ -525,15 +555,13 @@ bool WalletImpl::recoverFromKeysWithPassword(const std::string &path, // parse view secret key if (viewkey_string.empty()) { - m_errorString = tr("No view key supplied, cancelled"); - m_status = Status_Error; + setStatusError(tr("No view key supplied, cancelled")); return false; } cryptonote::blobdata viewkey_data; if(!epee::string_tools::parse_hexstr_to_binbuff(viewkey_string, viewkey_data) || viewkey_data.size() != sizeof(crypto::secret_key)) { - m_errorString = tr("failed to parse secret view key"); - m_status = Status_Error; + setStatusError(tr("failed to parse secret view key")); return false; } crypto::secret_key viewkey = *reinterpret_cast<const crypto::secret_key*>(viewkey_data.data()); @@ -542,24 +570,20 @@ bool WalletImpl::recoverFromKeysWithPassword(const std::string &path, crypto::public_key pkey; if(has_spendkey) { if (!crypto::secret_key_to_public_key(spendkey, pkey)) { - m_errorString = tr("failed to verify secret spend key"); - m_status = Status_Error; + setStatusError(tr("failed to verify secret spend key")); return false; } if (info.address.m_spend_public_key != pkey) { - m_errorString = tr("spend key does not match address"); - m_status = Status_Error; + setStatusError(tr("spend key does not match address")); return false; } } if (!crypto::secret_key_to_public_key(viewkey, pkey)) { - m_errorString = tr("failed to verify secret view key"); - m_status = Status_Error; + setStatusError(tr("failed to verify secret view key")); return false; } if (info.address.m_view_public_key != pkey) { - m_errorString = tr("view key does not match address"); - m_status = Status_Error; + setStatusError(tr("view key does not match address")); return false; } @@ -577,8 +601,7 @@ bool WalletImpl::recoverFromKeysWithPassword(const std::string &path, } catch (const std::exception& e) { - m_errorString = string(tr("failed to generate new wallet: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("failed to generate new wallet: ")) + e.what()); return false; } return true; @@ -599,16 +622,15 @@ 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->set_ring_database(get_default_ringdb_path(m_wallet->nettype())); m_wallet->load(path, password); m_password = password; } catch (const std::exception &e) { LOG_ERROR("Error opening wallet: " << e.what()); - m_status = Status_Critical; - m_errorString = e.what(); + setStatusCritical(e.what()); } - return m_status == Status_Ok; + return status() == Status_Ok; } bool WalletImpl::recover(const std::string &path, const std::string &seed) @@ -621,9 +643,8 @@ bool WalletImpl::recover(const std::string &path, const std::string &password, c clearStatus(); m_errorString.clear(); if (seed.empty()) { - m_errorString = "Electrum seed is empty"; - LOG_ERROR(m_errorString); - m_status = Status_Error; + LOG_ERROR("Electrum seed is empty"); + setStatusError(tr("Electrum seed is empty")); return false; } @@ -631,8 +652,7 @@ bool WalletImpl::recover(const std::string &path, const std::string &password, c crypto::secret_key recovery_key; std::string old_language; if (!crypto::ElectrumWords::words_to_bytes(seed, recovery_key, old_language)) { - m_errorString = "Electrum-style word list failed verification"; - m_status = Status_Error; + setStatusError(tr("Electrum-style word list failed verification")); return false; } @@ -644,10 +664,9 @@ bool WalletImpl::recover(const std::string &path, const std::string &password, c m_wallet->generate(path, password, recovery_key, true, false); } catch (const std::exception &e) { - m_status = Status_Critical; - m_errorString = e.what(); + setStatusCritical(e.what()); } - return m_status == Status_Ok; + return status() == Status_Ok; } bool WalletImpl::close(bool store) @@ -671,8 +690,7 @@ bool WalletImpl::close(bool store) result = true; clearStatus(); } catch (const std::exception &e) { - m_status = Status_Critical; - m_errorString = e.what(); + setStatusCritical(e.what()); LOG_ERROR("Error closing wallet: " << e.what()); } return result; @@ -698,14 +716,22 @@ void WalletImpl::setSeedLanguage(const std::string &arg) int WalletImpl::status() const { + boost::lock_guard<boost::mutex> l(m_statusMutex); return m_status; } std::string WalletImpl::errorString() const { + boost::lock_guard<boost::mutex> l(m_statusMutex); return m_errorString; } +void WalletImpl::statusWithErrorString(int& status, std::string& errorString) const { + boost::lock_guard<boost::mutex> l(m_statusMutex); + status = m_status; + errorString = m_errorString; +} + bool WalletImpl::setPassword(const std::string &password) { clearStatus(); @@ -713,10 +739,9 @@ bool WalletImpl::setPassword(const std::string &password) m_wallet->rewrite(m_wallet->get_wallet_file(), password); m_password = password; } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); } - return m_status == Status_Ok; + return status() == Status_Ok; } std::string WalletImpl::address(uint32_t accountIndex, uint32_t addressIndex) const @@ -769,11 +794,11 @@ bool WalletImpl::store(const std::string &path) } } catch (const std::exception &e) { LOG_ERROR("Error saving wallet: " << e.what()); - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); + return false; } - return m_status == Status_Ok; + return true; } string WalletImpl::filename() const @@ -806,8 +831,7 @@ bool WalletImpl::lightWalletImportWalletRequest(std::string &payment_id, uint64_ { cryptonote::COMMAND_RPC_IMPORT_WALLET_REQUEST::response response; if(!m_wallet->light_wallet_import_wallet_request(response)){ - m_errorString = tr("Failed to send import wallet request"); - m_status = Status_Error; + setStatusError(tr("Failed to send import wallet request")); return false; } fee = response.import_fee; @@ -820,8 +844,7 @@ bool WalletImpl::lightWalletImportWalletRequest(std::string &payment_id, uint64_ catch (const std::exception &e) { LOG_ERROR("Error sending import wallet request: " << e.what()); - m_errorString = e.what(); - m_status = Status_Error; + setStatusError(e.what()); return false; } return true; @@ -870,12 +893,9 @@ uint64_t WalletImpl::daemonBlockChainHeight() const if (!err.empty()) { LOG_ERROR(__FUNCTION__ << ": " << err); result = 0; - m_errorString = err; - m_status = Status_Error; - + setStatusError(err); } else { - m_status = Status_Ok; - m_errorString = ""; + clearStatus(); } return result; } @@ -892,12 +912,9 @@ uint64_t WalletImpl::daemonBlockChainTargetHeight() const if (!err.empty()) { LOG_ERROR(__FUNCTION__ << ": " << err); result = 0; - m_errorString = err; - m_status = Status_Error; - + setStatusError(err); } else { - m_status = Status_Ok; - m_errorString = ""; + clearStatus(); } // Target height can be 0 when daemon is synced. Use blockchain height instead. if(result == 0) @@ -921,8 +938,10 @@ bool WalletImpl::synchronized() const bool WalletImpl::refresh() { clearStatus(); + //TODO: make doRefresh return bool to know whether the error occured during refresh or not + //otherwise one may try, say, to send transaction, transfer fails and this method returns false doRefresh(); - return m_status == Status_Ok; + return status() == Status_Ok; } void WalletImpl::refreshAsync() @@ -952,8 +971,7 @@ UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_file clearStatus(); UnsignedTransactionImpl * transaction = new UnsignedTransactionImpl(*this); if (!m_wallet->load_unsigned_tx(unsigned_filename, transaction->m_unsigned_tx_set)){ - m_errorString = tr("Failed to load unsigned transactions"); - m_status = Status_Error; + setStatusError(tr("Failed to load unsigned transactions")); } // Check tx data and construct confirmation message @@ -961,8 +979,7 @@ UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_file if (!transaction->m_unsigned_tx_set.transfers.empty()) extra_message = (boost::format("%u outputs to import. ") % (unsigned)transaction->m_unsigned_tx_set.transfers.size()).str(); transaction->checkLoadedTx([&transaction](){return transaction->m_unsigned_tx_set.txes.size();}, [&transaction](size_t n)->const tools::wallet2::tx_construction_data&{return transaction->m_unsigned_tx_set.txes[n];}, extra_message); - m_status = transaction->status(); - m_errorString = transaction->errorString(); + setStatus(transaction->status(), transaction->errorString()); return transaction; } @@ -973,14 +990,12 @@ bool WalletImpl::submitTransaction(const string &fileName) { bool r = m_wallet->load_tx(fileName, transaction->m_pending_tx); if (!r) { - m_errorString = tr("Failed to load transaction from file"); - m_status = Status_Ok; + setStatus(Status_Ok, tr("Failed to load transaction from file")); return false; } if(!transaction->commit()) { - m_errorString = transaction->m_errorString; - m_status = Status_Error; + setStatusError(transaction->m_errorString); return false; } @@ -991,8 +1006,7 @@ bool WalletImpl::exportKeyImages(const string &filename) { if (m_wallet->watch_only()) { - m_errorString = tr("Wallet is view only"); - m_status = Status_Error; + setStatusError(tr("Wallet is view only")); return false; } @@ -1000,16 +1014,14 @@ bool WalletImpl::exportKeyImages(const string &filename) { if (!m_wallet->export_key_images(filename)) { - m_errorString = tr("failed to save file ") + filename; - m_status = Status_Error; + setStatusError(tr("failed to save file ") + filename); return false; } } catch (const std::exception &e) { LOG_ERROR("Error exporting key images: " << e.what()); - m_errorString = e.what(); - m_status = Status_Error; + setStatusError(e.what()); return false; } return true; @@ -1018,8 +1030,7 @@ bool WalletImpl::exportKeyImages(const string &filename) bool WalletImpl::importKeyImages(const string &filename) { if (!trustedDaemon()) { - m_status = Status_Error; - m_errorString = tr("Key images can only be imported with a trusted daemon"); + setStatusError(tr("Key images can only be imported with a trusted daemon")); return false; } try @@ -1032,8 +1043,7 @@ bool WalletImpl::importKeyImages(const string &filename) catch (const std::exception &e) { LOG_ERROR("Error exporting key images: " << e.what()); - m_errorString = string(tr("Failed to import key images: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("Failed to import key images: ")) + e.what()); return false; } @@ -1065,8 +1075,7 @@ std::string WalletImpl::getSubaddressLabel(uint32_t accountIndex, uint32_t addre catch (const std::exception &e) { LOG_ERROR("Error getting subaddress label: ") << e.what(); - m_errorString = string(tr("Failed to get subaddress label: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("Failed to get subaddress label: ")) + e.what()); return ""; } } @@ -1079,11 +1088,136 @@ void WalletImpl::setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex catch (const std::exception &e) { LOG_ERROR("Error setting subaddress label: ") << e.what(); - m_errorString = string(tr("Failed to set subaddress label: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("Failed to set subaddress label: ")) + e.what()); } } +MultisigState WalletImpl::multisig() const { + MultisigState state; + state.isMultisig = m_wallet->multisig(&state.isReady, &state.threshold, &state.total); + + return state; +} + +string WalletImpl::getMultisigInfo() const { + try { + clearStatus(); + return m_wallet->get_multisig_info(); + } catch (const exception& e) { + LOG_ERROR("Error on generating multisig info: ") << e.what(); + setStatusError(string(tr("Failed to get multisig info: ")) + e.what()); + } + + return string(); +} + +string WalletImpl::makeMultisig(const vector<string>& info, uint32_t threshold) { + try { + clearStatus(); + + if (m_wallet->multisig()) { + throw runtime_error("Wallet is already multisig"); + } + + return m_wallet->make_multisig(epee::wipeable_string(m_password), info, threshold); + } catch (const exception& e) { + LOG_ERROR("Error on making multisig wallet: ") << e.what(); + setStatusError(string(tr("Failed to make multisig: ")) + e.what()); + } + + return string(); +} + +bool WalletImpl::finalizeMultisig(const vector<string>& extraMultisigInfo) { + try { + clearStatus(); + checkMultisigWalletNotReady(m_wallet); + + if (m_wallet->finalize_multisig(epee::wipeable_string(m_password), extraMultisigInfo)) { + return true; + } + + setStatusError(tr("Failed to finalize multisig wallet creation")); + } catch (const exception& e) { + LOG_ERROR("Error on finalizing multisig wallet creation: ") << e.what(); + setStatusError(string(tr("Failed to finalize multisig wallet creation: ")) + e.what()); + } + + return false; +} + +bool WalletImpl::exportMultisigImages(string& images) { + try { + clearStatus(); + checkMultisigWalletReady(m_wallet); + + auto blob = m_wallet->export_multisig(); + images = epee::string_tools::buff_to_hex_nodelimer(blob); + return true; + } catch (const exception& e) { + LOG_ERROR("Error on exporting multisig images: ") << e.what(); + setStatusError(string(tr("Failed to export multisig images: ")) + e.what()); + } + + return false; +} + +size_t WalletImpl::importMultisigImages(const vector<string>& images) { + try { + clearStatus(); + checkMultisigWalletReady(m_wallet); + + std::vector<std::string> blobs; + blobs.reserve(images.size()); + + for (const auto& image: images) { + std::string blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(image, blob)) { + LOG_ERROR("Failed to parse imported multisig images"); + setStatusError(tr("Failed to parse imported multisig images")); + return 0; + } + + blobs.emplace_back(std::move(blob)); + } + + return m_wallet->import_multisig(blobs); + } catch (const exception& e) { + LOG_ERROR("Error on importing multisig images: ") << e.what(); + setStatusError(string(tr("Failed to import multisig images: ")) + e.what()); + } + + return 0; +} + +PendingTransaction* WalletImpl::restoreMultisigTransaction(const string& signData) { + try { + clearStatus(); + checkMultisigWalletReady(m_wallet); + + string binary; + if (!epee::string_tools::parse_hexstr_to_binbuff(signData, binary)) { + throw runtime_error("Failed to deserialize multisig transaction"); + } + + tools::wallet2::multisig_tx_set txSet; + if (!m_wallet->load_multisig_tx(binary, txSet, {})) { + throw runtime_error("couldn't parse multisig transaction data"); + } + + auto ptx = new PendingTransactionImpl(*this); + ptx->m_pending_tx = txSet.m_ptx; + ptx->m_signers = txSet.m_signers; + + return ptx; + } catch (exception& e) { + LOG_ERROR("Error on restoring multisig transaction: ") << e.what(); + setStatusError(string(tr("Failed to restore multisig transaction: ")) + e.what()); + } + + return nullptr; +} + // TODO: // 1 - properly handle payment id (add another menthod with explicit 'payment_id' param) // 2 - check / design how "Transaction" can be single interface @@ -1117,8 +1251,7 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const do { if(!cryptonote::get_account_address_from_str(info, m_wallet->nettype(), dst_addr)) { // TODO: copy-paste 'if treating as an address fails, try as url' from simplewallet.cpp:1982 - m_status = Status_Error; - m_errorString = "Invalid destination address"; + setStatusError(tr("Invalid destination address")); break; } @@ -1143,8 +1276,7 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const } if (!r) { - m_status = Status_Error; - m_errorString = tr("payment id has invalid format, expected 16 or 64 character hex string: ") + payment_id; + setStatusError(tr("payment id has invalid format, expected 16 or 64 character hex string: ") + payment_id); break; } } @@ -1153,8 +1285,7 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, info.payment_id); bool r = add_extra_nonce_to_tx_extra(extra, extra_nonce); if (!r) { - m_status = Status_Error; - m_errorString = tr("Failed to add short payment id: ") + epee::string_tools::pod_to_hex(info.payment_id); + setStatusError(tr("Failed to add short payment id: ") + epee::string_tools::pod_to_hex(info.payment_id)); break; } } @@ -1185,40 +1316,33 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const extra, subaddr_account, subaddr_indices, m_trustedDaemon); } + if (multisig().isMultisig) { + transaction->m_signers = m_wallet->make_multisig_tx_set(transaction->m_pending_tx).m_signers; + } } catch (const tools::error::daemon_busy&) { // TODO: make it translatable with "tr"? - m_errorString = tr("daemon is busy. Please try again later."); - m_status = Status_Error; + setStatusError(tr("daemon is busy. Please try again later.")); } catch (const tools::error::no_connection_to_daemon&) { - m_errorString = tr("no connection to daemon. Please make sure daemon is running."); - m_status = Status_Error; + setStatusError(tr("no connection to daemon. Please make sure daemon is running.")); } catch (const tools::error::wallet_rpc_error& e) { - m_errorString = tr("RPC error: ") + e.to_string(); - m_status = Status_Error; + setStatusError(tr("RPC error: ") + e.to_string()); } catch (const tools::error::get_random_outs_error &e) { - m_errorString = (boost::format(tr("failed to get random outputs to mix: %s")) % e.what()).str(); - m_status = Status_Error; - + setStatusError((boost::format(tr("failed to get random outputs to mix: %s")) % e.what()).str()); } catch (const tools::error::not_enough_unlocked_money& e) { - m_status = Status_Error; std::ostringstream writer; writer << boost::format(tr("not enough money to transfer, available only %s, sent amount %s")) % print_money(e.available()) % print_money(e.tx_amount()); - m_errorString = writer.str(); - + setStatusError(writer.str()); } catch (const tools::error::not_enough_money& e) { - m_status = Status_Error; std::ostringstream writer; writer << boost::format(tr("not enough money to transfer, overall balance only %s, sent amount %s")) % print_money(e.available()) % print_money(e.tx_amount()); - m_errorString = writer.str(); - + setStatusError(writer.str()); } catch (const tools::error::tx_not_possible& e) { - m_status = Status_Error; std::ostringstream writer; writer << boost::format(tr("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)")) % @@ -1226,8 +1350,7 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const print_money(e.tx_amount() + e.fee()) % print_money(e.tx_amount()) % print_money(e.fee()); - m_errorString = writer.str(); - + setStatusError(writer.str()); } catch (const tools::error::not_enough_outs_to_mix& e) { std::ostringstream writer; writer << tr("not enough outputs for specified ring size") << " = " << (e.mixin_count() + 1) << ":"; @@ -1235,42 +1358,31 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to use") << " = " << outs_for_amount.second; } writer << "\n" << tr("Please sweep unmixable outputs."); - m_errorString = writer.str(); - m_status = Status_Error; + setStatusError(writer.str()); } catch (const tools::error::tx_not_constructed&) { - m_errorString = tr("transaction was not constructed"); - m_status = Status_Error; + setStatusError(tr("transaction was not constructed")); } catch (const tools::error::tx_rejected& e) { std::ostringstream writer; writer << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); - m_errorString = writer.str(); - m_status = Status_Error; + setStatusError(writer.str()); } catch (const tools::error::tx_sum_overflow& e) { - m_errorString = e.what(); - m_status = Status_Error; + setStatusError(e.what()); } catch (const tools::error::zero_destination&) { - m_errorString = tr("one of destinations is zero"); - m_status = Status_Error; + setStatusError(tr("one of destinations is zero")); } catch (const tools::error::tx_too_big& e) { - m_errorString = tr("failed to find a suitable way to split transactions"); - m_status = Status_Error; + setStatusError(tr("failed to find a suitable way to split transactions")); } catch (const tools::error::transfer_error& e) { - m_errorString = string(tr("unknown transfer error: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("unknown transfer error: ")) + e.what()); } catch (const tools::error::wallet_internal_error& e) { - m_errorString = string(tr("internal error: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("internal error: ")) + e.what()); } catch (const std::exception& e) { - m_errorString = string(tr("unexpected error: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("unexpected error: ")) + e.what()); } catch (...) { - m_errorString = tr("unknown error"); - m_status = Status_Error; + setStatusError(tr("unknown error")); } } while (false); - transaction->m_status = m_status; - transaction->m_errorString = m_errorString; + statusWithErrorString(transaction->m_status, transaction->m_errorString); // Resume refresh thread startRefresh(); return transaction; @@ -1291,38 +1403,31 @@ PendingTransaction *WalletImpl::createSweepUnmixableTransaction() } catch (const tools::error::daemon_busy&) { // TODO: make it translatable with "tr"? - m_errorString = tr("daemon is busy. Please try again later."); - m_status = Status_Error; + setStatusError(tr("daemon is busy. Please try again later.")); } catch (const tools::error::no_connection_to_daemon&) { - m_errorString = tr("no connection to daemon. Please make sure daemon is running."); - m_status = Status_Error; + setStatusError(tr("no connection to daemon. Please make sure daemon is running.")); } catch (const tools::error::wallet_rpc_error& e) { - m_errorString = tr("RPC error: ") + e.to_string(); - m_status = Status_Error; + setStatusError(tr("RPC error: ") + e.to_string()); } catch (const tools::error::get_random_outs_error&) { - m_errorString = tr("failed to get random outputs to mix"); - m_status = Status_Error; - + setStatusError(tr("failed to get random outputs to mix")); } catch (const tools::error::not_enough_unlocked_money& e) { - m_status = Status_Error; + setStatusError(""); std::ostringstream writer; writer << boost::format(tr("not enough money to transfer, available only %s, sent amount %s")) % print_money(e.available()) % print_money(e.tx_amount()); - m_errorString = writer.str(); - + setStatusError(writer.str()); } catch (const tools::error::not_enough_money& e) { - m_status = Status_Error; + setStatusError(""); std::ostringstream writer; writer << boost::format(tr("not enough money to transfer, overall balance only %s, sent amount %s")) % print_money(e.available()) % print_money(e.tx_amount()); - m_errorString = writer.str(); - + setStatusError(writer.str()); } catch (const tools::error::tx_not_possible& e) { - m_status = Status_Error; + setStatusError(""); std::ostringstream writer; writer << boost::format(tr("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)")) % @@ -1330,50 +1435,38 @@ PendingTransaction *WalletImpl::createSweepUnmixableTransaction() print_money(e.tx_amount() + e.fee()) % print_money(e.tx_amount()) % print_money(e.fee()); - m_errorString = writer.str(); - + setStatusError(writer.str()); } catch (const tools::error::not_enough_outs_to_mix& e) { std::ostringstream writer; writer << tr("not enough outputs for specified ring size") << " = " << (e.mixin_count() + 1) << ":"; for (const std::pair<uint64_t, uint64_t> outs_for_amount : e.scanty_outs()) { writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to use") << " = " << outs_for_amount.second; } - m_errorString = writer.str(); - m_status = Status_Error; + setStatusError(writer.str()); } catch (const tools::error::tx_not_constructed&) { - m_errorString = tr("transaction was not constructed"); - m_status = Status_Error; + setStatusError(tr("transaction was not constructed")); } catch (const tools::error::tx_rejected& e) { std::ostringstream writer; writer << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); - m_errorString = writer.str(); - m_status = Status_Error; + setStatusError(writer.str()); } catch (const tools::error::tx_sum_overflow& e) { - m_errorString = e.what(); - m_status = Status_Error; + setStatusError(e.what()); } catch (const tools::error::zero_destination&) { - m_errorString = tr("one of destinations is zero"); - m_status = Status_Error; + setStatusError(tr("one of destinations is zero")); } catch (const tools::error::tx_too_big& e) { - m_errorString = tr("failed to find a suitable way to split transactions"); - m_status = Status_Error; + setStatusError(tr("failed to find a suitable way to split transactions")); } catch (const tools::error::transfer_error& e) { - m_errorString = string(tr("unknown transfer error: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("unknown transfer error: ")) + e.what()); } catch (const tools::error::wallet_internal_error& e) { - m_errorString = string(tr("internal error: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("internal error: ")) + e.what()); } catch (const std::exception& e) { - m_errorString = string(tr("unexpected error: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("unexpected error: ")) + e.what()); } catch (...) { - m_errorString = tr("unknown error"); - m_status = Status_Error; + setStatusError(tr("unknown error")); } } while (false); - transaction->m_status = m_status; - transaction->m_errorString = m_errorString; + statusWithErrorString(transaction->m_status, transaction->m_errorString); return transaction; } @@ -1444,8 +1537,7 @@ std::string WalletImpl::getTxKey(const std::string &txid_str) const crypto::hash txid; if(!epee::string_tools::hex_to_pod(txid_str, txid)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse txid"); + setStatusError(tr("Failed to parse txid")); return ""; } @@ -1453,7 +1545,7 @@ std::string WalletImpl::getTxKey(const std::string &txid_str) const std::vector<crypto::secret_key> additional_tx_keys; if (m_wallet->get_tx_key(txid, tx_key, additional_tx_keys)) { - m_status = Status_Ok; + clearStatus(); std::ostringstream oss; oss << epee::string_tools::pod_to_hex(tx_key); for (size_t i = 0; i < additional_tx_keys.size(); ++i) @@ -1462,8 +1554,7 @@ std::string WalletImpl::getTxKey(const std::string &txid_str) const } else { - m_status = Status_Error; - m_errorString = tr("no tx keys found for this txid"); + setStatusError(tr("no tx keys found for this txid")); return ""; } } @@ -1473,8 +1564,7 @@ bool WalletImpl::checkTxKey(const std::string &txid_str, std::string tx_key_str, crypto::hash txid; if (!epee::string_tools::hex_to_pod(txid_str, txid)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse txid"); + setStatusError(tr("Failed to parse txid")); return false; } @@ -1482,8 +1572,7 @@ bool WalletImpl::checkTxKey(const std::string &txid_str, std::string tx_key_str, std::vector<crypto::secret_key> additional_tx_keys; if (!epee::string_tools::hex_to_pod(tx_key_str.substr(0, 64), tx_key)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse tx key"); + setStatusError(tr("Failed to parse tx key")); return false; } tx_key_str = tx_key_str.substr(64); @@ -1492,8 +1581,7 @@ bool WalletImpl::checkTxKey(const std::string &txid_str, std::string tx_key_str, additional_tx_keys.resize(additional_tx_keys.size() + 1); if (!epee::string_tools::hex_to_pod(tx_key_str.substr(0, 64), additional_tx_keys.back())) { - m_status = Status_Error; - m_errorString = tr("Failed to parse tx key"); + setStatusError(tr("Failed to parse tx key")); return false; } tx_key_str = tx_key_str.substr(64); @@ -1502,21 +1590,19 @@ bool WalletImpl::checkTxKey(const std::string &txid_str, std::string tx_key_str, cryptonote::address_parse_info info; if (!cryptonote::get_account_address_from_str(info, m_wallet->nettype(), address_str)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse address"); + setStatusError(tr("Failed to parse address")); return false; } try { m_wallet->check_tx_key(txid, tx_key, additional_tx_keys, info.address, received, in_pool, confirmations); - m_status = Status_Ok; + clearStatus(); return true; } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return false; } } @@ -1526,28 +1612,25 @@ std::string WalletImpl::getTxProof(const std::string &txid_str, const std::strin crypto::hash txid; if (!epee::string_tools::hex_to_pod(txid_str, txid)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse txid"); + setStatusError(tr("Failed to parse txid")); return ""; } cryptonote::address_parse_info info; if (!cryptonote::get_account_address_from_str(info, m_wallet->nettype(), address_str)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse address"); + setStatusError(tr("Failed to parse address")); return ""; } try { - m_status = Status_Ok; + clearStatus(); return m_wallet->get_tx_proof(txid, info.address, info.is_subaddress, message); } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return ""; } } @@ -1557,29 +1640,26 @@ bool WalletImpl::checkTxProof(const std::string &txid_str, const std::string &ad crypto::hash txid; if (!epee::string_tools::hex_to_pod(txid_str, txid)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse txid"); + setStatusError(tr("Failed to parse txid")); return false; } cryptonote::address_parse_info info; if (!cryptonote::get_account_address_from_str(info, m_wallet->nettype(), address_str)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse address"); + setStatusError(tr("Failed to parse address")); return false; } try { good = m_wallet->check_tx_proof(txid, info.address, info.is_subaddress, message, signature, received, in_pool, confirmations); - m_status = Status_Ok; + clearStatus(); return true; } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return false; } } @@ -1588,20 +1668,18 @@ std::string WalletImpl::getSpendProof(const std::string &txid_str, const std::st crypto::hash txid; if(!epee::string_tools::hex_to_pod(txid_str, txid)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse txid"); + setStatusError(tr("Failed to parse txid")); return ""; } try { - m_status = Status_Ok; + clearStatus(); return m_wallet->get_spend_proof(txid, message); } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return ""; } } @@ -1611,21 +1689,19 @@ bool WalletImpl::checkSpendProof(const std::string &txid_str, const std::string crypto::hash txid; if(!epee::string_tools::hex_to_pod(txid_str, txid)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse txid"); + setStatusError(tr("Failed to parse txid")); return false; } try { - m_status = Status_Ok; + clearStatus(); good = m_wallet->check_spend_proof(txid, message, signature); return true; } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return false; } } @@ -1633,7 +1709,7 @@ bool WalletImpl::checkSpendProof(const std::string &txid_str, const std::string std::string WalletImpl::getReserveProof(bool all, uint32_t account_index, uint64_t amount, const std::string &message) const { try { - m_status = Status_Ok; + clearStatus(); boost::optional<std::pair<uint32_t, uint64_t>> account_minreserve; if (!all) { @@ -1643,8 +1719,7 @@ std::string WalletImpl::getReserveProof(bool all, uint32_t account_index, uint64 } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return ""; } } @@ -1653,28 +1728,25 @@ bool WalletImpl::checkReserveProof(const std::string &address, const std::string cryptonote::address_parse_info info; if (!cryptonote::get_account_address_from_str(info, m_wallet->nettype(), address)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse address"); + setStatusError(tr("Failed to parse address")); return false; } if (info.is_subaddress) { - m_status = Status_Error; - m_errorString = tr("Address must not be a subaddress"); + setStatusError(tr("Address must not be a subaddress")); return false; } good = false; try { - m_status = Status_Ok; + clearStatus(); good = m_wallet->check_reserve_proof(info.address, message, signature, total, spent); return true; } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return false; } } @@ -1697,10 +1769,10 @@ bool WalletImpl::verifySignedMessage(const std::string &message, const std::stri bool WalletImpl::connectToDaemon() { bool result = m_wallet->check_connection(NULL, DEFAULT_CONNECTION_TIMEOUT_MILLIS); - m_status = result ? Status_Ok : Status_Error; if (!result) { - m_errorString = "Error connecting to daemon at " + m_wallet->get_daemon_address(); + setStatusError("Error connecting to daemon at " + m_wallet->get_daemon_address()); } else { + clearStatus(); // start refreshing here } return result; @@ -1735,10 +1807,28 @@ bool WalletImpl::watchOnly() const void WalletImpl::clearStatus() const { + boost::lock_guard<boost::mutex> l(m_statusMutex); m_status = Status_Ok; m_errorString.clear(); } +void WalletImpl::setStatusError(const std::string& message) const +{ + setStatus(Status_Error, message); +} + +void WalletImpl::setStatusCritical(const std::string& message) const +{ + setStatus(Status_Critical, message); +} + +void WalletImpl::setStatus(int status, const std::string& message) const +{ + boost::lock_guard<boost::mutex> l(m_statusMutex); + m_status = status; + m_errorString = message; +} + void WalletImpl::refreshThreadFunc() { LOG_PRINT_L3(__FUNCTION__ << ": starting refresh thread"); @@ -1760,7 +1850,7 @@ void WalletImpl::refreshThreadFunc() LOG_PRINT_L3(__FUNCTION__ << ": refresh lock acquired..."); LOG_PRINT_L3(__FUNCTION__ << ": m_refreshEnabled: " << m_refreshEnabled); - LOG_PRINT_L3(__FUNCTION__ << ": m_status: " << m_status); + LOG_PRINT_L3(__FUNCTION__ << ": m_status: " << status()); if (m_refreshEnabled) { LOG_PRINT_L3(__FUNCTION__ << ": refreshing..."); doRefresh(); @@ -1792,8 +1882,7 @@ void WalletImpl::doRefresh() LOG_PRINT_L3(__FUNCTION__ << ": skipping refresh - daemon is not synced"); } } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); } if (m_wallet2Callback->getListener()) { m_wallet2Callback->getListener()->refreshed(); @@ -1882,16 +1971,14 @@ bool WalletImpl::rescanSpent() { clearStatus(); if (!trustedDaemon()) { - m_status = Status_Error; - m_errorString = tr("Rescan spent can only be used with a trusted daemon"); + setStatusError(tr("Rescan spent can only be used with a trusted daemon")); return false; } try { m_wallet->rescan_spent(); } catch (const std::exception &e) { LOG_ERROR(__FUNCTION__ << " error: " << e.what()); - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return false; } return true; @@ -1917,8 +2004,7 @@ bool WalletImpl::blackballOutputs(const std::vector<std::string> &pubkeys, bool 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"); + setStatusError(tr("Failed to parse output public key")); return false; } raw_pubkeys.push_back(pkey); @@ -1926,8 +2012,7 @@ bool WalletImpl::blackballOutputs(const std::vector<std::string> &pubkeys, bool bool ret = m_wallet->set_blackballed_outputs(raw_pubkeys, add); if (!ret) { - m_status = Status_Error; - m_errorString = tr("Failed to set blackballed outputs"); + setStatusError(tr("Failed to set blackballed outputs")); return false; } return true; @@ -1938,15 +2023,13 @@ 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"); + setStatusError(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"); + setStatusError(tr("Failed to unblackball output")); return false; } return true; @@ -1957,15 +2040,13 @@ bool WalletImpl::getRing(const std::string &key_image, std::vector<uint64_t> &ri 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"); + setStatusError(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"); + setStatusError(tr("Failed to get ring")); return false; } return true; @@ -1976,16 +2057,14 @@ bool WalletImpl::getRings(const std::string &txid, std::vector<std::pair<std::st 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"); + setStatusError(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"); + setStatusError(tr("Failed to get rings")); return false; } for (const auto &r: raw_rings) @@ -2000,15 +2079,13 @@ bool WalletImpl::setRing(const std::string &key_image, const std::vector<uint64_ 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"); + setStatusError(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"); + setStatusError(tr("Failed to set ring")); return false; } return true; diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 4929c9673..e0e627c36 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -83,6 +83,7 @@ public: // void setListener(Listener *) {} int status() const; std::string errorString() const; + void statusWithErrorString(int& status, std::string& errorString) const override; bool setPassword(const std::string &password); std::string address(uint32_t accountIndex = 0, uint32_t addressIndex = 0) const; std::string integratedAddress(const std::string &payment_id) const; @@ -126,6 +127,14 @@ public: std::string getSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex) const; void setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label); + MultisigState multisig() const override; + std::string getMultisigInfo() const override; + std::string makeMultisig(const std::vector<std::string>& info, uint32_t threshold) override; + bool finalizeMultisig(const std::vector<std::string>& extraMultisigInfo) override; + bool exportMultisigImages(std::string& images) override; + size_t importMultisigImages(const std::vector<std::string>& images) override; + PendingTransaction* restoreMultisigTransaction(const std::string& signData) override; + PendingTransaction * createTransaction(const std::string &dst_addr, const std::string &payment_id, optional<uint64_t> amount, uint32_t mixin_count, PendingTransaction::Priority priority = PendingTransaction::Priority_Low, @@ -174,6 +183,9 @@ public: private: void clearStatus() const; + void setStatusError(const std::string& message) const; + void setStatusCritical(const std::string& message) const; + void setStatus(int status, const std::string& message) const; void refreshThreadFunc(); void doRefresh(); bool daemonSynced() const; @@ -191,7 +203,8 @@ private: friend class SubaddressAccountImpl; tools::wallet2 * m_wallet; - mutable std::atomic<int> m_status; + mutable boost::mutex m_statusMutex; + mutable int m_status; mutable std::string m_errorString; std::string m_password; TransactionHistoryImpl * m_history; diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index d4e41c5aa..27d290e68 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -100,6 +100,30 @@ struct PendingTransaction virtual uint64_t txCount() const = 0; virtual std::vector<uint32_t> subaddrAccount() const = 0; virtual std::vector<std::set<uint32_t>> subaddrIndices() const = 0; + + /** + * @brief multisigSignData + * @return encoded multisig transaction with signers' keys. + * Transfer this data to another wallet participant to sign it. + * Assumed use case is: + * 1. Initiator: + * auto data = pendingTransaction->multisigSignData(); + * 2. Signer1: + * pendingTransaction = wallet->restoreMultisigTransaction(data); + * pendingTransaction->signMultisigTx(); + * auto signed = pendingTransaction->multisigSignData(); + * 3. Signer2: + * pendingTransaction = wallet->restoreMultisigTransaction(signed); + * pendingTransaction->signMultisigTx(); + * pendingTransaction->commit(); + */ + virtual std::string multisigSignData() = 0; + virtual void signMultisigTx() = 0; + /** + * @brief signersKeys + * @return vector of base58-encoded signers' public keys + */ + virtual std::vector<std::string> signersKeys() const = 0; }; /** @@ -291,6 +315,15 @@ struct SubaddressAccount virtual void refresh() = 0; }; +struct MultisigState { + MultisigState() : isMultisig(false), isReady(false), threshold(0), total(0) {} + + bool isMultisig; + bool isReady; + uint32_t threshold; + uint32_t total; +}; + struct WalletListener { virtual ~WalletListener() = 0; @@ -358,9 +391,11 @@ struct Wallet virtual std::string getSeedLanguage() const = 0; virtual void setSeedLanguage(const std::string &arg) = 0; //! returns wallet status (Status_Ok | Status_Error) - virtual int status() const = 0; + virtual int status() const = 0; //deprecated: use safe alternative statusWithErrorString //! in case error status, returns error string - virtual std::string errorString() const = 0; + virtual std::string errorString() const = 0; //deprecated: use safe alternative statusWithErrorString + //! returns both error and error string atomically. suggested to use in instead of status() and errorString() + virtual void statusWithErrorString(int& status, std::string& errorString) const = 0; virtual bool setPassword(const std::string &password) = 0; virtual std::string address(uint32_t accountIndex = 0, uint32_t addressIndex = 0) const = 0; std::string mainAddress() const { return address(0, 0); } @@ -556,7 +591,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); @@ -628,6 +664,48 @@ struct Wallet */ virtual void setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label) = 0; + /** + * @brief multisig - returns current state of multisig wallet creation process + * @return MultisigState struct + */ + virtual MultisigState multisig() const = 0; + /** + * @brief getMultisigInfo + * @return serialized and signed multisig info string + */ + virtual std::string getMultisigInfo() const = 0; + /** + * @brief makeMultisig - switches wallet in multisig state. The one and only creation phase for N / N wallets + * @param info - vector of multisig infos from other participants obtained with getMulitisInfo call + * @param threshold - number of required signers to make valid transaction. Must be equal to number of participants (N) or N - 1 + * @return in case of N / N wallets returns empty string since no more key exchanges needed. For N - 1 / N wallets returns base58 encoded extra multisig info + */ + virtual std::string makeMultisig(const std::vector<std::string>& info, uint32_t threshold) = 0; + /** + * @brief finalizeMultisig - finalizes N - 1 / N multisig wallets creation + * @param extraMultisigInfo - wallet participants' extra multisig info obtained with makeMultisig call + * @return true if success + */ + virtual bool finalizeMultisig(const std::vector<std::string>& extraMultisigInfo) = 0; + /** + * @brief exportMultisigImages - exports transfers' key images + * @param images - output paramter for hex encoded array of images + * @return true if success + */ + virtual bool exportMultisigImages(std::string& images) = 0; + /** + * @brief importMultisigImages - imports other participants' multisig images + * @param images - array of hex encoded arrays of images obtained with exportMultisigImages + * @return number of imported images + */ + virtual size_t importMultisigImages(const std::vector<std::string>& images) = 0; + + /** + * @brief restoreMultisigTransaction creates PendingTransaction from signData + * @param signData encrypted unsigned transaction. Obtained with PendingTransaction::multisigSignData + * @return PendingTransaction + */ + virtual PendingTransaction* restoreMultisigTransaction(const std::string& signData) = 0; /*! * \brief createTransaction creates transaction. if dst_addr is an integrated address, payment_id is ignored * \param dst_addr destination address as string @@ -956,25 +1034,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/ringdb.cpp b/src/wallet/ringdb.cpp index 44992520f..3f2634c8b 100644 --- a/src/wallet/ringdb.cpp +++ b/src/wallet/ringdb.cpp @@ -190,7 +190,8 @@ namespace tools { ringdb::ringdb(std::string filename, const std::string &genesis): - filename(filename) + filename(filename), + env(NULL) { MDB_txn *txn; bool tx_active = false; @@ -227,9 +228,18 @@ ringdb::ringdb(std::string filename, const std::string &genesis): ringdb::~ringdb() { - mdb_dbi_close(env, dbi_rings); - mdb_dbi_close(env, dbi_blackballs); - mdb_env_close(env); + close(); +} + +void ringdb::close() +{ + if (env) + { + mdb_dbi_close(env, dbi_rings); + mdb_dbi_close(env, dbi_blackballs); + mdb_env_close(env); + env = NULL; + } } bool ringdb::add_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx) diff --git a/src/wallet/ringdb.h b/src/wallet/ringdb.h index 2bd1ac149..6b4bce124 100644 --- a/src/wallet/ringdb.h +++ b/src/wallet/ringdb.h @@ -41,6 +41,7 @@ namespace tools { public: ringdb(std::string filename, const std::string &genesis); + void close(); ~ringdb(); bool add_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 4b7e6dd93..365a14515 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -110,7 +110,7 @@ using namespace cryptonote; #define MULTISIG_EXPORT_FILE_MAGIC "Monero multisig export\001" -#define SEGREGATION_FORK_HEIGHT 1564965 +#define SEGREGATION_FORK_HEIGHT 1546000 #define TESTNET_SEGREGATION_FORK_HEIGHT 1000000 #define STAGENET_SEGREGATION_FORK_HEIGHT 1000000 #define SEGREGATION_FORK_VICINITY 1500 /* blocks */ @@ -141,13 +141,15 @@ 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 = { + const command_line::arg_descriptor<std::string, false, true, 2> 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) { - if (testnet) + {{ &testnet, &stagenet }}, + [](std::array<bool, 2> testnet_stagenet, bool defaulted, std::string val)->std::string { + if (testnet_stagenet[0]) return (boost::filesystem::path(val) / "testnet").string(); + else if (testnet_stagenet[1]) + return (boost::filesystem::path(val) / "stagenet").string(); return val; } }; @@ -653,6 +655,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), @@ -1007,13 +1010,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 @@ -1080,9 +1086,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; @@ -1119,8 +1131,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"); @@ -1140,6 +1154,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) { @@ -1161,16 +1176,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) @@ -1181,14 +1199,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 { @@ -1198,7 +1221,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(); } } } @@ -2010,6 +2037,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(); @@ -2395,7 +2423,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; @@ -2560,6 +2588,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()); @@ -2599,6 +2630,12 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable 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); @@ -2661,6 +2698,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; @@ -2669,6 +2707,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()) @@ -2761,6 +2804,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); @@ -2785,6 +2830,16 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ (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 { @@ -2830,6 +2885,7 @@ bool wallet2::verify_password(const epee::wipeable_string& password) const * \param keys_file_name Keys file to verify password for * \param password Password to verify * \param no_spend_key If set = only verify view keys, otherwise also spend keys + * \param hwdev The hardware device to use * \return true if password is correct * * for verification only @@ -2880,9 +2936,10 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip /*! * \brief Generates a wallet or restores one. - * \param wallet_ Name of wallet file - * \param password Password of wallet file - * \param multisig_data The multisig restore info and keys + * \param wallet_ Name of wallet file + * \param password Password of wallet file + * \param multisig_data The multisig restore info and keys + * \param create_address_file Whether to create an address file */ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& password, const std::string& multisig_data, bool create_address_file) @@ -2975,12 +3032,13 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& /*! * \brief Generates a wallet or restores one. - * \param wallet_ Name of wallet file - * \param password Password of wallet file - * \param recovery_param If it is a restore, the recovery key - * \param recover Whether it is a restore - * \param two_random Whether it is a non-deterministic wallet - * \return The secret key of the generated wallet + * \param wallet_ Name of wallet file + * \param password Password of wallet file + * \param recovery_param If it is a restore, the recovery key + * \param recover Whether it is a restore + * \param two_random Whether it is a non-deterministic wallet + * \param create_address_file Whether to create an address file + * \return The secret key of the generated wallet */ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wipeable_string& password, const crypto::secret_key& recovery_param, bool recover, bool two_random, bool create_address_file) @@ -3076,9 +3134,11 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip /*! * \brief Creates a watch only wallet from a public address and a view secret key. -* \param wallet_ Name of wallet file -* \param password Password of wallet file -* \param viewkey view secret key +* \param wallet_ Name of wallet file +* \param password Password of wallet file +* \param account_public_address The account's public address +* \param viewkey view secret key +* \param create_address_file Whether to create an address file */ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& password, const cryptonote::account_public_address &account_public_address, @@ -3125,10 +3185,12 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& /*! * \brief Creates a wallet from a public address and a spend/view secret key pair. -* \param wallet_ Name of wallet file -* \param password Password of wallet file -* \param spendkey spend secret key -* \param viewkey view secret key +* \param wallet_ Name of wallet file +* \param password Password of wallet file +* \param account_public_address The account's public address +* \param spendkey spend secret key +* \param viewkey view secret key +* \param create_address_file Whether to create an address file */ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& password, const cryptonote::account_public_address &account_public_address, @@ -3575,8 +3637,9 @@ void wallet2::rewrite(const std::string& wallet_name, const epee::wipeable_strin } /*! * \brief Writes to a file named based on the normal wallet (doesn't generate key, assumes it's already there) - * \param wallet_name Base name of wallet file - * \param password Password for wallet file + * \param wallet_name Base name of wallet file + * \param password Password for wallet file + * \param new_keys_filename [OUT] Name of new keys file */ void wallet2::write_watch_only_wallet(const std::string& wallet_name, const epee::wipeable_string& password, std::string &new_keys_filename) { @@ -3822,6 +3885,11 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass 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"); @@ -4955,7 +5023,7 @@ bool wallet2::save_multisig_tx(const multisig_tx_set &txs, const std::string &fi return epee::file_io_utils::save_string_to_file(filename, ciphertext); } //---------------------------------------------------------------------------------------------------- -std::string wallet2::save_multisig_tx(const std::vector<pending_tx>& ptx_vector) +wallet2::multisig_tx_set wallet2::make_multisig_tx_set(const std::vector<pending_tx>& ptx_vector) const { multisig_tx_set txs; txs.m_ptx = ptx_vector; @@ -4967,8 +5035,12 @@ std::string wallet2::save_multisig_tx(const std::vector<pending_tx>& ptx_vector) } txs.m_signers.insert(get_multisig_signer_public_key()); + return txs; +} - return save_multisig_tx(txs); +std::string wallet2::save_multisig_tx(const std::vector<pending_tx>& ptx_vector) +{ + return save_multisig_tx(make_multisig_tx_set(ptx_vector)); } //---------------------------------------------------------------------------------------------------- bool wallet2::save_multisig_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename) @@ -5097,7 +5169,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), @@ -5467,45 +5539,61 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto } } -void wallet2::set_ring_database(const std::string &filename) +bool wallet2::set_ring_database(const std::string &filename) { m_ring_database = filename; MINFO("ringdb path set to " << filename); m_ringdb.reset(); - cryptonote::block b; - generate_genesis(b); if (!m_ring_database.empty()) - m_ringdb.reset(new tools::ringdb(m_ring_database, epee::string_tools::pod_to_hex(get_block_hash(b)))); + { + 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 true; - return m_ringdb->add_rings(key, tx); + 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); - return add_rings(key, tx); + 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 true; + return false; crypto::chacha_key key; generate_chacha_key_from_secret_keys(key); - return m_ringdb->remove_rings(key, tx); + 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 true; - return m_ringdb->get_ring(key, key_image, outs); + 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) @@ -5536,18 +5624,20 @@ bool wallet2::get_ring(const crypto::key_image &key_image, std::vector<uint64_t> crypto::chacha_key key; generate_chacha_key_from_secret_keys(key); - return get_ring(key, key_image, outs); + 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 true; + return false; crypto::chacha_key key; generate_chacha_key_from_secret_keys(key); - return m_ringdb->set_ring(key, key_image, outs, relative); + 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) @@ -5555,7 +5645,7 @@ bool wallet2::find_and_save_rings(bool force) if (!force && m_ring_history_saved) return true; if (!m_ringdb) - return true; + return false; COMMAND_RPC_GET_TRANSACTIONS::request req = AUTO_VAL_INIT(req); COMMAND_RPC_GET_TRANSACTIONS::response res = AUTO_VAL_INIT(res); @@ -5563,39 +5653,49 @@ bool wallet2::find_and_save_rings(bool force) 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; - req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); + txs_hashes.push_back(txid); } - MDEBUG("Found " << std::to_string(req.txs_hashes.size()) << " transactions"); - - // get those transactions from the daemon - req.decode_as_json = false; - 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"); + MDEBUG("Found " << std::to_string(txs_hashes.size()) << " transactions"); crypto::chacha_key key; generate_chacha_key_from_secret_keys(key); - auto it = req.txs_hashes.begin(); - for (size_t i = 0; i < res.txs.size(); ++i, ++it) + // 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 = false; + 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"); @@ -5604,9 +5704,10 @@ bool wallet2::find_and_save_rings(bool force) 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 " << res.txs.size() << " transactions"); + MINFO("Found and saved rings for " << txs_hashes.size() << " transactions"); m_ring_history_saved = true; return true; } @@ -5614,34 +5715,41 @@ bool wallet2::find_and_save_rings(bool force) bool wallet2::blackball_output(const crypto::public_key &output) { if (!m_ringdb) - return true; - return m_ringdb->blackball(output); + return false; + try { return m_ringdb->blackball(output); } + catch (const std::exception &e) { return false; } } bool wallet2::set_blackballed_outputs(const std::vector<crypto::public_key> &outputs, bool add) { if (!m_ringdb) - return true; - bool ret = true; - if (!add) - ret &= m_ringdb->clear_blackballs(); - for (const auto &output: outputs) - ret &= m_ringdb->blackball(output); - return ret; + 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 true; - return m_ringdb->unblackball(output); + 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 true; - return m_ringdb->blackballed(output); + 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 @@ -5785,6 +5893,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> 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 cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request req_t = AUTO_VAL_INIT(req_t); @@ -5805,7 +5914,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> // 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 (m_segregate_pre_fork_outputs || m_key_reuse_mitigation2) + 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); @@ -5814,10 +5923,11 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> 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 = segregation_fork_height >= RECENT_OUTPUT_ZONE ? height >= (segregation_fork_height ? segregation_fork_height : height) - RECENT_OUTPUT_BLOCKS : 0; + 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); + 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"); @@ -5834,6 +5944,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> { 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]; @@ -5872,7 +5983,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> float pre_fork_num_out_ratio = 0.0f; float post_fork_num_out_ratio = 0.0f; - if (m_segregate_pre_fork_outputs && output_is_pre_fork) + if (is_after_segregation_fork && m_segregate_pre_fork_outputs && output_is_pre_fork) { num_outs = segregation_limit[amount].first; num_recent_outs = segregation_limit[amount].second; @@ -5892,7 +6003,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> break; } } - if (m_key_reuse_mitigation2) + if (is_after_segregation_fork && m_key_reuse_mitigation2) { if (output_is_pre_fork) { @@ -6107,7 +6218,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> 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 (m_segregate_pre_fork_outputs && output_is_pre_fork) + 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) { @@ -6573,7 +6684,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); @@ -7224,6 +7335,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(); @@ -7441,8 +7557,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(); @@ -7671,7 +7787,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; @@ -7802,6 +7918,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; @@ -7834,8 +7955,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(); @@ -7921,7 +8041,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; @@ -8036,6 +8156,7 @@ std::vector<size_t> wallet2::select_available_outputs_from_histogram(uint64_t co 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"); @@ -8072,6 +8193,8 @@ uint64_t wallet2::get_num_rct_outputs() 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"); @@ -8092,14 +8215,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); } //---------------------------------------------------------------------------------------------------- @@ -8155,6 +8278,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; { @@ -8274,6 +8398,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; { @@ -8396,6 +8521,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(); @@ -8532,6 +8659,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(); @@ -8642,6 +8771,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(); @@ -8875,6 +9006,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(); @@ -9449,6 +9582,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(); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index abc7bb538..5e0dd076b 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -477,21 +477,23 @@ namespace tools bool two_random = false, bool create_address_file = false); /*! * \brief Creates a wallet from a public address and a spend/view secret key pair. - * \param wallet_ Name of wallet file - * \param password Password of wallet file - * \param viewkey view secret key - * \param spendkey spend secret key - * \param create_address_file Whether to create an address file + * \param wallet_ Name of wallet file + * \param password Password of wallet file + * \param account_public_address The account's public address + * \param spendkey spend secret key + * \param viewkey view secret key + * \param create_address_file Whether to create an address file */ void generate(const std::string& wallet, const epee::wipeable_string& password, const cryptonote::account_public_address &account_public_address, const crypto::secret_key& spendkey, const crypto::secret_key& viewkey, bool create_address_file = false); /*! * \brief Creates a watch only wallet from a public address and a view secret key. - * \param wallet_ Name of wallet file - * \param password Password of wallet file - * \param viewkey view secret key - * \param create_address_file Whether to create an address file + * \param wallet_ Name of wallet file + * \param password Password of wallet file + * \param account_public_address The account's public address + * \param viewkey view secret key + * \param create_address_file Whether to create an address file */ void generate(const std::string& wallet, const epee::wipeable_string& password, const cryptonote::account_public_address &account_public_address, @@ -561,9 +563,9 @@ namespace tools void load(const std::string& wallet, const epee::wipeable_string& password); void store(); /*! - * \brief store_to - stores wallet to another file(s), deleting old ones - * \param path - path to the wallet file (keys and address filenames will be generated based on this filename) - * \param password - password to protect new wallet (TODO: probably better save the password in the wallet object?) + * \brief store_to Stores wallet to another file(s), deleting old ones + * \param path Path to the wallet file (keys and address filenames will be generated based on this filename) + * \param password Password to protect new wallet (TODO: probably better save the password in the wallet object?) */ void store_to(const std::string &path, const epee::wipeable_string &password); @@ -686,6 +688,7 @@ namespace tools bool save_multisig_tx(const multisig_tx_set &txs, const std::string &filename); std::string save_multisig_tx(const std::vector<pending_tx>& ptx_vector); bool save_multisig_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename); + multisig_tx_set make_multisig_tx_set(const std::vector<pending_tx>& ptx_vector) const; // load unsigned tx from file and sign it. Takes confirmation callback as argument. Used by the cli wallet bool sign_tx(const std::string &unsigned_filename, const std::string &signed_filename, std::vector<wallet2::pending_tx> &ptx, std::function<bool(const unsigned_tx_set&)> accept_func = NULL, bool export_raw = false); // sign unsigned tx. Takes unsigned_tx_set as argument. Used by GUI @@ -874,6 +877,8 @@ namespace tools 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); @@ -957,7 +962,7 @@ namespace tools /*! * \brief Set the label of the given tag. * \param tag Tag's name (which must be non-empty). - * \param label Tag's description. + * \param description Tag's description. */ void set_account_tag_description(const std::string& tag, const std::string& description); @@ -1061,7 +1066,7 @@ namespace tools return epee::net_utils::invoke_http_json_rpc(uri, method_name, req, res, m_http_client, timeout, http_method, req_id); } - void set_ring_database(const std::string &filename); + 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); @@ -1194,6 +1199,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; diff --git a/src/wallet/wallet_args.cpp b/src/wallet/wallet_args.cpp index a6ff63dd3..6311e7700 100644 --- a/src/wallet/wallet_args.cpp +++ b/src/wallet/wallet_args.cpp @@ -82,7 +82,7 @@ namespace wallet_args return i18n_translate(str, "wallet_args"); } - boost::optional<boost::program_options::variables_map> main( + std::pair<boost::optional<boost::program_options::variables_map>, bool> main( int argc, char** argv, const char* const usage, const char* const notice, @@ -127,6 +127,7 @@ namespace wallet_args po::options_description desc_all; desc_all.add(desc_general).add(desc_params); po::variables_map vm; + bool should_terminate = false; bool r = command_line::handle_error_helper(desc_all, [&]() { auto parser = po::command_line_parser(argc, argv).options(desc_all).positional(positional_options); @@ -139,12 +140,14 @@ namespace wallet_args "daemon to work correctly.") << ENDL; Print(print) << wallet_args::tr("Usage:") << ENDL << " " << usage; Print(print) << desc_all; - return false; + should_terminate = true; + return true; } else if (command_line::get_arg(vm, command_line::arg_version)) { Print(print) << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"; - return false; + should_terminate = true; + return true; } if(command_line::has_arg(vm, arg_config_file)) @@ -167,7 +170,10 @@ namespace wallet_args return true; }); if (!r) - return boost::none; + return {boost::none, true}; + + if (should_terminate) + return {std::move(vm), should_terminate}; std::string log_path; if (!command_line::is_arg_defaulted(vm, arg_log_file)) @@ -196,6 +202,6 @@ namespace wallet_args Print(print) << boost::format(wallet_args::tr("Logging to %s")) % log_path; - return {std::move(vm)}; + return {std::move(vm), should_terminate}; } } diff --git a/src/wallet/wallet_args.h b/src/wallet/wallet_args.h index af6685845..a1f251144 100644 --- a/src/wallet/wallet_args.h +++ b/src/wallet/wallet_args.h @@ -44,8 +44,11 @@ namespace wallet_args concurrency. Log file and concurrency arguments are handled, along with basic global init for the wallet process. - \return The list of parsed options, iff there are no errors.*/ - boost::optional<boost::program_options::variables_map> main( + \return + pair.first: The list of parsed options, iff there are no errors. + pair.second: Should the execution terminate succesfully without actually launching the application + */ + std::pair<boost::optional<boost::program_options::variables_map>, bool> main( int argc, char** argv, const char* const usage, const char* const notice, diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index a9d211532..dc1beef7b 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -2876,6 +2876,12 @@ namespace tools return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_get_version(const wallet_rpc::COMMAND_RPC_GET_VERSION::request& req, wallet_rpc::COMMAND_RPC_GET_VERSION::response& res, epee::json_rpc::error& er) + { + res.version = WALLET_RPC_VERSION; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ } int main(int argc, char** argv) { @@ -2895,7 +2901,9 @@ int main(int argc, char** argv) { command_line::add_arg(desc_params, arg_wallet_dir); command_line::add_arg(desc_params, arg_prompt_for_password); - const auto vm = wallet_args::main( + boost::optional<po::variables_map> vm; + bool should_terminate = false; + std::tie(vm, should_terminate) = wallet_args::main( argc, argv, "monero-wallet-rpc [--wallet-file=<file>|--generate-from-json=<file>|--wallet-dir=<directory>] [--rpc-bind-port=<port>]", tools::wallet_rpc_server::tr("This is the RPC monero wallet. It needs to connect to a monero\ndaemon to work correctly."), @@ -2909,6 +2917,10 @@ int main(int argc, char** argv) { { return 1; } + if (should_terminate) + { + return 0; + } std::unique_ptr<tools::wallet2> wal; try diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 2ec53cc80..cb1a274b6 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -135,6 +135,7 @@ namespace tools MAP_JON_RPC_WE("finalize_multisig", on_finalize_multisig, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG) MAP_JON_RPC_WE("sign_multisig", on_sign_multisig, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG) MAP_JON_RPC_WE("submit_multisig", on_submit_multisig, wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG) + MAP_JON_RPC_WE("get_version", on_get_version, wallet_rpc::COMMAND_RPC_GET_VERSION) END_JSON_RPC_MAP() END_URI_MAP2() @@ -203,6 +204,7 @@ namespace tools bool on_finalize_multisig(const wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::response& res, epee::json_rpc::error& er); bool on_sign_multisig(const wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::response& res, epee::json_rpc::error& er); bool on_submit_multisig(const wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::response& res, epee::json_rpc::error& er); + bool on_get_version(const wallet_rpc::COMMAND_RPC_GET_VERSION::request& req, wallet_rpc::COMMAND_RPC_GET_VERSION::response& res, epee::json_rpc::error& er); //json rpc v2 bool on_query_key(const wallet_rpc::COMMAND_RPC_QUERY_KEY::request& req, wallet_rpc::COMMAND_RPC_QUERY_KEY::response& res, epee::json_rpc::error& er); diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index a0f43c9b9..d44aa459f 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -39,6 +39,17 @@ #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "wallet.rpc" +// When making *any* change here, bump minor +// If the change is incompatible, then bump major and set minor to 0 +// This ensures WALLET_RPC_VERSION always increases, that every change +// has its own version, and that clients can just test major to see +// whether they can talk to a given wallet without having to know in +// advance which version they will stop working with +// Don't go over 32767 for any of these +#define WALLET_RPC_VERSION_MAJOR 1 +#define WALLET_RPC_VERSION_MINOR 0 +#define MAKE_WALLET_RPC_VERSION(major,minor) (((major)<<16)|(minor)) +#define WALLET_RPC_VERSION MAKE_WALLET_RPC_VERSION(WALLET_RPC_VERSION_MAJOR, WALLET_RPC_VERSION_MINOR) namespace tools { namespace wallet_rpc @@ -1848,5 +1859,23 @@ namespace wallet_rpc }; }; + struct COMMAND_RPC_GET_VERSION + { + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct response + { + uint32_t version; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(version) + END_KV_SERIALIZE_MAP() + }; + }; + } } |