diff --git a/src/wallet/api/CMakeLists.txt b/src/wallet/api/CMakeLists.txt index 3428de27a3..74b0d5306a 100644 --- a/src/wallet/api/CMakeLists.txt +++ b/src/wallet/api/CMakeLists.txt @@ -33,6 +33,7 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(wallet_api_sources wallet.cpp wallet_manager.cpp + enote_details.cpp transaction_info.cpp transaction_history.cpp pending_transaction.cpp @@ -48,6 +49,7 @@ set(wallet_api_headers set(wallet_api_private_headers wallet.h wallet_manager.h + enote_details.h transaction_info.h transaction_history.h pending_transaction.h diff --git a/src/wallet/api/enote_details.cpp b/src/wallet/api/enote_details.cpp new file mode 100644 index 0000000000..55576f6e11 --- /dev/null +++ b/src/wallet/api/enote_details.cpp @@ -0,0 +1,96 @@ +// Copyright (c) 2014-2024, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include "enote_details.h" + + +namespace Monero { + +EnoteDetails::~EnoteDetails() {} + + +EnoteDetailsImpl::EnoteDetailsImpl(): + m_block_height(0), + m_internal_enote_index(0), + m_global_enote_index(0), + m_spent(false), + m_frozen(false), + m_spent_height(0), + m_amount(0), + m_protocol_version(Tx_Protocol_CryptoNote), + m_key_image_known(false), + m_key_image_request(false), + m_pk_index(0), + m_key_image_partial(false) +{ +} + +EnoteDetailsImpl::~EnoteDetailsImpl() {} + +std::string EnoteDetailsImpl::onetimeAddress() const +{ return m_onetime_address; } +std::string EnoteDetailsImpl::viewTag() const +{ return m_view_tag; } +std::uint64_t EnoteDetailsImpl::blockHeight() const +{ return m_block_height; } +std::string EnoteDetailsImpl::txId() const +{ return m_tx_id; } +std::uint64_t EnoteDetailsImpl::internalEnoteIndex() const +{ return m_internal_enote_index; } +std::uint64_t EnoteDetailsImpl::globalEnoteIndex() const +{ return m_global_enote_index; } +bool EnoteDetailsImpl::isSpent() const +{ return m_spent; } +bool EnoteDetailsImpl::isFrozen() const +{ return m_frozen; } +std::uint64_t EnoteDetailsImpl::spentHeight() const +{ return m_spent_height; } +std::string EnoteDetailsImpl::keyImage() const +{ return m_key_image; } +std::string EnoteDetailsImpl::mask() const +{ return m_mask; } +std::uint64_t EnoteDetailsImpl::amount() const +{ return m_amount; } +EnoteDetails::TxProtocol EnoteDetailsImpl::protocolVersion() const +{ return m_protocol_version; } +bool EnoteDetailsImpl::isKeyImageKnown() const +{ return m_key_image_known; } +bool EnoteDetailsImpl::isKeyImageRequest() const +{ return m_key_image_request; } +std::uint64_t EnoteDetailsImpl::pkIndex() const +{ return m_pk_index; } +std::vector> EnoteDetailsImpl::uses() const +{ return m_uses; } + +// Multisig +bool EnoteDetailsImpl::isKeyImagePartial() const +{ return m_key_image_partial; } + +} // namespace diff --git a/src/wallet/api/enote_details.h b/src/wallet/api/enote_details.h new file mode 100644 index 0000000000..27a55d4b23 --- /dev/null +++ b/src/wallet/api/enote_details.h @@ -0,0 +1,109 @@ +// Copyright (c) 2014-2024, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers + +#include "wallet/api/wallet2_api.h" + + +namespace Monero { + +class EnoteDetailsImpl : public EnoteDetails +{ +public: + EnoteDetailsImpl(); + ~EnoteDetailsImpl() override; + std::string onetimeAddress() const override; + std::string viewTag() const override; + std::uint64_t blockHeight() const override; + std::string txId() const override; + std::uint64_t internalEnoteIndex() const override; + std::uint64_t globalEnoteIndex() const override; + bool isSpent() const override; + bool isFrozen() const override; + std::uint64_t spentHeight() const override; + std::string keyImage() const override; + std::string mask() const override; + std::uint64_t amount() const override; + TxProtocol protocolVersion() const override; + bool isKeyImageKnown() const override; + bool isKeyImageRequest() const override; + std::uint64_t pkIndex() const override; + std::vector> uses() const override; + + // Multisig + bool isKeyImagePartial() const override; + +private: + friend class WalletImpl; + + // Ko + std::string m_onetime_address; + // view_tag + std::string m_view_tag; + // this enote was received at block height + std::uint64_t m_block_height; + // tx id in which tx enote was received + std::string m_tx_id; + // relative index in tx + std::uint64_t m_internal_enote_index; + // absolute index from `cryptonote::COMMAND_RPC_GET_TRANSACTIONS::entry.output_indices` + std::uint64_t m_global_enote_index; + // is spent + bool m_spent; + // is frozen + bool m_frozen; + // blockchain height, set if spent + std::uint64_t m_spent_height; + // key image + std::string m_key_image; + // x, blinding factor in amount commitment C = x G + a H + std::string m_mask; + // a + std::uint64_t m_amount; + // protocol version : Tx_Protocol_CryptoNote / Tx_Protocol_RingCT + TxProtocol m_protocol_version; + // is key image known + bool m_key_image_known; + // view wallets: we want to request it; cold wallets: it was requested + bool m_key_image_request; + // public key index in tx_extra + std::uint64_t m_pk_index; + // track uses of this enote in the blockchain in the format [ [block_height, tx_id], ... ] if `wallet2::m_track_uses` is true (default is false) + std::vector> m_uses; + + // Multisig + bool m_key_image_partial; + // NOTE : These multisig members are part of wallet2 transfer_details and may need to get added here. +/* + std::vector m_multisig_k; + std::vector m_multisig_info; // one per other participant +*/ +}; + +} // namespace diff --git a/src/wallet/api/pending_transaction.h b/src/wallet/api/pending_transaction.h index 9d8d754c0c..f6eac46577 100644 --- a/src/wallet/api/pending_transaction.h +++ b/src/wallet/api/pending_transaction.h @@ -69,6 +69,8 @@ class PendingTransactionImpl : public PendingTransaction std::unordered_set m_signers; std::vector m_tx_device_aux; std::vector m_key_images; + // wallet2 m_cold_key_images + std::unordered_map m_tx_key_images; }; diff --git a/src/wallet/api/transaction_history.cpp b/src/wallet/api/transaction_history.cpp index 4dc869d5cb..16ab43d4e3 100644 --- a/src/wallet/api/transaction_history.cpp +++ b/src/wallet/api/transaction_history.cpp @@ -150,6 +150,11 @@ void TransactionHistoryImpl::refresh() ti->m_timestamp = pd.m_timestamp; ti->m_confirmations = (wallet_height > pd.m_block_height) ? wallet_height - pd.m_block_height : 0; ti->m_unlock_time = pd.m_unlock_time; + // not used for payment_details + ti->m_change = 0; + ti->m_tx_state = TransactionInfo::confirmed; + // not used for payment_details + ti->m_double_spend_seen = false; m_history.push_back(ti); } @@ -193,6 +198,11 @@ void TransactionHistoryImpl::refresh() ti->m_label = pd.m_subaddr_indices.size() == 1 ? m_wallet->m_wallet->get_subaddress_label({pd.m_subaddr_account, *pd.m_subaddr_indices.begin()}) : ""; ti->m_timestamp = pd.m_timestamp; ti->m_confirmations = (wallet_height > pd.m_block_height) ? wallet_height - pd.m_block_height : 0; + ti->m_unlock_time = pd.m_unlock_time; + ti->m_change = pd.m_change; + ti->m_tx_state = TransactionInfo::confirmed; + // not used for confirmed_transfer_details + ti->m_double_spend_seen = false; // single output transaction might contain multiple transfers for (const auto &d: pd.m_dests) { @@ -229,6 +239,11 @@ void TransactionHistoryImpl::refresh() ti->m_label = pd.m_subaddr_indices.size() == 1 ? m_wallet->m_wallet->get_subaddress_label({pd.m_subaddr_account, *pd.m_subaddr_indices.begin()}) : ""; ti->m_timestamp = pd.m_timestamp; ti->m_confirmations = 0; + ti->m_unlock_time = pd.m_tx.unlock_time; + ti->m_change = pd.m_change; + ti->m_tx_state = (TransactionInfo::TxState) pd.m_state; + // not used for unconfirmed_transfer_details + ti->m_double_spend_seen = false; for (const auto &d : pd.m_dests) { ti->m_transfers.push_back({d.amount, d.address(m_wallet->m_wallet->nettype(), pd.m_payment_id)}); @@ -258,6 +273,11 @@ void TransactionHistoryImpl::refresh() ti->m_label = m_wallet->m_wallet->get_subaddress_label(pd.m_subaddr_index); ti->m_timestamp = pd.m_timestamp; ti->m_confirmations = 0; + ti->m_unlock_time = pd.m_unlock_time; + // not used for pool_payment_details + ti->m_change = 0; + ti->m_tx_state = TransactionInfo::pending_in_pool; + ti->m_double_spend_seen = i->second.m_double_spend_seen; m_history.push_back(ti); LOG_PRINT_L1(__FUNCTION__ << ": Unconfirmed payment found " << pd.m_amount); diff --git a/src/wallet/api/transaction_info.cpp b/src/wallet/api/transaction_info.cpp index 909c770864..4e09f6ec8b 100644 --- a/src/wallet/api/transaction_info.cpp +++ b/src/wallet/api/transaction_info.cpp @@ -149,4 +149,19 @@ uint64_t TransactionInfoImpl::unlockTime() const return m_unlock_time; } +std::uint64_t TransactionInfoImpl::receivedChangeAmount() const +{ + return m_change; +} + +TransactionInfo::TxState TransactionInfoImpl::txState() const +{ + return m_tx_state; +} + +bool TransactionInfoImpl::isDoubleSpendSeen() const +{ + return m_double_spend_seen; +} + } // namespace diff --git a/src/wallet/api/transaction_info.h b/src/wallet/api/transaction_info.h index 0510fb216d..7daf10592a 100644 --- a/src/wallet/api/transaction_info.h +++ b/src/wallet/api/transaction_info.h @@ -63,6 +63,10 @@ class TransactionInfoImpl : public TransactionInfo virtual uint64_t confirmations() const override; virtual uint64_t unlockTime() const override; + std::uint64_t receivedChangeAmount() const override; + TxState txState() const override; + bool isDoubleSpendSeen() const override; + private: int m_direction; bool m_pending; @@ -81,6 +85,12 @@ class TransactionInfoImpl : public TransactionInfo std::vector m_transfers; uint64_t m_confirmations; uint64_t m_unlock_time; + // received change amount from outgoing transaction + std::uint64_t m_change; + // tx state : pending / pending_in_pool / failed / confirmed + TxState m_tx_state; + // is double spend seen + bool m_double_spend_seen; friend class TransactionHistoryImpl; diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 6c50002dd1..706eb5a2da 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -30,6 +30,7 @@ #include "wallet.h" +#include "enote_details.h" #include "pending_transaction.h" #include "unsigned_transaction.h" #include "transaction_history.h" @@ -61,7 +62,7 @@ using namespace cryptonote; #define LOCK_REFRESH() \ bool refresh_enabled = m_refreshEnabled; \ m_refreshEnabled = false; \ - m_wallet->stop(); \ + stop(); \ m_refreshCV.notify_one(); \ boost::mutex::scoped_lock lock(m_refreshMutex); \ boost::mutex::scoped_lock lock2(m_refreshMutex2); \ @@ -80,7 +81,7 @@ using namespace cryptonote; setStatusError(tr("HW wallet cannot use background sync")); \ return false; \ } \ - if (m_wallet->watch_only()) \ + if (watchOnly()) \ { \ setStatusError(tr("View only wallet cannot use background sync")); \ return false; \ @@ -294,6 +295,32 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback } } + virtual void on_reorg(std::uint64_t height, std::uint64_t blocks_detached, std::size_t transfers_detached) + { + if (m_listener) { + m_listener->onReorg(height, blocks_detached, transfers_detached); + } + } + + virtual boost::optional on_get_password(const char *reason) + { + if (m_listener) { + auto password = m_listener->onGetPassword(reason); + if (password) { + return boost::make_optional(epee::wipeable_string((*password).data(), (*password).size())); + } + } + return boost::none; + } + + virtual void on_pool_tx_removed(const crypto::hash &txid) + { + std::string txid_hex = epee::string_tools::pod_to_hex(txid); + if (m_listener) { + m_listener->onPoolTxRemoved(txid_hex); + } + } + WalletListener * m_listener; WalletImpl * m_wallet; }; @@ -397,6 +424,12 @@ uint64_t Wallet::maximumAllowedAmount() return std::numeric_limits::max(); } +bool Wallet::walletExists(const std::string &path, bool &key_file_exists, bool &wallet_file_exists) +{ + tools::wallet2::wallet_exists(path, key_file_exists, wallet_file_exists); + return (key_file_exists || wallet_file_exists); +} + 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 @@ -481,7 +514,7 @@ bool WalletImpl::create(const std::string &path, const std::string &password, co m_recoveringFromDevice = false; bool keys_file_exists; bool wallet_file_exists; - tools::wallet2::wallet_exists(path, keys_file_exists, wallet_file_exists); + Wallet::walletExists(path, keys_file_exists, wallet_file_exists); LOG_PRINT_L3("wallet_path: " << path << ""); LOG_PRINT_L3("keys_file_exists: " << std::boolalpha << keys_file_exists << std::noboolalpha << " wallet_file_exists: " << std::boolalpha << wallet_file_exists << std::noboolalpha); @@ -494,8 +527,9 @@ bool WalletImpl::create(const std::string &path, const std::string &password, co setStatusCritical(error); return false; } - // TODO: validate language - m_wallet->set_seed_language(language); + setSeedLanguage(language); + if (!statusOk()) + return false; crypto::secret_key recovery_val, secret_key; try { recovery_val = m_wallet->generate(path, password, secret_key, false, false); @@ -520,7 +554,7 @@ bool WalletImpl::createWatchOnly(const std::string &path, const std::string &pas bool keys_file_exists; bool wallet_file_exists; - tools::wallet2::wallet_exists(path, keys_file_exists, wallet_file_exists); + Wallet::walletExists(path, keys_file_exists, wallet_file_exists); LOG_PRINT_L3("wallet_path: " << path << ""); LOG_PRINT_L3("keys_file_exists: " << std::boolalpha << keys_file_exists << std::noboolalpha << " wallet_file_exists: " << std::boolalpha << wallet_file_exists << std::noboolalpha); @@ -670,6 +704,8 @@ bool WalletImpl::recoverFromKeysWithPassword(const std::string &path, if(has_spendkey && !has_viewkey) { m_wallet->generate(path, password, spendkey, true, false); setSeedLanguage(language); + if (!statusOk()) + return false; LOG_PRINT_L1("Generated deterministic wallet from spend key with seed language: " + language); } @@ -713,7 +749,7 @@ bool WalletImpl::open(const std::string &path, const std::string &password) // Check if wallet cache exists bool keys_file_exists; bool wallet_file_exists; - tools::wallet2::wallet_exists(path, keys_file_exists, wallet_file_exists); + Wallet::walletExists(path, keys_file_exists, wallet_file_exists); if(!wallet_file_exists){ // Rebuilding wallet cache, using refresh height from .keys file m_rebuildWalletCache = true; @@ -726,7 +762,7 @@ bool WalletImpl::open(const std::string &path, const std::string &password) LOG_ERROR("Error opening wallet: " << e.what()); setStatusCritical(e.what()); } - return status() == Status_Ok; + return statusOk(); } bool WalletImpl::recover(const std::string &path, const std::string &seed) @@ -761,32 +797,34 @@ bool WalletImpl::recover(const std::string &path, const std::string &password, c old_language = Language::English().get_language_name(); try { - m_wallet->set_seed_language(old_language); + setSeedLanguage(old_language); + if (!statusOk()) + return false; m_wallet->generate(path, password, recovery_key, true, false); } catch (const std::exception &e) { setStatusCritical(e.what()); } - return status() == Status_Ok; + return statusOk(); } -bool WalletImpl::close(bool store) +bool WalletImpl::close(bool do_store) { bool result = false; LOG_PRINT_L1("closing wallet..."); try { - if (store) { + if (do_store) { // Do not store wallet with invalid status // Status Critical refers to errors on opening or creating wallets. if (status() != Status_Critical) - m_wallet->store(); + store(""); else LOG_ERROR("Status_Critical - not saving wallet"); LOG_PRINT_L1("wallet::store done"); } LOG_PRINT_L1("Calling wallet::stop..."); - m_wallet->stop(); + stop(); LOG_PRINT_L1("wallet::stop done"); m_wallet->deinit(); result = true; @@ -817,7 +855,11 @@ void WalletImpl::setSeedLanguage(const std::string &arg) { if (checkBackgroundSync("cannot set seed language")) return; - m_wallet->set_seed_language(arg); + + if (crypto::ElectrumWords::is_valid_language(arg)) + m_wallet->set_seed_language(arg); + else + setStatusError(string(tr("Failed to set seed language. Language not valid: ")) + arg); } int WalletImpl::status() const @@ -849,7 +891,7 @@ bool WalletImpl::setPassword(const std::string &password) } catch (const std::exception &e) { setStatusError(e.what()); } - return status() == Status_Ok; + return statusOk(); } const std::string& WalletImpl::getPassword() const @@ -865,7 +907,7 @@ bool WalletImpl::setDevicePin(const std::string &pin) } catch (const std::exception &e) { setStatusError(e.what()); } - return status() == Status_Ok; + return statusOk(); } bool WalletImpl::setDevicePassphrase(const std::string &passphrase) @@ -876,7 +918,7 @@ bool WalletImpl::setDevicePassphrase(const std::string &passphrase) } catch (const std::exception &e) { setStatusError(e.what()); } - return status() == Status_Ok; + return statusOk(); } std::string WalletImpl::address(uint32_t accountIndex, uint32_t addressIndex) const @@ -1069,7 +1111,7 @@ bool WalletImpl::refresh() //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 status() == Status_Ok; + return statusOk(); } void WalletImpl::refreshAsync() @@ -1086,7 +1128,7 @@ bool WalletImpl::rescanBlockchain() clearStatus(); m_refreshShouldRescan = true; doRefresh(); - return status() == Status_Ok; + return statusOk(); } void WalletImpl::rescanBlockchainAsync() @@ -1142,7 +1184,7 @@ bool WalletImpl::submitTransaction(const string &fileName) { bool r = m_wallet->load_tx(fileName, transaction->m_pending_tx); if (!r) { - setStatus(Status_Ok, tr("Failed to load transaction from file")); + setStatusError(tr("Failed to load transaction from file")); return false; } @@ -1156,7 +1198,7 @@ bool WalletImpl::submitTransaction(const string &fileName) { bool WalletImpl::exportKeyImages(const string &filename, bool all) { - if (m_wallet->watch_only()) + if (watchOnly()) { setStatusError(tr("Wallet is view only")); return false; @@ -1218,8 +1260,8 @@ bool WalletImpl::exportOutputs(const string &filename, bool all) try { - std::string data = m_wallet->export_outputs_to_str(all); - bool r = m_wallet->save_to_file(filename, data); + std::string data = exportEnotesToStr(all); + bool r = saveToFile(filename, data); if (!r) { LOG_ERROR("Failed to save file " << filename); @@ -1249,7 +1291,7 @@ bool WalletImpl::importOutputs(const string &filename) } std::string data; - bool r = m_wallet->load_from_file(filename, data); + bool r = loadFromFile(filename, data); if (!r) { LOG_ERROR("Failed to read file: " << filename); @@ -1259,7 +1301,9 @@ bool WalletImpl::importOutputs(const string &filename) try { - size_t n_outputs = m_wallet->import_outputs_from_str(data); + size_t n_outputs = importEnotesFromStr(data); + if (!statusOk()) + throw runtime_error(errorString()); LOG_PRINT_L2(std::to_string(n_outputs) << " outputs imported"); } catch (const std::exception &e) @@ -1476,7 +1520,7 @@ string WalletImpl::makeMultisig(const vector& info, const uint32_t thres try { clearStatus(); - if (m_wallet->get_multisig_status().multisig_is_active) { + if (multisig().isMultisig) { throw runtime_error("Wallet is already multisig"); } @@ -1623,10 +1667,12 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vectoradjust_priority(static_cast(priority)); - PendingTransactionImpl * transaction = new PendingTransactionImpl(*this); + uint32_t adjusted_priority = adjustPriority(static_cast(priority)); + if (!statusOk()) + return transaction; + do { if (checkBackgroundSync("cannot create transactions")) break; @@ -1678,7 +1724,7 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vectorget_num_subaddresses(subaddr_account); ++index) + for (uint32_t index = 0; index < numSubaddresses(subaddr_account); ++index) subaddr_indices.insert(index); } } @@ -1691,7 +1737,7 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector 0 ? mixin_count : m_wallet->default_mixin(); + size_t fake_outs_count = mixin_count > 0 ? mixin_count : defaultMixin(); fake_outs_count = m_wallet->adjust_mixin(mixin_count); if (amount) { @@ -1707,11 +1753,9 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vectormake_multisig_tx_set(transaction->m_pending_tx); - transaction->m_pending_tx = tx_set.m_ptx; transaction->m_signers = tx_set.m_signers; } } catch (const tools::error::daemon_busy&) { - // TODO: make it translatable with "tr"? setStatusError(tr("daemon is busy. Please try again later.")); } catch (const tools::error::no_connection_to_daemon&) { setStatusError(tr("no connection to daemon. Please make sure daemon is running.")); @@ -1805,7 +1849,6 @@ PendingTransaction *WalletImpl::createSweepUnmixableTransaction() pendingTxPostProcess(transaction); } catch (const tools::error::daemon_busy&) { - // TODO: make it translatable with "tr"? setStatusError(tr("daemon is busy. Please try again later.")); } catch (const tools::error::no_connection_to_daemon&) { setStatusError(tr("no connection to daemon. Please make sure daemon is running.")); @@ -1886,16 +1929,16 @@ uint64_t WalletImpl::estimateTransactionFee(const std::vectorestimate_fee( - m_wallet->use_fork_rules(HF_VERSION_PER_BYTE_FEE, 0), - m_wallet->use_fork_rules(4, 0), + useForkRules(HF_VERSION_PER_BYTE_FEE, 0), + useForkRules(4, 0), 1, m_wallet->get_min_ring_size() - 1, destinations.size() + 1, extra_size, - m_wallet->use_fork_rules(8, 0), - m_wallet->use_fork_rules(HF_VERSION_CLSAG, 0), - m_wallet->use_fork_rules(HF_VERSION_BULLETPROOF_PLUS, 0), - m_wallet->use_fork_rules(HF_VERSION_VIEW_TAGS, 0), + useForkRules(8, 0), + useForkRules(HF_VERSION_CLSAG, 0), + useForkRules(HF_VERSION_BULLETPROOF_PLUS, 0), + useForkRules(HF_VERSION_VIEW_TAGS, 0), m_wallet->get_base_fee(priority), m_wallet->get_fee_quantization_mask()); } @@ -2218,13 +2261,15 @@ bool WalletImpl::checkReserveProof(const std::string &address, const std::string } } -std::string WalletImpl::signMessage(const std::string &message, const std::string &address) +std::string WalletImpl::signMessage(const std::string &message, const std::string &address, bool sign_with_view_key) { if (checkBackgroundSync("cannot sign message")) return ""; + tools::wallet2::message_signature_type_t sig_type = sign_with_view_key ? tools::wallet2::sign_with_view_key : tools::wallet2::sign_with_spend_key; + if (address.empty()) { - return m_wallet->sign(message, tools::wallet2::sign_with_spend_key); + return m_wallet->sign(message, sig_type); } cryptonote::address_parse_info info; @@ -2238,7 +2283,7 @@ std::string WalletImpl::signMessage(const std::string &message, const std::strin return ""; } - return m_wallet->sign(message, tools::wallet2::sign_with_spend_key, *index); + return m_wallet->sign(message, sig_type, *index); } bool WalletImpl::verifySignedMessage(const std::string &message, const std::string &address, const std::string &signature) const @@ -2514,11 +2559,11 @@ bool WalletImpl::doInit(const string &daemon_address, const std::string &proxy_a // If daemon isn't synced a calculated block height will be used instead if (isNewWallet() && daemonSynced()) { LOG_PRINT_L2(__FUNCTION__ << ":New Wallet - fast refresh until " << daemonBlockChainHeight()); - m_wallet->set_refresh_from_block_height(daemonBlockChainHeight()); + setRefreshFromBlockHeight(daemonBlockChainHeight()); } if (m_rebuildWalletCache) - LOG_PRINT_L2(__FUNCTION__ << ": Rebuilding wallet cache, fast refresh until block " << m_wallet->get_refresh_from_block_height()); + LOG_PRINT_L2(__FUNCTION__ << ": Rebuilding wallet cache, fast refresh until block " << getRefreshFromBlockHeight()); if (Utils::isAddressLocal(daemon_address)) { this->setTrustedDaemon(true); @@ -2599,8 +2644,19 @@ void WalletImpl::hardForkInfo(uint8_t &version, uint64_t &earliest_height) const bool WalletImpl::useForkRules(uint8_t version, int64_t early_blocks) const { - return m_wallet->use_fork_rules(version,early_blocks); + clearStatus(); + + try + { + return m_wallet->use_fork_rules(version, early_blocks); + } + catch (const std::exception &e) + { + setStatusError((boost::format(tr("Failed to check if fork rules for version `%u` with `%d` early blocks should be used: %s")) % version % early_blocks % e.what()).str()); + } + return false; } +//------------------------------------------------------------------------------------------------------------------- bool WalletImpl::blackballOutputs(const std::vector &outputs, bool add) { @@ -2819,4 +2875,853 @@ uint64_t WalletImpl::getBytesSent() return m_wallet->get_bytes_sent(); } +//------------------------------------------------------------------------------------------------------------------- +std::string WalletImpl::getMultisigSeed(const std::string &seed_offset) const +{ + clearStatus(); + + try + { + checkMultisigWalletReady(m_wallet); + + epee::wipeable_string seed; + if (m_wallet->get_multisig_seed(seed, seed_offset)) + return std::string(seed.data(), seed.size()); + } + catch (const std::exception &e) + { + LOG_ERROR(__FUNCTION__ << " error: " << e.what()); + setStatusError(string(tr("Failed to get multisig seed: ")) + e.what()); + } + return ""; +} +//------------------------------------------------------------------------------------------------------------------- +std::pair WalletImpl::getSubaddressIndex(const std::string &address) const +{ + clearStatus(); + + cryptonote::address_parse_info info; + std::pair indices{0, 0}; + if (!cryptonote::get_account_address_from_str(info, m_wallet->nettype(), address)) + { + setStatusError(string(tr("Failed to parse address: ") + address)); + return indices; + } + + auto index = m_wallet->get_subaddress_index(info.address); + if (!index) + setStatusError(string(tr("Address doesn't belong to the wallet: ") + address)); + else + indices = std::make_pair((*index).major, (*index).minor); + + return indices; +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::freeze(std::size_t idx) +{ + clearStatus(); + + try + { + m_wallet->freeze(idx); + } + catch (const std::exception &e) + { + LOG_ERROR(__FUNCTION__ << " error: " << e.what()); + setStatusError((boost::format(tr("Failed to freeze enote with index `%zu`: %s")) % idx % e.what()).str()); + } +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::freeze(const std::string &key_image) +{ + try + { + freeze(getEnoteIndex(key_image)); + } + catch (const std::exception &e) + { + LOG_ERROR(__FUNCTION__ << " error: " << e.what()); + setStatusError((boost::format(tr("Failed to freeze enote with key image `%s`: %s")) % key_image % e.what()).str()); + } +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::thaw(std::size_t idx) +{ + clearStatus(); + + try + { + m_wallet->thaw(idx); + } + catch (const std::exception &e) + { + LOG_ERROR(__FUNCTION__ << " error: " << e.what()); + setStatusError((boost::format(tr("Failed to thaw enote with index `%zu`: %s")) % idx % e.what()).str()); + } +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::thaw(const std::string &key_image) +{ + try + { + thaw(getEnoteIndex(key_image)); + } + catch (const std::exception &e) + { + LOG_ERROR(__FUNCTION__ << " error: " << e.what()); + setStatusError((boost::format(tr("Failed to thaw enote with key image `%s`: %s")) % key_image % e.what()).str()); + } +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::isFrozen(std::size_t idx) const +{ + clearStatus(); + + try + { + return m_wallet->frozen(idx); + } + catch (const std::exception &e) + { + LOG_ERROR(__FUNCTION__ << " error: " << e.what()); + setStatusError((boost::format(tr("Failed to determine if enote with index `%zu` is frozen: %s")) % idx % e.what()).str()); + } + + return false; +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::isFrozen(const std::string &key_image) const +{ + try + { + return isFrozen(getEnoteIndex(key_image)); + } + catch (const std::exception &e) + { + LOG_ERROR(__FUNCTION__ << " error: " << e.what()); + setStatusError((boost::format(tr("Failed to determine if enote with key image `%s` is frozen: %s")) % key_image % e.what()).str()); + } + + return false; +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::createOneOffSubaddress(std::uint32_t account_index, std::uint32_t address_index) +{ + m_wallet->create_one_off_subaddress({account_index, address_index}); +} +//------------------------------------------------------------------------------------------------------------------- +Wallet::WalletState WalletImpl::getWalletState() const +{ + WalletState wallet_state{}; + + wallet_state.is_deprecated = m_wallet->is_deprecated(); + wallet_state.ring_size = 16; + wallet_state.daemon_address = m_wallet->get_daemon_address(); + + return wallet_state; +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::rewriteWalletFile(const std::string &wallet_name, const std::string &password) +{ + clearStatus(); + + try + { + m_wallet->rewrite(wallet_name, epee::wipeable_string(password.data(), password.size())); + } + catch (const std::exception &e) + { + LOG_ERROR(__FUNCTION__ << " error: " << e.what()); + setStatusError((boost::format(tr("Failed to rewrite wallet file with wallet name `%s`: %s")) % wallet_name % e.what()).str()); + } +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::writeWatchOnlyWallet(const std::string &password, std::string &new_keys_file_name) +{ + clearStatus(); + + try + { + m_wallet->write_watch_only_wallet(m_wallet->get_wallet_file(), epee::wipeable_string(password.data(), password.size()), new_keys_file_name); + } + catch (const std::exception &e) + { + LOG_ERROR(__FUNCTION__ << " error: " << e.what()); + setStatusError(string(tr("Failed to write watch only wallet: ")) + e.what()); + } +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::refreshPoolOnly(bool refreshed /*false*/, bool try_incremental /*false*/) +{ + clearStatus(); + + // Update pool state + std::vector> process_txs_pod; + try + { + m_wallet->update_pool_state(process_txs_pod, refreshed, try_incremental); + } + catch (const std::exception &e) + { + LOG_ERROR(__FUNCTION__ << " error: " << e.what()); + setStatusError(string(tr("Failed to update pool state: ")) + e.what()); + return; + } + + if (process_txs_pod.empty()) + return; + + // Process pool state + try + { + m_wallet->process_pool_state(process_txs_pod); + } + catch (const std::exception &e) + { + LOG_ERROR(__FUNCTION__ << " error: " << e.what()); + setStatusError(string(tr("Failed to process pool state: ")) + e.what()); + } +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::getEnoteDetails(std::vector> &enote_details) const +{ + tools::wallet2::transfer_container tc; + m_wallet->get_transfers(tc); + enote_details.reserve(tc.size()); + + for (const auto &td : tc) + { + auto ed = std::make_unique(); + + cryptonote::txout_target_v txout_v = td.m_tx.vout[td.m_internal_output_index].target; + if (txout_v.type() == typeid(cryptonote::txout_to_key)) + ed->m_onetime_address = epee::string_tools::pod_to_hex(boost::get(txout_v).key); + else if (txout_v.type() == typeid(cryptonote::txout_to_tagged_key)) + { + ed->m_onetime_address = epee::string_tools::pod_to_hex(boost::get(txout_v).key); + ed->m_view_tag = epee::string_tools::pod_to_hex(boost::get(txout_v).view_tag); + } + + ed->m_block_height = td.m_block_height; + ed->m_tx_id = epee::string_tools::pod_to_hex(td.m_txid); + ed->m_internal_enote_index = td.m_internal_output_index; + ed->m_global_enote_index = td.m_global_output_index; + ed->m_spent = td.m_spent; + ed->m_frozen = td.m_frozen; + ed->m_spent_height = td.m_spent_height; + ed->m_key_image = epee::string_tools::pod_to_hex(td.m_key_image); + ed->m_mask = epee::string_tools::pod_to_hex(td.m_mask); + ed->m_amount = td.m_amount; + ed->m_protocol_version = td.m_rct ? EnoteDetails::Tx_Protocol_RingCT : EnoteDetails::Tx_Protocol_CryptoNote; + ed->m_key_image_known = td.m_key_image_known; + ed->m_key_image_request = td.m_key_image_request; + ed->m_pk_index = td.m_pk_index; + ed->m_uses.reserve(td.m_uses.size()); + for (auto &u : td.m_uses) + ed->m_uses.push_back(std::make_pair(u.first, epee::string_tools::pod_to_hex(u.second))); + ed->m_key_image_partial = td.m_key_image_partial; + + enote_details.push_back(std::move(ed)); + } +} +//------------------------------------------------------------------------------------------------------------------- +std::string WalletImpl::convertMultisigTxToStr(const PendingTransaction &multisig_ptx) const +{ + clearStatus(); + + try + { + checkMultisigWalletReady(m_wallet); + + const PendingTransactionImpl *ptx_impl = dynamic_cast(&multisig_ptx); + + tools::wallet2::multisig_tx_set multisig_tx_set; + multisig_tx_set.m_ptx = ptx_impl->m_pending_tx; + multisig_tx_set.m_signers = ptx_impl->m_signers; + + return m_wallet->save_multisig_tx(multisig_tx_set); + } + catch (const exception &e) + { + setStatusError(string(tr("Failed to convert pending multisig tx to string: ")) + e.what()); + } + + return ""; +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::saveMultisigTx(const PendingTransaction &multisig_ptx, const std::string &filename) const +{ + clearStatus(); + + try + { + checkMultisigWalletReady(m_wallet); + + const PendingTransactionImpl *ptx_impl = dynamic_cast(&multisig_ptx); + + tools::wallet2::multisig_tx_set multisig_tx_set; + multisig_tx_set.m_ptx = ptx_impl->m_pending_tx; + multisig_tx_set.m_signers = ptx_impl->m_signers; + + return m_wallet->save_multisig_tx(multisig_tx_set, filename); + } + catch (const exception &e) + { + setStatusError((boost::format(tr("Failed to save multisig tx to file with filename `%s`: %s")) % filename % e.what()).str()); + } + + return false; +} +//------------------------------------------------------------------------------------------------------------------- +std::string WalletImpl::convertTxToStr(const PendingTransaction &ptxs) const +{ + clearStatus(); + + const PendingTransactionImpl *ptx_impl = dynamic_cast(&ptxs); + std::string tx_dump = m_wallet->dump_tx_to_str(ptx_impl->m_pending_tx); + if (tx_dump.empty()) + setStatusError(tr("Failed to convert pending tx to string")); + + return tx_dump; +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::parseUnsignedTxFromStr(const std::string &unsigned_tx_str, UnsignedTransaction &exported_txs) const +{ + UnsignedTransactionImpl *utx_impl = dynamic_cast(&exported_txs); + return m_wallet->parse_unsigned_tx_from_str(unsigned_tx_str, utx_impl->m_unsigned_tx_set); +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::parseTxFromStr(const std::string &signed_tx_str, PendingTransaction &ptx) const +{ + PendingTransactionImpl *ptx_impl = dynamic_cast(&ptx); + std::shared_ptr signed_tx_set_out = std::make_shared(); + bool r = m_wallet->parse_tx_from_str(signed_tx_str, ptx_impl->m_pending_tx, /* accept_func = */ NULL, signed_tx_set_out.get(), /* do_handle_key_images = */ false); + if (r) + ptx_impl->m_tx_key_images = signed_tx_set_out->tx_key_images; + return r; +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::insertColdKeyImages(PendingTransaction &ptx) +{ + m_wallet->insert_cold_key_images(dynamic_cast(&ptx)->m_tx_key_images); +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::parseMultisigTxFromStr(const std::string &multisig_tx_str, PendingTransaction &exported_txs) const +{ + clearStatus(); + + try + { + checkMultisigWalletReady(m_wallet); + + PendingTransactionImpl *ptx_impl = dynamic_cast(&exported_txs); + tools::wallet2::multisig_tx_set multisig_tx; + + if (!m_wallet->parse_multisig_tx_from_str(multisig_tx_str, multisig_tx)) + throw runtime_error(tr("Failed to parse multisig transaction from string.")); + + ptx_impl->m_pending_tx = multisig_tx.m_ptx; + ptx_impl->m_signers = multisig_tx.m_signers; + + return true; + } + catch (const exception &e) + { + setStatusError(e.what()); + } + + return false; +} +//------------------------------------------------------------------------------------------------------------------- +std::uint64_t WalletImpl::getFeeMultiplier(std::uint32_t priority, int fee_algorithm) const +{ + clearStatus(); + + try + { + return m_wallet->get_fee_multiplier(priority, fee_algorithm); + } + catch (const exception &e) + { + setStatusError((boost::format(tr("Failed to get fee multiplier for priority `%u` and fee algorithm `%d`: %s")) % priority % fee_algorithm % e.what()).str()); + } + return 0; +} +//------------------------------------------------------------------------------------------------------------------- +std::uint64_t WalletImpl::getBaseFee() const +{ + return m_wallet->get_base_fee(); +} +//------------------------------------------------------------------------------------------------------------------- +std::uint32_t WalletImpl::adjustPriority(std::uint32_t priority) +{ + return m_wallet->adjust_priority(priority); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::coldTxAuxImport(const PendingTransaction &ptx, const std::vector &tx_device_aux) const +{ + clearStatus(); + + const PendingTransactionImpl *ptx_impl = dynamic_cast(&ptx); + + try + { + m_wallet->cold_tx_aux_import(ptx_impl->m_pending_tx, tx_device_aux); + } + catch (const std::exception &e) + { + setStatusError(string(tr("Failed to import cold tx aux: ")) + e.what()); + } +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::coldSignTx(const PendingTransaction &ptx_in, PendingTransaction &exported_txs_out) const +{ + clearStatus(); + + try + { + const PendingTransactionImpl *ptx_impl_in = dynamic_cast(&ptx_in); + PendingTransactionImpl *ptx_impl_out = dynamic_cast(&exported_txs_out); + tools::wallet2::signed_tx_set signed_txs; + std::vector dsts_info; + + m_wallet->cold_sign_tx(ptx_impl_in->m_pending_tx, signed_txs, dsts_info, ptx_impl_out->m_tx_device_aux); + ptx_impl_out->m_key_images = signed_txs.key_images; + ptx_impl_out->m_pending_tx = signed_txs.ptx; + ptx_impl_out->m_tx_key_images = signed_txs.tx_key_images; + } + catch (const std::exception &e) + { + setStatusError(string(tr("Failed to cold sign tx: ")) + e.what()); + } +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::discardUnmixableEnotes() +{ + clearStatus(); + + try + { + m_wallet->discard_unmixable_outputs(); + } + catch (const std::exception &e) + { + setStatusError(string(tr("Failed to discard unmixable enotes: ")) + e.what()); + } +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setTxKey(const std::string &txid, const std::string &tx_key, const std::vector &additional_tx_keys, const std::string &single_destination_subaddress) +{ + clearStatus(); + + crypto::hash txid_pod; + if (!epee::string_tools::hex_to_pod(txid, txid_pod)) + { + setStatusError(string(tr("Failed to parse tx id: ")) + txid); + return; + } + + crypto::secret_key tx_key_pod; + if (!epee::string_tools::hex_to_pod(tx_key, tx_key_pod)) + { + setStatusError(tr("Failed to parse tx key")); + return; + } + + std::vector additional_tx_keys_pod; + crypto::secret_key tmp_additional_tx_key_pod; + additional_tx_keys_pod.reserve(additional_tx_keys.size()); + for (std::string additional_tx_key : additional_tx_keys) + { + if (!epee::string_tools::hex_to_pod(additional_tx_key, tmp_additional_tx_key_pod)) + { + setStatusError(string(tr("Failed to parse additional tx key: ")) + additional_tx_key); + return; + } + additional_tx_keys_pod.push_back(tmp_additional_tx_key_pod); + } + + boost::optional single_destination_subaddress_pod = boost::none; + cryptonote::address_parse_info info; + if (!single_destination_subaddress.empty()) + { + if (!cryptonote::get_account_address_from_str(info, m_wallet->nettype(), single_destination_subaddress)) + { + setStatusError(string(tr("Failed to get account address from string: ")) + single_destination_subaddress); + return; + } + single_destination_subaddress_pod = info.address; + } + + try + { + m_wallet->set_tx_key(txid_pod, tx_key_pod, additional_tx_keys_pod, info.address); + } + catch (const std::exception &e) + { + setStatusError(string(tr("Failed to set tx key: ")) + e.what()); + } +} +//------------------------------------------------------------------------------------------------------------------- +const std::pair, std::vector>& WalletImpl::getAccountTags() const +{ + return m_wallet->get_account_tags(); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setAccountTag(const std::set &account_indices, const std::string &tag) +{ + clearStatus(); + + try + { + m_wallet->set_account_tag(account_indices, tag); + } + catch (const std::exception &e) + { + setStatusError(string(tr("Failed to set account tag: ")) + e.what()); + } +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setAccountTagDescription(const std::string &tag, const std::string &description) +{ + clearStatus(); + + try + { + m_wallet->set_account_tag_description(tag, description); + } + catch (const std::exception &e) + { + setStatusError(string(tr("Failed to set account tag description: ")) + e.what()); + } +} +//------------------------------------------------------------------------------------------------------------------- +std::string WalletImpl::exportEnotesToStr(bool all, std::uint32_t start, std::uint32_t count) const +{ + clearStatus(); + + try + { + return m_wallet->export_outputs_to_str(all, start, count); + } + catch (const std::exception &e) + { + setStatusError(string(tr("Failed to export enotes to string: ")) + e.what()); + } + return ""; +} +//------------------------------------------------------------------------------------------------------------------- +std::size_t WalletImpl::importEnotesFromStr(const std::string &enotes_str) +{ + clearStatus(); + + try + { + return m_wallet->import_outputs_from_str(enotes_str); + } + catch (const std::exception &e) + { + setStatusError(string(tr("Failed to import enotes to string: ")) + e.what()); + } + return 0; +} +//------------------------------------------------------------------------------------------------------------------- +std::uint64_t WalletImpl::getBlockchainHeightByDate(std::uint16_t year, std::uint8_t month, std::uint8_t day) const +{ + clearStatus(); + + try + { + return m_wallet->get_blockchain_height_by_date(year, month, day); + } + catch (const std::exception &e) + { + setStatusError(string(tr("Failed to get blockchain height by date: ")) + e.what()); + } + return 0; +} +//------------------------------------------------------------------------------------------------------------------- +std::vector> WalletImpl::estimateBacklog(const std::vector> &fee_levels) const +{ + clearStatus(); + + try + { + return m_wallet->estimate_backlog(fee_levels); + } + catch (const std::exception &e) + { + setStatusError(string(tr("Failed to estimate backlog: ")) + e.what()); + } + return { std::make_pair(0, 0) }; +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::saveToFile(const std::string &path_to_file, const std::string &binary, bool is_printable /* = false */) const +{ + return m_wallet->save_to_file(path_to_file, binary, is_printable); +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::loadFromFile(const std::string &path_to_file, std::string &target_str, std::size_t max_size /* = 1000000000 */) const +{ + return m_wallet->load_from_file(path_to_file, target_str, max_size); +} +//------------------------------------------------------------------------------------------------------------------- +std::uint64_t WalletImpl::hashEnotes(std::uint64_t enote_idx, std::string &hash) const +{ + clearStatus(); + + try + { + crypto::hash hash_pod; + boost::optional idx = enote_idx == 0 ? boost::none : boost::optional(enote_idx); + std::uint64_t current_height = m_wallet->hash_m_transfers(idx, hash_pod); + hash = epee::string_tools::pod_to_hex(hash_pod); + return current_height; + } + catch (const std::exception &e) + { + setStatusError(string(tr("Failed to hash enotes: ")) + e.what()); + } + return 0; +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::finishRescanBcKeepKeyImages(std::uint64_t enote_idx, const std::string &hash) +{ + clearStatus(); + + try + { + crypto::hash hash_pod; + if (!epee::string_tools::hex_to_pod(hash, hash_pod)) + setStatusError(tr("Failed to parse hash")); + else + m_wallet->finish_rescan_bc_keep_key_images(enote_idx, hash_pod); + } + catch (const std::exception &e) + { + setStatusError(string(tr("Failed to finish rescan blockchain: ")) + e.what()); + } +} +//------------------------------------------------------------------------------------------------------------------- +std::vector> WalletImpl::getPublicNodes(bool white_only /* = true */) const +{ + clearStatus(); + + std::vector public_nodes; + std::vector> public_nodes_out; + try + { + public_nodes = m_wallet->get_public_nodes(white_only); + } + catch (const std::exception &e) + { + setStatusError(string(tr("Failed to get public nodes: ")) + e.what()); + return std::vector>{}; + } + + for (auto pub_node : public_nodes) + public_nodes_out.push_back(std::tuple{pub_node.host, pub_node.rpc_port, pub_node.last_seen}); + + return public_nodes_out; +} +//------------------------------------------------------------------------------------------------------------------- +std::pair WalletImpl::estimateTxSizeAndWeight(bool use_rct, int n_inputs, int ring_size, int n_outputs, std::size_t extra_size) const +{ + clearStatus(); + + try + { + return m_wallet->estimate_tx_size_and_weight(use_rct, n_inputs, ring_size, n_outputs, extra_size); + } + catch (const std::exception &e) + { + setStatusError(string(tr("Failed to estimate transaction size and weight: ")) + e.what()); + } + return std::make_pair(0, 0); +} +//------------------------------------------------------------------------------------------------------------------- +std::uint64_t WalletImpl::importKeyImages(const std::vector> &signed_key_images, std::size_t offset, std::uint64_t &spent, std::uint64_t &unspent, bool check_spent) +{ + clearStatus(); + + std::vector> signed_key_images_pod; + crypto::key_image tmp_key_image_pod; + crypto::signature tmp_signature_pod{}; + size_t sig_size = sizeof(crypto::signature); + + signed_key_images_pod.reserve(signed_key_images.size()); + + for (auto ski : signed_key_images) + { + if (!epee::string_tools::hex_to_pod(ski.first, tmp_key_image_pod)) + { + setStatusError(string(tr("Failed to parse key image: ")) + ski.first); + return false; + } + if (!epee::string_tools::hex_to_pod(ski.second.substr(0, sig_size/2), tmp_signature_pod.c)) + { + setStatusError(string(tr("Failed to parse signature.c: ")) + ski.second.substr(0, sig_size/2)); + return false; + } + if (!epee::string_tools::hex_to_pod(ski.second.substr(sig_size/2, sig_size), tmp_signature_pod.r)) + { + setStatusError(string(tr("Failed to parse signature.r: ")) + ski.second.substr(sig_size/2, sig_size)); + return false; + } + signed_key_images_pod.push_back(std::make_pair(tmp_key_image_pod, tmp_signature_pod)); + } + + try + { + return m_wallet->import_key_images(signed_key_images_pod, offset, spent, unspent, check_spent); + } + catch (const std::exception &e) + { + setStatusError(string(tr("Failed to import key images: ")) + e.what()); + } + return false; +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::importKeyImages(const std::vector &key_images, std::size_t offset, const std::unordered_set &selected_enotes_indices) +{ + clearStatus(); + + std::vector key_images_pod; + crypto::key_image tmp_key_image_pod; + key_images_pod.reserve(key_images.size()); + for (std::string key_image : key_images) + { + if (!epee::string_tools::hex_to_pod(key_image, tmp_key_image_pod)) + { + setStatusError(string(tr("Failed to parse key image: ")) + key_image); + return false; + } + key_images_pod.push_back(tmp_key_image_pod); + } + + try + { + boost::optional> optional_selected_enote_indices = selected_enotes_indices.empty() ? boost::none : boost::optional>(selected_enotes_indices); + return m_wallet->import_key_images(key_images_pod, offset, optional_selected_enote_indices); + } + catch (const std::exception &e) + { + setStatusError(string(tr("Failed to import key images: ")) + e.what()); + } + return false; +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::getAllowMismatchedDaemonVersion() const +{ + return m_wallet->is_mismatched_daemon_version_allowed(); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setAllowMismatchedDaemonVersion(bool allow_mismatch) +{ + m_wallet->allow_mismatched_daemon_version(allow_mismatch); +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::setDaemon(const std::string &daemon_address, + const std::string &daemon_username /* = "" */, + const std::string &daemon_password /* = "" */, + bool trusted_daemon /* = true */, + const std::string &ssl_support /* = "autodetect" */, + const std::string &ssl_private_key_path /* = "" */, + const std::string &ssl_certificate_path /* = "" */, + const std::string &ssl_ca_file_path /* = "" */, + const std::vector &ssl_allowed_fingerprints_str /* = {} */, + bool ssl_allow_any_cert /* = false */, + const std::string &proxy /* = "" */) +{ + clearStatus(); + + // SSL allowed fingerprints + std::vector> ssl_allowed_fingerprints; + ssl_allowed_fingerprints.reserve(ssl_allowed_fingerprints_str.size()); + for (const std::string &fp: ssl_allowed_fingerprints_str) + { + ssl_allowed_fingerprints.push_back({}); + std::vector &v = ssl_allowed_fingerprints.back(); + for (auto c: fp) + v.push_back(c); + } + + // SSL options + epee::net_utils::ssl_options_t ssl_options = epee::net_utils::ssl_support_t::e_ssl_support_enabled; + if (ssl_allow_any_cert) + ssl_options.verification = epee::net_utils::ssl_verification_t::none; + else if (!ssl_allowed_fingerprints.empty() || !ssl_ca_file_path.empty()) + ssl_options = epee::net_utils::ssl_options_t{std::move(ssl_allowed_fingerprints), std::move(ssl_ca_file_path)}; + + if (!epee::net_utils::ssl_support_from_string(ssl_options.support, ssl_support)) + { + setStatusError(string(tr("Invalid ssl support option (allowed options:`disabled` | `enabled` | `autodetect`), actual value: ")) + ssl_support); + return false; + } + + ssl_options.auth = epee::net_utils::ssl_authentication_t{ + std::move(ssl_private_key_path), std::move(ssl_certificate_path) + }; + + const bool verification_required = + ssl_options.verification != epee::net_utils::ssl_verification_t::none && + ssl_options.support == epee::net_utils::ssl_support_t::e_ssl_support_enabled; + + if (verification_required && !ssl_options.has_strong_verification(boost::string_ref{})) + { + setStatusError(string(tr("SSL is enabled but no user certificate or fingerprints were provided"))); + return false; + } + + // daemon login + if(daemon_username != "") + m_daemon_login.emplace(daemon_username, daemon_password); + + // set daemon + try + { + return m_wallet->set_daemon(daemon_address, m_daemon_login, trusted_daemon, ssl_options, proxy); + } + catch (const std::exception &e) + { + setStatusError(string(tr("Failed to set daemon: ")) + e.what()); + } + return false; +} +//------------------------------------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------------------------------------- +// PRIVATE +//------------------------------------------------------------------------------------------------------------------- +std::size_t WalletImpl::getEnoteIndex(const std::string &key_image) const +{ + std::vector> enote_details; + getEnoteDetails(enote_details); + for (size_t idx = 0; idx < enote_details.size(); ++idx) + { + const auto &ed = dynamic_cast(*enote_details[idx].get()); + if (ed.m_key_image == key_image) + { + if (ed.m_key_image_known) + return idx; + else if (ed.m_key_image_partial) + { + setStatusError("Failed to get enote index by key image: Enote detail lookups are not allowed for multisig partial key images"); + return 0; + } + } + } + + setStatusError("Failed to get enote index by key image: Key image not found"); + return 0; +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::statusOk() const +{ + boost::lock_guard l(m_statusMutex); + return m_status == Status_Ok; +} +//------------------------------------------------------------------------------------------------------------------- + + } // namespace diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index d48d7f130e..9d682f7d16 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -42,6 +42,7 @@ class WalletApiAccessorTest; namespace Monero { +class EnoteDetailsImpl; class TransactionHistoryImpl; class PendingTransactionImpl; class UnsignedTransactionImpl; @@ -207,7 +208,7 @@ class WalletImpl : public Wallet virtual bool checkSpendProof(const std::string &txid, const std::string &message, const std::string &signature, bool &good) const override; virtual std::string getReserveProof(bool all, uint32_t account_index, uint64_t amount, const std::string &message) const override; virtual bool checkReserveProof(const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &total, uint64_t &spent) const override; - virtual std::string signMessage(const std::string &message, const std::string &address) override; + virtual std::string signMessage(const std::string &message, const std::string &address, bool sign_with_view_key = false) override; virtual bool verifySignedMessage(const std::string &message, const std::string &address, const std::string &signature) const override; virtual std::string signMultisigParticipant(const std::string &message) const override; virtual bool verifyMessageWithPublicKey(const std::string &message, const std::string &publicKey, const std::string &signature) const override; @@ -234,6 +235,53 @@ class WalletImpl : public Wallet virtual uint64_t getBytesReceived() override; virtual uint64_t getBytesSent() override; + std::string getMultisigSeed(const std::string &seed_offset) const override; + std::pair getSubaddressIndex(const std::string &address) const override; + void freeze(std::size_t idx) override; + void freeze(const std::string &key_image) override; + void thaw(std::size_t idx) override; + void thaw(const std::string &key_image) override; + bool isFrozen(std::size_t idx) const override; + bool isFrozen(const std::string &key_image) const override; + void createOneOffSubaddress(std::uint32_t account_index, std::uint32_t address_index) override; + WalletState getWalletState() const override; + void rewriteWalletFile(const std::string &wallet_name, const std::string &password) override; + void writeWatchOnlyWallet(const std::string &password, std::string &new_keys_file_name) override; + void refreshPoolOnly(bool refreshed = false, bool try_incremental = false) override; + void getEnoteDetails(std::vector> &enote_details) const override; + std::string convertMultisigTxToStr(const PendingTransaction &multisig_ptx) const override; + bool saveMultisigTx(const PendingTransaction &multisig_ptx, const std::string &filename) const override; + std::string convertTxToStr(const PendingTransaction &ptxs) const override; + bool parseUnsignedTxFromStr(const std::string &unsigned_tx_str, UnsignedTransaction &exported_txs) const override; + bool parseTxFromStr(const std::string &signed_tx_str, PendingTransaction &ptx) const override; + void insertColdKeyImages(PendingTransaction &ptx) override; + bool parseMultisigTxFromStr(const std::string &multisig_tx_str, PendingTransaction &exported_txs) const override; + std::uint64_t getFeeMultiplier(std::uint32_t priority, int fee_algorithm) const override; + std::uint64_t getBaseFee() const override; + std::uint32_t adjustPriority(std::uint32_t priority) override; + void coldTxAuxImport(const PendingTransaction &ptx, const std::vector &tx_device_aux) const override; + void coldSignTx(const PendingTransaction &ptx_in, PendingTransaction &exported_txs_out) const override; + void discardUnmixableEnotes() override; + void setTxKey(const std::string &txid, const std::string &tx_key, const std::vector &additional_tx_keys, const std::string &single_destination_subaddress) override; + const std::pair, std::vector>& getAccountTags() const override; + void setAccountTag(const std::set &account_indices, const std::string &tag) override; + void setAccountTagDescription(const std::string &tag, const std::string &description) override; + std::string exportEnotesToStr(bool all = false, std::uint32_t start = 0, std::uint32_t count = 0xffffffff) const override; + std::size_t importEnotesFromStr(const std::string &enotes_str) override; + std::uint64_t getBlockchainHeightByDate(std::uint16_t year, std::uint8_t month, std::uint8_t day) const override; + std::vector> estimateBacklog(const std::vector> &fee_levels) const override; + bool saveToFile(const std::string &path_to_file, const std::string &binary, bool is_printable = false) const override; + bool loadFromFile(const std::string &path_to_file, std::string &target_str, std::size_t max_size = 1000000000) const override; + std::uint64_t hashEnotes(std::uint64_t enote_idx, std::string &hash) const override; + void finishRescanBcKeepKeyImages(std::uint64_t enote_idx, const std::string &hash) override; + std::vector> getPublicNodes(bool white_only = true) const override; + std::pair estimateTxSizeAndWeight(bool use_rct, int n_inputs, int ring_size, int n_outputs, std::size_t extra_size) const override; + std::uint64_t importKeyImages(const std::vector> &signed_key_images, std::size_t offset, std::uint64_t &spent, std::uint64_t &unspent, bool check_spent = true) override; + bool importKeyImages(const std::vector &key_images, std::size_t offset = 0, const std::unordered_set &selected_enotes_indices = {}) override; + bool getAllowMismatchedDaemonVersion() const override; + void setAllowMismatchedDaemonVersion(bool allow_mismatch) override; + bool setDaemon(const std::string &daemon_address, const std::string &daemon_username = "", const std::string &daemon_password = "", bool trusted_daemon = true, const std::string &ssl_support = "autodetect", const std::string &ssl_private_key_path = "", const std::string &ssl_certificate_path = "", const std::string &ssl_ca_file_path = "", const std::vector &ssl_allowed_fingerprints_str = {}, bool ssl_allow_any_cert = false, const std::string &proxy = "") override; + private: void clearStatus() const; void setStatusError(const std::string& message) const; @@ -248,6 +296,18 @@ class WalletImpl : public Wallet bool doInit(const std::string &daemon_address, const std::string &proxy_address, uint64_t upper_transaction_size_limit = 0, bool ssl = false); bool checkBackgroundSync(const std::string &message) const; + /** + * brief: getEnoteIndex - get the index of an enote in local enote storage + * param: key_image - key image to identify the enote + * return: enote index + */ + std::size_t getEnoteIndex(const std::string &key_image) const; + /** + * brief: statusOk - + * return: true if status is ok, else false + */ + bool statusOk() const; + private: friend class PendingTransactionImpl; friend class UnsignedTransactionImpl; diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index c374d1574b..f68f1a0aad 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -38,8 +38,12 @@ #include #include #include +#include +#include +#include #include + // Public interface for libwallet library namespace Monero { @@ -67,6 +71,39 @@ enum NetworkType : uint8_t { bool set; }; +/** +* brief: EnoteDetails - Container for all the necessary information that belongs to an enote +*/ +struct EnoteDetails +{ + enum TxProtocol { + Tx_Protocol_CryptoNote, + Tx_Protocol_RingCT + }; + + virtual ~EnoteDetails() = 0; + virtual std::string onetimeAddress() const = 0; + virtual std::string viewTag() const = 0; + virtual std::uint64_t blockHeight() const = 0; + virtual std::string txId() const = 0; + virtual std::uint64_t internalEnoteIndex() const = 0; + virtual std::uint64_t globalEnoteIndex() const = 0; + virtual bool isSpent() const = 0; + virtual bool isFrozen() const = 0; + virtual std::uint64_t spentHeight() const = 0; + virtual std::string keyImage() const = 0; + virtual std::string mask() const = 0; + virtual std::uint64_t amount() const = 0; + virtual TxProtocol protocolVersion() const = 0; + virtual bool isKeyImageKnown() const = 0; + virtual bool isKeyImageRequest() const = 0; + virtual std::uint64_t pkIndex() const = 0; + virtual std::vector> uses() const = 0; + + // Multisig + virtual bool isKeyImagePartial() const = 0; +}; + /** * @brief Transaction-like interface for sending money */ @@ -173,6 +210,13 @@ struct TransactionInfo Direction_Out }; + enum TxState { + pending, + pending_in_pool, + failed, + confirmed + }; + struct Transfer { Transfer(uint64_t _amount, const std::string &address); const uint64_t amount; @@ -181,7 +225,9 @@ struct TransactionInfo virtual ~TransactionInfo() = 0; virtual int direction() const = 0; + // legacy : use txState() instead virtual bool isPending() const = 0; + // legacy : use txState() instead virtual bool isFailed() const = 0; virtual bool isCoinbase() const = 0; virtual uint64_t amount() const = 0; @@ -199,6 +245,10 @@ struct TransactionInfo virtual std::string paymentId() const = 0; //! only applicable for output transactions virtual const std::vector & transfers() const = 0; + + virtual std::uint64_t receivedChangeAmount() const = 0; + virtual TxState txState() const = 0; + virtual bool isDoubleSpendSeen() const = 0; }; /** * @brief The TransactionHistory - interface for displaying transaction history @@ -304,7 +354,6 @@ struct SubaddressAccountRow { std::string m_balance; std::string m_unlockedBalance; public: - std::string extra; std::string getAddress() const {return m_address;} std::string getLabel() const {return m_label;} std::string getBalance() const {return m_balance;} @@ -420,6 +469,18 @@ struct WalletListener * @brief If the listener is created before the wallet this enables to set created wallet object */ virtual void onSetWallet(Wallet * wallet) { (void)wallet; }; + /** + * brief: onReorg - called on blockchain reorg + */ + virtual void onReorg(std::uint64_t height, std::uint64_t blocks_detached, std::size_t transfers_detached) = 0; + /** + * brief: onGetPassword - called by scan_output() to decrypt keys + */ + virtual optional onGetPassword(const char *reason) = 0; + /** + * brief: onPoolTxRemoved - when obsolete pool transactions get removed + */ + virtual void onPoolTxRemoved(const std::string &txid) = 0; }; @@ -452,6 +513,13 @@ struct Wallet BackgroundSync_CustomPassword = 2 }; + struct WalletState { + // is wallet file format deprecated + bool is_deprecated; + std::uint64_t ring_size; + std::string daemon_address; + }; + virtual ~Wallet() = 0; virtual std::string seed(const std::string& seed_offset = "") const = 0; virtual std::string getSeedLanguage() const = 0; @@ -699,6 +767,14 @@ struct Wallet return paymentIdFromAddress(str, testnet ? TESTNET : MAINNET); } static uint64_t maximumAllowedAmount(); + /** + * brief: walletExists - check if wallet file and .keys file exist for given path + * param: path - filename + * outparam: keys_file_exists - + * outparam: wallet_file_exists - + * return: true if (key_file_exists || wallet_file_exists) + */ + static bool walletExists(const std::string &path, bool &key_file_exists, bool &wallet_file_exists); // Easylogger wrapper 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); @@ -1043,12 +1119,15 @@ struct Wallet virtual std::string getReserveProof(bool all, uint32_t account_index, uint64_t amount, const std::string &message) const = 0; virtual bool checkReserveProof(const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &total, uint64_t &spent) const = 0; - /* - * \brief signMessage - sign a message with the spend private key - * \param message - the message to sign (arbitrary byte data) - * \return the signature - */ - virtual std::string signMessage(const std::string &message, const std::string &address = "") = 0; + /** + * brief: signMessage - sign a message with your private key (SigV2) + * param: message - message to sign (arbitrary byte data) + * param: address - address used to sign the message (use main address if empty) + * param: sign_with_view_key - (default: false, use spend key to sign) + * return: proof type prefix + base58 encoded signature, else empty string + * note: sets status error on fail + */ + virtual std::string signMessage(const std::string &message, const std::string &address = "", bool sign_with_view_key = false) = 0; /*! * \brief verifySignedMessage - verify a signature matches a given message * \param message - the message (arbitrary byte data) @@ -1144,6 +1223,342 @@ struct Wallet //! get bytes sent virtual uint64_t getBytesSent() = 0; + + /** + * brief: getMultisigSeed - get seed for multisig wallet + * param: seed_offset - passphrase + * return: seed if succeeded, else empty string + * note: sets status error on fail + */ + virtual std::string getMultisigSeed(const std::string &seed_offset) const = 0; + /** + * brief: getSubaddressIndex - get major and minor index of provided subaddress + * param: address - main- or sub-address to get the index from + * return: [major_index, minor_index] if succeeded + * note: sets status error on fail + */ + virtual std::pair getSubaddressIndex(const std::string &address) const = 0; + /** + * brief: freeze - freeze enote "so they don't appear in balance, nor are considered when creating a transaction, etc." (https://github.com/monero-project/monero/pull/5333) + * param: idx - index of enote in local enote storage + * param: key_image - key image of enote + * note: sets status error on fail + */ + virtual void freeze(std::size_t idx) = 0; + virtual void freeze(const std::string &key_image) = 0; + /** + * brief: thaw - thaw enote that is frozen, so it appears in balance and can be spent in a transaction + * param: idx - index of enote in local enote storage + * param: key_image - key image of enote + * note: sets status error on fail + */ + virtual void thaw(std::size_t idx) = 0; + virtual void thaw(const std::string &key_image) = 0; + /** + * brief: isFrozen - check if enote is frozen + * param: idx - index of enote in local enote storage + * param: key_image - key image of enote + * return : true if enote is frozen, else false + * note: sets status error on fail + */ + virtual bool isFrozen(std::size_t idx) const = 0; + virtual bool isFrozen(const std::string &key_image) const = 0; + /** + * brief: createOneOffSubaddress - create a subaddress for given index + * param: account_index - major index + * param: address_index - minor index + */ + virtual void createOneOffSubaddress(std::uint32_t account_index, std::uint32_t address_index) = 0; + /** + * brief: getWalletState - get information about the wallet + * return: WalletState object + */ + virtual WalletState getWalletState() const = 0; + /** + * brief: rewriteWalletFile - rewrite the wallet file for wallet update + * param: wallet_name - name of the wallet file (should exist) + * param: password - wallet password + * note: sets status error on fail + */ + virtual void rewriteWalletFile(const std::string &wallet_name, const std::string &password) = 0; + /** + * brief: writeWatchOnlyWallet - create a new watch-only wallet file with view keys from current wallet + * param: password - password for new watch-only wallet + * outparam: new_keys_file_name - wallet_name + "-watchonly.keys" + * note: sets status error on fail + */ + virtual void writeWatchOnlyWallet(const std::string &password, std::string &new_keys_file_name) = 0; + /** + * brief: refreshPoolOnly - calls wallet2 update_pool_state and process_pool_state + * param: refreshed - (default: false) + * param: try_incremental - (default: false) + * note: sets status error on fail + */ + virtual void refreshPoolOnly(bool refreshed = false, bool try_incremental = false) = 0; + /** + * brief: getEnoteDetails - get information about all enotes + * outparam: enote_details - + */ + virtual void getEnoteDetails(std::vector> &enote_details) const = 0; + /** + * brief: convertMultisigTxToString - get the encrypted unsigned multisig transaction as hex string from a multisig pending transaction + * param: multisig_ptx - multisig pending transaction + * return: encrypted tx data as hex string if succeeded, else empty string + * note: sets status error on fail + */ + virtual std::string convertMultisigTxToStr(const PendingTransaction &multisig_ptx) const = 0; + /** + * brief: saveMultisigTx - save a multisig pending transaction to file + * param: multisig_ptx - multisig pending transaction + * param: filename - + * return: true if succeeded + * note: sets status error on fail + */ + virtual bool saveMultisigTx(const PendingTransaction &multisig_ptx, const std::string &filename) const = 0; + /** + * brief: convertTxToStr - get the encrypted data from a vector of pending transactions as hex string + * param: ptxs - + * return: unsigned tx data as encrypted hex string if succeeded, else empty string + * note: sets status error on fail + */ + virtual std::string convertTxToStr(const PendingTransaction &ptxs) const = 0; + /** + * brief: parseUnsignedTxFromStr - get an unsigned transaction set from encrypted unsigned transaction as hex string + * param: unsigned_tx_str - encrypted hex string + * outparam: exported_txs - + * return: true if succeeded + */ + virtual bool parseUnsignedTxFromStr(const std::string &unsigned_tx_str, UnsignedTransaction &exported_txs) const = 0; + /** + * brief: parseTxFromStr - get transactions from encrypted signed transaction as hex string + * param: signed_tx_str - + * outparam: ptx - + * return: true if succeeded + */ + virtual bool parseTxFromStr(const std::string &signed_tx_str, PendingTransaction &ptx) const = 0; + /** + * brief: insertColdKeyImages - remember cold key images for parsed tx, for when we get those txes from the blockchain + * Call: + * - parseTxFromStr() + * - accept_func() (optional) + * - importKeyImages() + * - insertColdKeyImages() + * param: ptx - PendingTransaction obtained from parseTxFromStr() outparam ptx + */ + virtual void insertColdKeyImages(PendingTransaction &ptx) = 0; + /** + * brief: parseMultisigTxFromStr - get pending multisig transaction from encrypted unsigned multisig transaction as hex string + * param: multisig_tx_str - + * outparam: exported_txs - + * return: true if succeeded + * note: sets status error on fail + */ + virtual bool parseMultisigTxFromStr(const std::string &multisig_tx_str, PendingTransaction &exported_txs) const = 0; + /** + * brief: getFeeMultiplier - + * param: priority - + * param: fee_algorithm - + * return: fee multiplier + * note: sets status error on fail + */ + virtual std::uint64_t getFeeMultiplier(std::uint32_t priority, int fee_algorithm) const = 0; + /** + * brief: getBaseFee - + * return: dynamic base fee estimate if using dynamic fee, else FEE_PER_KB + */ + virtual std::uint64_t getBaseFee() const = 0; + /** + * brief: adjustPriority - adjust priority depending on how "full" last N blocks are + * param: priority - + * return: adjusted priority + * warning: doesn't tell if it failed + */ + virtual std::uint32_t adjustPriority(std::uint32_t priority) = 0; + /** + * brief: coldTxAuxImport - + * param: ptx - + * param: tx_device_aux - + * note: sets status error on fail + */ + virtual void coldTxAuxImport(const PendingTransaction &ptx, const std::vector &tx_device_aux) const = 0; + /** + * brief: coldSignTx - + * param: ptx_in - + * outparam: exported_txs_out - + */ + virtual void coldSignTx(const PendingTransaction &ptx_in, PendingTransaction &exported_txs_out) const = 0; + /** + * brief: discardUnmixableEnotes - freeze all unmixable enotes + * note: sets status error on fail + */ + virtual void discardUnmixableEnotes() = 0; + /** + * brief: setTxKey - set the transaction key (r) for a given in case the tx was made by some other device or 3rd party wallet + * param: txid - + * param: tx_key - secret transaction key r + * param: additional_tx_keys - + * param: single_destination_subaddress - + * note: sets status error on fail + */ + virtual void setTxKey(const std::string &txid, const std::string &tx_key, const std::vector &additional_tx_keys, const std::string &single_destination_subaddress) = 0; + /** + * brief: getAccountTags - get the list of registered account tags + * return: first.Key=(tag's name), first.Value=(tag's label), second[i]=(i-th account's tag) + */ + virtual const std::pair, std::vector>& getAccountTags() const = 0; + /** + * brief: setAccountTag - set a tag to a set of subaddress accounts by index + * param: account_index - major index + * param: tag - + * note: sets status error on fail + */ + virtual void setAccountTag(const std::set &account_indices, const std::string &tag) = 0; + /** + * brief: setAccountTagDescription - set a description for a tag, tag must already exist + * param: tag - + * param: description - + * note: sets status error on fail + */ + virtual void setAccountTagDescription(const std::string &tag, const std::string &description) = 0; + /** + * brief: exportEnotesToStr - export enotes and return encrypted data + * (comparable with legacy exportOutputs(), with the difference that this returns a string, the other one saves to file) + * param: all - go from `start` for `count` enotes if true, else go incremental from last exported enote for `count` enotes (default: false) + * param: start - offset index in enote storage, needs to be 0 for incremental mode (default: 0) + * param: count - try to export this amount of enotes (default: 0xffffffff) + * return: encrypted data of exported enotes as hex string if succeeded, else empty string + * note: sets status error on fail + */ + virtual std::string exportEnotesToStr(bool all = false, std::uint32_t start = 0, std::uint32_t count = 0xffffffff) const = 0; + /** + * brief: importEnotesFromStr - import enotes from encrypted hex string + * param: enotes_str - enotes data as encrypted hex string + * return: total size of enote storage + * note: sets status error on fail + */ + virtual std::size_t importEnotesFromStr(const std::string &enotes_str) = 0; + /** + * brief: getBlockchainHeightByDate - + * param: year - + * param: month - in range 1-12 + * param: day - in range 1-31 + * return: blockchain height + * note: sets status error on fail + */ + virtual std::uint64_t getBlockchainHeightByDate(std::uint16_t year, std::uint8_t month, std::uint8_t day) const = 0; + /** + * brief: estimateBacklog - + * param: fee_levels - [ [fee per byte min, fee per byte max], ... ] + * return: [ [number of blocks min, number of blocks max], ... ] + * note: sets status error on fail + */ + virtual std::vector> estimateBacklog(const std::vector> &fee_levels) const = 0; + /** + * brief: saveToFile - save hex string to file + * param: path_to_file - file name + * param: binary - hex string data + * param: is_printable - (default: false) + * return: true if succeeded + */ + virtual bool saveToFile(const std::string &path_to_file, const std::string &binary, bool is_printable = false) const = 0; + /** + * brief: loadFromFile - load hex string from file + * param: path_to_file - file name + * outparam: target_str - hex string data + * param: max_size - maximum size in bytes (default: 1000000000) + * return: true if succeeded + */ + virtual bool loadFromFile(const std::string &path_to_file, std::string &target_str, std::size_t max_size = 1000000000) const = 0; + /** + * brief: hashEnotes - get hash of all enotes in local enote store up until `enote_idx` + * (formerly in wallet2: `uint64_t wallet2::hash_m_transfers(boost::optional transfer_height, crypto::hash &hash)`) + * param: enote_idx - include all enotes below this index + * outparam: hash - hash as hex string + * return: number of hashed enotes + * note: sets status error on fail + */ + virtual std::uint64_t hashEnotes(std::uint64_t enote_idx, std::string &hash) const = 0; + /** + * brief: finishRescanBcKeepKeyImages - + * param: enote_idx - + * param: hash - + * note: sets status error on fail + */ + virtual void finishRescanBcKeepKeyImages(std::uint64_t enote_idx, const std::string &hash) = 0; + /** + * brief: getPublicNodes - get a list of public notes with information when they were last seen + * param: white_only - include gray nodes if false (default: true) + * return: [ [ host_ip, host_rpc_port, last_seen ], ... ] + * note: sets status error on fail + */ + virtual std::vector> getPublicNodes(bool white_only = true) const = 0; + /** + * brief: estimateTxSizeAndWeight - + * param: use_rct - + * param: n_inputs - number of input enotes + * param: ring_size - + * param: n_outputs - number of output enotes + * param: extra_size - size of tx_extra + * return: [estimated tx size, estimated tx weight] + * note: sets status error on fail + */ + virtual std::pair estimateTxSizeAndWeight(bool use_rct, int n_inputs, int ring_size, int n_outputs, std::size_t extra_size) const = 0; + /** + * brief: importKeyImages - + * param: signed_key_images - [ [key_image, signature c || signature r], ... ] + * param: offset - offset in local enote storage + * outparam: spent - total spent amount of the wallet + * outparam: unspent - total unspent amount of the wallet + * param: check_spent - + * return: blockchain height of last signed key image, can be 0 if height unknown + * note: sets status error on fail + */ + virtual std::uint64_t importKeyImages(const std::vector> &signed_key_images, std::size_t offset, std::uint64_t &spent, std::uint64_t &unspent, bool check_spent = true) = 0; + /** + * brief: importKeyImages - + * param: key_images - + * param: offset - offset in local enote storage + * param: selected_enotes_indices - + * return: true if succeeded + * note: sets status error on fail + */ + virtual bool importKeyImages(const std::vector &key_images, std::size_t offset = 0, const std::unordered_set &selected_enotes_indices = {}) = 0; + /** + * brief: getAllowMismatchedDaemonVersion - + */ + virtual bool getAllowMismatchedDaemonVersion() const = 0; + /** + * brief: setAllowMismatchedDaemonVersion - + * param: allow_mismatch - + */ + virtual void setAllowMismatchedDaemonVersion(bool allow_mismatch) = 0; + /** + * brief: setDaemon - + * param: daemon_address - + * param: daemon_username - for daemon login (default: empty string) + * param: daemon_password - for daemon login (default: empty string) + * param: trusted_daemon - (default: true) + * param: ssl_support - "disabled" | "enabled" | "autodetect" (default: "autodetect") + * param: ssl_private_key_path - (default: empty string) + * param: ssl_certificate_path - (default: empty string) + * param: ssl_ca_file_path - (default: empty string) + * param: ssl_allowed_fingerprints_str - (default: empty vector) + * param: ssl_allow_any_cert - (default: false) + * param: proxy - (default: empty string) + * return: true if succeeded + * note: sets status error on fail + */ + virtual bool setDaemon(const std::string &daemon_address, + const std::string &daemon_username = "", + const std::string &daemon_password = "", + bool trusted_daemon = true, + const std::string &ssl_support = "autodetect", + const std::string &ssl_private_key_path = "", + const std::string &ssl_certificate_path = "", + const std::string &ssl_ca_file_path = "", + const std::vector &ssl_allowed_fingerprints_str = {}, + bool ssl_allow_any_cert = false, + const std::string &proxy = "") = 0; }; /** diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 3874ce8504..d1674abcf2 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -7979,7 +7979,7 @@ bool wallet2::load_tx(const std::string &signed_filename, std::vector &ptx, std::function accept_func) +bool wallet2::parse_tx_from_str(const std::string &signed_tx_st, std::vector &ptx, std::function accept_func, tools::wallet2::signed_tx_set *signed_txs_out /* = nullptr */, bool do_handle_key_images /* = true */) { std::string s = signed_tx_st; signed_tx_set signed_txs; @@ -8073,19 +8073,31 @@ bool wallet2::parse_tx_from_str(const std::string &signed_tx_st, std::vector &cold_key_images) +{ + for (const auto &ki: cold_key_images) + m_cold_key_images.insert(ki); +} +//---------------------------------------------------------------------------------------------------- std::string wallet2::save_multisig_tx(multisig_tx_set txs) { LOG_PRINT_L0("saving " << txs.m_ptx.size() << " multisig transactions"); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 7228f19077..3d99359935 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -1198,7 +1198,8 @@ namespace tools bool load_unsigned_tx(const std::string &unsigned_filename, unsigned_tx_set &exported_txs) const; bool parse_unsigned_tx_from_str(const std::string &unsigned_tx_st, unsigned_tx_set &exported_txs) const; bool load_tx(const std::string &signed_filename, std::vector &ptx, std::function accept_func = NULL); - bool parse_tx_from_str(const std::string &signed_tx_st, std::vector &ptx, std::function accept_func); + bool parse_tx_from_str(const std::string &signed_tx_st, std::vector &ptx, std::function accept_func, signed_tx_set *signed_tx_set_out = nullptr, bool do_handle_key_images = true); + void insert_cold_key_images(std::unordered_map &cold_key_images); std::vector create_transactions_2(std::vector dsts, const size_t fake_outs_count, uint32_t priority, const std::vector& extra, uint32_t subaddr_account, std::set subaddr_indices, const unique_index_container& subtract_fee_from_outputs = {}); // pass subaddr_indices by value on purpose std::vector create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, uint32_t priority, const std::vector& extra, uint32_t subaddr_account, std::set subaddr_indices); std::vector create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, uint32_t priority, const std::vector& extra);