diff --git a/src/wallet/api/CMakeLists.txt b/src/wallet/api/CMakeLists.txt index 3428de27a32..74b0d5306a7 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 00000000000..691973efb68 --- /dev/null +++ b/src/wallet/api/enote_details.cpp @@ -0,0 +1,105 @@ +// Copyright (c) 2014-2025, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "enote_details.h" + + +namespace Monero { + +EnoteDetails::~EnoteDetails() {} + + +EnoteDetailsImpl::EnoteDetailsImpl(): + m_block_height(0), + m_unlock_time(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(TxProtocol::TxProtocol_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::string EnoteDetailsImpl::paymentId() const +{ return m_payment_id; } +std::uint64_t EnoteDetailsImpl::blockHeight() const +{ return m_block_height; } +std::uint64_t EnoteDetailsImpl::unlockTime() const +{ return m_unlock_time; } +bool EnoteDetailsImpl::isUnlocked() const +{ return m_is_unlocked; } +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; } +std::uint32_t EnoteDetailsImpl::subaddressIndexMajor() const +{ return m_subaddress_index_major; } +std::uint32_t EnoteDetailsImpl::subaddressIndexMinor() const +{ return m_subaddress_index_minor; } + +// Multisig +bool EnoteDetailsImpl::isKeyImagePartial() const +{ return m_key_image_partial; } + +} // namespace Monero diff --git a/src/wallet/api/enote_details.h b/src/wallet/api/enote_details.h new file mode 100644 index 00000000000..148e17a9fec --- /dev/null +++ b/src/wallet/api/enote_details.h @@ -0,0 +1,97 @@ +// Copyright (c) 2014-2025, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#pragma once + +#include "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::string paymentId() const override; + std::uint64_t blockHeight() const override; + std::uint64_t unlockTime() const override; + bool isUnlocked() 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; + std::uint32_t subaddressIndexMajor() const override; + std::uint32_t subaddressIndexMinor() const override; + + // Multisig + bool isKeyImagePartial() const override; + +private: + friend class WalletImpl; + + std::string m_onetime_address; + std::string m_view_tag; + std::string m_payment_id; + std::uint64_t m_block_height; + std::uint64_t m_unlock_time; + bool m_is_unlocked; + std::string m_tx_id; + std::uint64_t m_internal_enote_index; + std::uint64_t m_global_enote_index; + bool m_spent; + bool m_frozen; + std::uint64_t m_spent_height; + std::string m_key_image; + std::string m_mask; + std::uint64_t m_amount; + TxProtocol m_protocol_version; + bool m_key_image_known; + bool m_key_image_request; + std::uint64_t m_pk_index; + std::vector> m_uses; + std::uint32_t m_subaddress_index_major; + std::uint32_t m_subaddress_index_minor; + + // Multisig + bool m_key_image_partial; +}; + +} // namespace Monero diff --git a/src/wallet/api/pending_transaction.cpp b/src/wallet/api/pending_transaction.cpp index 9783da5bcda..d8108e01e6c 100644 --- a/src/wallet/api/pending_transaction.cpp +++ b/src/wallet/api/pending_transaction.cpp @@ -29,6 +29,7 @@ // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers #include "pending_transaction.h" +#include "string_tools.h" #include "wallet.h" #include "common_defines.h" @@ -83,6 +84,7 @@ bool PendingTransactionImpl::commit(const std::string &filename, bool overwrite) LOG_PRINT_L3("m_pending_tx size: " << m_pending_tx.size()); + bool warn_of_possible_attack = !m_wallet.trustedDaemon(); try { // Save tx to file if (!filename.empty()) { @@ -134,13 +136,57 @@ bool PendingTransactionImpl::commit(const std::string &filename, bool overwrite) m_pending_tx.pop_back(); } // TODO: extract method; } - } catch (const tools::error::daemon_busy&) { - // TODO: make it translatable with "tr"? - m_errorString = tr("daemon is busy. Please try again later."); + } catch (const tools::error::deprecated_rpc_access&) { + m_errorString = tr("Daemon requires deprecated RPC payment. See https://github.com/monero-project/monero/issues/8722"); m_status = Status_Error; } 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; + } catch (const tools::error::daemon_busy&) { + m_errorString = tr("daemon is busy. Please try again later."); + m_status = Status_Error; + } catch (const tools::error::wallet_rpc_error& e) { + m_errorString = (boost::format(tr("RPC error: %s")) % e.what()).str(); + m_status = Status_Error; + LOG_ERROR(tr("RPC error: ") << e.what()); + } catch (const tools::error::get_outs_error &e) { + m_errorString = (boost::format(tr("failed to get random outputs to mix: %s")) % e.what()).str(); + m_status = Status_Error; + } catch (const tools::error::not_enough_unlocked_money& e) { + m_errorString = (boost::format("not enough unlocked money to transfer, available only %s, sent amount %s") + % cryptonote::print_money(e.available()) + % cryptonote::print_money(e.tx_amount())).str(); + m_status = Status_Error; + LOG_PRINT_L0(m_errorString); + warn_of_possible_attack = false; + } catch (const tools::error::not_enough_money& e) { + m_errorString = (boost::format("not enough money to transfer, available only %s, sent amount %s") + % cryptonote::print_money(e.available()) + % cryptonote::print_money(e.tx_amount())).str(); + m_status = Status_Error; + LOG_PRINT_L0(m_errorString); + warn_of_possible_attack = false; + } catch (const tools::error::tx_not_possible& e) { + m_errorString = (boost::format("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees, or trying to send more money than the unlocked balance, or not leaving enough for fees, available only %s, transaction amount %s = %s + %s (fee)") + % cryptonote::print_money(e.available()) + % cryptonote::print_money(e.tx_amount() + e.fee()) + % cryptonote::print_money(e.tx_amount()) + % cryptonote::print_money(e.fee())).str(); + m_status = Status_Error; + LOG_PRINT_L0(m_errorString); + warn_of_possible_attack = false; + } catch (const tools::error::not_enough_outs_to_mix& e) { + std::ostringstream writer(m_errorString); + writer << tr("not enough outputs for specified ring size") << " = " << (e.mixin_count() + 1) << ":"; + for (std::pair outs_for_amount : e.scanty_outs()) + writer << "\n" << tr("output amount") << " = " << cryptonote::print_money(outs_for_amount.first) << ", " << tr("found outputs to use") << " = " << outs_for_amount.second; + m_errorString = writer.str(); + m_status = Status_Error; + LOG_PRINT_L0(m_errorString); + } catch (const tools::error::tx_not_constructed&) { + m_errorString = tr("transaction was not constructed"); + m_status = Status_Error; + warn_of_possible_attack = false; } catch (const tools::error::tx_rejected& e) { std::ostringstream writer(m_errorString); writer << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); @@ -149,6 +195,35 @@ bool PendingTransactionImpl::commit(const std::string &filename, bool overwrite) m_errorString = writer.str(); if (!reason.empty()) m_errorString += string(tr(". Reason: ")) + reason; + } catch (const tools::error::tx_sum_overflow& e) { + m_errorString = e.what(); + m_status = Status_Error; + warn_of_possible_attack = false; + } catch (const tools::error::zero_amount&) { + m_errorString = tr("destination amount is zero"); + m_status = Status_Error; + warn_of_possible_attack = false; + } catch (const tools::error::zero_destination&) { + m_errorString = tr("transaction has no destination"); + m_status = Status_Error; + warn_of_possible_attack = false; + } catch (const tools::error::tx_too_big& e) { + m_errorString = tr("failed to find a suitable way to split transactions"); + m_status = Status_Error; + warn_of_possible_attack = false; + } catch (const tools::error::transfer_error& e) { + m_errorString = (boost::format(tr("unknown transfer error: %s")) % e.what()).str(); + m_status = Status_Error; + LOG_ERROR("unknown transfer error: " << e.to_string()); + } catch (const tools::error::multisig_export_needed& e) { + m_errorString = (boost::format(tr("Multisig error: ")) % e.what()).str(); + m_status = Status_Error; + LOG_ERROR("Multisig error: " << e.to_string()); + warn_of_possible_attack = false; + } catch (const tools::error::wallet_internal_error& e) { + m_errorString = (boost::format(tr("internal error: ")) % e.what()).str(); + m_status = Status_Error; + LOG_ERROR("internal error: " << e.to_string()); } catch (const std::exception &e) { m_errorString = string(tr("Unknown exception: ")) + e.what(); m_status = Status_Error; @@ -157,6 +232,8 @@ bool PendingTransactionImpl::commit(const std::string &filename, bool overwrite) LOG_ERROR(m_errorString); m_status = Status_Error; } + if (warn_of_possible_attack && m_status != Status_Ok) + m_errorString += tr("\nThere was an error, which could mean the node may be trying to get you to retry creating a transaction, and zero in on which outputs you own. Or it could be a bona fide error. It may be prudent to disconnect from this node, and not try to send a transaction immediately. Alternatively, connect to another node so the original node cannot correlate information."); m_wallet.startRefresh(); return m_status == Status_Ok; @@ -182,6 +259,16 @@ uint64_t PendingTransactionImpl::dust() const return result; } +uint64_t PendingTransactionImpl::dustInFee() const +{ + uint64_t result = 0; + for (const auto & ptx : m_pending_tx) { + if (ptx.dust_added_to_fee) + result += ptx.dust; + } + return result; +} + uint64_t PendingTransactionImpl::fee() const { uint64_t result = 0; @@ -191,6 +278,15 @@ uint64_t PendingTransactionImpl::fee() const return result; } +uint64_t PendingTransactionImpl::change() const +{ + uint64_t result = 0; + for (const auto &ptx : m_pending_tx) { + result += ptx.change_dts.amount; + } + return result; +} + uint64_t PendingTransactionImpl::txCount() const { return m_pending_tx.size(); @@ -212,6 +308,147 @@ std::vector> PendingTransactionImpl::subaddrIndices() const return result; } +std::string PendingTransactionImpl::convertTxToStr() +{ + std::string tx; + LOG_PRINT_L3("m_pending_tx size: " << m_pending_tx.size()); + + try { + tx = m_wallet.m_wallet->dump_tx_to_str(m_pending_tx); + m_status = Status_Ok; + return tx; + } catch (const tools::error::wallet_internal_error &e) { + m_errorString = std::string(tr("wallet internal error: ")) + e.what(); + m_status = Status_Error; + LOG_ERROR(std::string(tr("wallet internal error: ")) + e.to_string()); + } catch (const std::exception &e) { + m_errorString = string(tr("Unknown exception: ")) + e.what(); + m_status = Status_Error; + } catch (...) { + m_errorString = tr("Unhandled exception"); + m_status = Status_Error; + } + return ""; // m_status != Status_Ok +} + +std::vector PendingTransactionImpl::convertTxToRawBlobStr() +{ + std::vector tx_blobs{}; + m_status = Status_Ok; + m_errorString = ""; + for (const auto &ptx : m_pending_tx) + { + std::string tx_blob; + if (cryptonote::tx_to_blob(ptx.tx, tx_blob)) + tx_blobs.push_back(tx_blob); + else + { + m_status = Status_Error; + m_errorString = tr("failed to serialize tx"); + return {}; + } + } + return tx_blobs; +} + +double PendingTransactionImpl::getWorstFeePerByte() const +{ + double worst_fee_per_byte = std::numeric_limits::max(); + for (size_t n = 0; n < m_pending_tx.size(); ++n) + { + const uint64_t blob_size = cryptonote::tx_to_blob(m_pending_tx[n].tx).size(); + const double fee_per_byte = m_pending_tx[n].fee / (double)blob_size; + if (fee_per_byte < worst_fee_per_byte) + worst_fee_per_byte = fee_per_byte; + } + return worst_fee_per_byte; +} + +std::vector>> PendingTransactionImpl::vinOffsets() const +{ + std::vector>> vin_offsets; + for (size_t n = 0; n < m_pending_tx.size(); ++n) + { + const cryptonote::transaction &tx = m_pending_tx[n].tx; + vin_offsets.push_back({}); + for (size_t i = 0; i < tx.vin.size(); ++i) + { + if (tx.vin[i].type() != typeid(cryptonote::txin_to_key)) + continue; + const cryptonote::txin_to_key& in_key = boost::get(tx.vin[i]); + vin_offsets[n].push_back(in_key.key_offsets); + } + } + return vin_offsets; +} + +std::vector> PendingTransactionImpl::constructionDataRealOutputIndices() const +{ + std::vector> vin_real_output_indices; + for (size_t n = 0; n < m_pending_tx.size(); ++n) + { + const cryptonote::transaction &tx = m_pending_tx[n].tx; + const tools::wallet2::tx_construction_data &construction_data = m_pending_tx[n].construction_data; + vin_real_output_indices.push_back({}); + for (size_t i = 0; i < tx.vin.size(); ++i) + { + if (tx.vin[i].type() != typeid(cryptonote::txin_to_key)) + continue; + const tools::wallet2::transfer_details &td = m_wallet.m_wallet->get_transfer_details(construction_data.selected_transfers[i]); + const cryptonote::tx_source_entry *sptr = NULL; + for (const auto &src: construction_data.sources) + if (src.outputs[src.real_output].second.dest == td.get_public_key()) + sptr = &src; + if (!sptr) + return {}; + vin_real_output_indices[n].push_back(sptr->real_output); + } + } + return vin_real_output_indices; +} + +std::vector> PendingTransactionImpl::vinAmounts() const +{ + std::vector> vin_amounts; + for (size_t n = 0; n < m_pending_tx.size(); ++n) + { + cryptonote::transaction tx = m_pending_tx[n].tx; + vin_amounts.push_back({}); + for (size_t i = 0; i < tx.vin.size(); ++i) + { + if (tx.vin[i].type() != typeid(cryptonote::txin_to_key)) + continue; + const cryptonote::txin_to_key& in_key = boost::get(tx.vin[i]); + vin_amounts[n].push_back(in_key.amount); + } + } + return vin_amounts; +} + +std::vector>> PendingTransactionImpl::getEnoteDetailsIn() const +{ + std::vector> eds = m_wallet.getEnoteDetails(); + std::vector>> eds_out; + for (const auto &ptx: m_pending_tx) + { + eds_out.push_back({}); + for (const auto i: ptx.selected_transfers) + eds_out.back().push_back(std::move(eds[i])); + } + return eds_out; +} + +bool PendingTransactionImpl::finishParsingTx() +{ + // import key images + bool r = m_wallet.m_wallet->import_key_images(m_key_images); + if (!r) return false; + + // remember key images for this tx, for when we get those txes from the blockchain + m_wallet.m_wallet->insert_cold_key_images(m_tx_key_images); + return true; +} + std::string PendingTransactionImpl::multisigSignData() { try { if (!m_wallet.multisig().isMultisig) { @@ -232,7 +469,7 @@ std::string PendingTransactionImpl::multisigSignData() { return std::string(); } -void PendingTransactionImpl::signMultisigTx() { +void PendingTransactionImpl::signMultisigTx(std::vector *txids /* = nullptr */) { try { std::vector ignore; @@ -244,6 +481,11 @@ void PendingTransactionImpl::signMultisigTx() { throw std::runtime_error("couldn't sign multisig transaction"); } + if (txids && !ignore.empty()) + { + for (const auto &txid : ignore) + txids->emplace_back(epee::string_tools::pod_to_hex(txid)); + } std::swap(m_pending_tx, txSet.m_ptx); std::swap(m_signers, txSet.m_signers); } catch (const std::exception& e) { @@ -263,4 +505,160 @@ std::vector PendingTransactionImpl::signersKeys() const { return keys; } +void PendingTransactionImpl::finishRestoringMultisigTransaction() { + tools::wallet2::multisig_tx_set exported_txs; + exported_txs.m_ptx = m_pending_tx; + exported_txs.m_signers = m_signers; + m_wallet.m_wallet->finish_loading_accepted_multisig_tx(exported_txs); +} + +//---------------------------------------------------------------------------------------------------- +bool PendingTransactionImpl::checkLoadedTx(const std::function get_num_txes, const std::function &get_tx, const std::string &extra_message) +{ + // gather info to ask the user + uint64_t amount = 0, amount_to_dests = 0, change = 0; + size_t min_ring_size = ~0; + std::unordered_map> dests; + int first_known_non_zero_change_index = -1; + std::string payment_id_string = ""; + for (size_t n = 0; n < get_num_txes(); ++n) + { + const tools::wallet2::tx_construction_data &cd = get_tx(n); + + std::vector tx_extra_fields; + bool has_encrypted_payment_id = false; + crypto::hash8 payment_id8 = crypto::null_hash8; + if (cryptonote::parse_tx_extra(cd.extra, tx_extra_fields)) + { + cryptonote::tx_extra_nonce extra_nonce; + if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) + { + crypto::hash payment_id; + if(cryptonote::get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8)) + { + if (!payment_id_string.empty()) + payment_id_string += ", "; + + // if none of the addresses are integrated addresses, it's a dummy one + bool is_dummy = true; + for (const auto &e: cd.dests) + if (e.is_integrated) + is_dummy = false; + + if (is_dummy) + payment_id_string += std::string("dummy encrypted payment ID"); + else + { + payment_id_string = std::string("encrypted payment ID ") + epee::string_tools::pod_to_hex(payment_id8); + has_encrypted_payment_id = true; + } + } + else if (cryptonote::get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id)) + { + if (!payment_id_string.empty()) + payment_id_string += ", "; + payment_id_string = std::string("unencrypted payment ID ") + epee::string_tools::pod_to_hex(payment_id); + payment_id_string += " (OBSOLETE)"; + } + } + } + + for (size_t s = 0; s < cd.sources.size(); ++s) + { + amount += cd.sources[s].amount; + size_t ring_size = cd.sources[s].outputs.size(); + if (ring_size < min_ring_size) + min_ring_size = ring_size; + } + for (size_t d = 0; d < cd.splitted_dsts.size(); ++d) + { + const cryptonote::tx_destination_entry &entry = cd.splitted_dsts[d]; + std::string address, standard_address = get_account_address_as_str(m_wallet.m_wallet->nettype(), entry.is_subaddress, entry.addr); + if (has_encrypted_payment_id && !entry.is_subaddress) + { + address = get_account_integrated_address_as_str(m_wallet.m_wallet->nettype(), entry.addr, payment_id8); + address += std::string(" (" + standard_address + " with encrypted payment id " + epee::string_tools::pod_to_hex(payment_id8) + ")"); + } + else + address = standard_address; + auto i = dests.find(entry.addr); + if (i == dests.end()) + dests.insert(std::make_pair(entry.addr, std::make_pair(address, entry.amount))); + else + i->second.second += entry.amount; + amount_to_dests += entry.amount; + } + if (cd.change_dts.amount > 0) + { + auto it = dests.find(cd.change_dts.addr); + if (it == dests.end()) + { + m_status = Status_Error; + m_errorString = tr("Claimed change does not go to a paid address"); + return false; + } + if (it->second.second < cd.change_dts.amount) + { + m_status = Status_Error; + m_errorString = tr("Claimed change is larger than payment to the change address"); + return false; + } + if (cd.change_dts.amount > 0) + { + if (first_known_non_zero_change_index == -1) + first_known_non_zero_change_index = n; + if (memcmp(&cd.change_dts.addr, &get_tx(first_known_non_zero_change_index).change_dts.addr, sizeof(cd.change_dts.addr))) + { + m_status = Status_Error; + m_errorString = tr("Change goes to more than one address"); + return false; + } + } + change += cd.change_dts.amount; + it->second.second -= cd.change_dts.amount; + if (it->second.second == 0) + dests.erase(cd.change_dts.addr); + } + } + + if (payment_id_string.empty()) + payment_id_string = "no payment ID"; + + std::string dest_string; + size_t n_dummy_outputs = 0; + for (auto i = dests.begin(); i != dests.end(); ) + { + if (i->second.second > 0) + { + if (!dest_string.empty()) + dest_string += ", "; + dest_string += (boost::format(tr("sending %s to %s")) % cryptonote::print_money(i->second.second) % i->second.first).str(); + } + else + ++n_dummy_outputs; + ++i; + } + if (n_dummy_outputs > 0) + { + if (!dest_string.empty()) + dest_string += ", "; + dest_string += std::to_string(n_dummy_outputs) + tr(" dummy output(s)"); + } + if (dest_string.empty()) + dest_string = tr("with no destinations"); + + std::string change_string; + if (change > 0) + { + std::string address = get_account_address_as_str(m_wallet.m_wallet->nettype(), get_tx(0).subaddr_account > 0, get_tx(0).change_dts.addr); + change_string += (boost::format(tr("%s change to %s")) % cryptonote::print_money(change) % address).str(); + } + else + change_string += tr("no change"); + uint64_t fee = amount - amount_to_dests; + m_confirmationMessage = (boost::format(tr("Loaded %lu transactions, for %s, fee %s, %s, %s, with min ring size %lu, %s. %s. Is this okay?")) % (unsigned long)get_num_txes() % cryptonote::print_money(amount) % cryptonote::print_money(fee) % dest_string % change_string % (unsigned long)min_ring_size % payment_id_string % extra_message).str(); + return true; +} + + } diff --git a/src/wallet/api/pending_transaction.h b/src/wallet/api/pending_transaction.h index 9d8d754c0c8..a0821cfdb85 100644 --- a/src/wallet/api/pending_transaction.h +++ b/src/wallet/api/pending_transaction.h @@ -45,30 +45,48 @@ class PendingTransactionImpl : public PendingTransaction ~PendingTransactionImpl(); int status() const override; std::string errorString() const override; + std::string confirmationMessage() const override { return m_confirmationMessage; } bool commit(const std::string &filename = "", bool overwrite = false) override; uint64_t amount() const override; uint64_t dust() const override; + uint64_t dustInFee() const override; uint64_t fee() const override; + uint64_t change() const override; std::vector txid() const override; uint64_t txCount() const override; std::vector subaddrAccount() const override; std::vector> subaddrIndices() const override; + std::string convertTxToStr() override; + std::vector convertTxToRawBlobStr() override; + double getWorstFeePerByte() const override; + std::vector>> vinOffsets() const override; + std::vector> constructionDataRealOutputIndices() const override; + std::vector> vinAmounts() const override; + std::vector>> getEnoteDetailsIn() const override; + bool finishParsingTx() override; // TODO: continue with interface; std::string multisigSignData() override; - void signMultisigTx() override; + void signMultisigTx(std::vector *txids = nullptr) override; std::vector signersKeys() const override; + void finishRestoringMultisigTransaction() override; private: + // Callback function to check all loaded tx's and generate confirmationMessage + bool checkLoadedTx(const std::function get_num_txes, const std::function &get_tx, const std::string &extra_message); + friend class WalletImpl; WalletImpl &m_wallet; int m_status; std::string m_errorString; + std::string m_confirmationMessage; std::vector m_pending_tx; 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 4dc869d5cbb..365888dd461 100644 --- a/src/wallet/api/transaction_history.cpp +++ b/src/wallet/api/transaction_history.cpp @@ -150,6 +150,12 @@ 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; + ti->m_is_unlocked = m_wallet->m_wallet->is_transfer_unlocked(pd.m_unlock_time, pd.m_block_height); + // not used for payment_details + ti->m_change = 0; + ti->m_tx_state = TransactionInfo::TxState::TxState_Confirmed; + // not used for payment_details + ti->m_double_spend_seen = false; m_history.push_back(ti); } @@ -193,6 +199,12 @@ 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_is_unlocked = true; + ti->m_change = pd.m_change; + ti->m_tx_state = TransactionInfo::TxState::TxState_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 +241,12 @@ 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_is_unlocked = true; + 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)}); @@ -236,7 +254,9 @@ void TransactionHistoryImpl::refresh() m_history.push_back(ti); } - + // simplewallet called m_wallet.update_pool_state() & m_wallet.process_pool_state() before getting unconfirmed incoming transfers from the tx pool + // src: https://github.com/monero-project/monero/blob/8d4c625713e3419573dfcc7119c8848f47cabbaa/src/simplewallet/simplewallet.cpp#L10293-L10296 + m_wallet->refreshPoolOnly(); // unconfirmed payments (tx pool) std::list> upayments; m_wallet->m_wallet->get_unconfirmed_payments(upayments); @@ -258,6 +278,12 @@ 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; + ti->m_is_unlocked = false; + // not used for pool_payment_details + ti->m_change = 0; + ti->m_tx_state = TransactionInfo::TxState::TxState_PendingInPool; + 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 909c7708648..81aa9886203 100644 --- a/src/wallet/api/transaction_info.cpp +++ b/src/wallet/api/transaction_info.cpp @@ -83,6 +83,11 @@ bool TransactionInfoImpl::isCoinbase() const return m_coinbase; } +bool TransactionInfoImpl::isUnlocked() const +{ + return m_is_unlocked; +} + uint64_t TransactionInfoImpl::amount() const { return m_amount; @@ -149,4 +154,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 0510fb216d8..17637b3fe65 100644 --- a/src/wallet/api/transaction_info.h +++ b/src/wallet/api/transaction_info.h @@ -47,6 +47,7 @@ class TransactionInfoImpl : public TransactionInfo virtual bool isPending() const override; virtual bool isFailed() const override; virtual bool isCoinbase() const override; + virtual bool isUnlocked() const override; virtual uint64_t amount() const override; //! always 0 for incoming txes virtual uint64_t fee() const override; @@ -63,11 +64,16 @@ 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; bool m_failed; bool m_coinbase; + bool m_is_unlocked; uint64_t m_amount; uint64_t m_fee; uint64_t m_blockheight; @@ -81,6 +87,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 : TxState_Pending / TxState_PendingInPool / TxState_Failed / TxState_Confirmed + TxState m_tx_state; + // is double spend seen + bool m_double_spend_seen; friend class TransactionHistoryImpl; diff --git a/src/wallet/api/unsigned_transaction.cpp b/src/wallet/api/unsigned_transaction.cpp index c549539e5b8..e0fc9b7a9a6 100644 --- a/src/wallet/api/unsigned_transaction.cpp +++ b/src/wallet/api/unsigned_transaction.cpp @@ -68,7 +68,7 @@ string UnsignedTransactionImpl::errorString() const return m_errorString; } -bool UnsignedTransactionImpl::sign(const std::string &signedFileName) +bool UnsignedTransactionImpl::sign(const std::string &signedFileName, bool do_export_raw /* = false */, std::vector *tx_ids_out /* = nullptr */) { if(m_wallet.watchOnly()) { @@ -79,7 +79,7 @@ bool UnsignedTransactionImpl::sign(const std::string &signedFileName) std::vector ptx; try { - bool r = m_wallet.m_wallet->sign_tx(m_unsigned_tx_set, signedFileName, ptx); + bool r = m_wallet.m_wallet->sign_tx(m_unsigned_tx_set, signedFileName, ptx, do_export_raw); if (!r) { m_errorString = tr("Failed to sign transaction"); @@ -89,10 +89,18 @@ bool UnsignedTransactionImpl::sign(const std::string &signedFileName) } catch (const std::exception &e) { - m_errorString = string(tr("Failed to sign transaction")) + e.what(); + m_errorString = string(tr("Failed to sign transaction: ")) + e.what(); m_status = Status_Error; return false; } + if (tx_ids_out) + { + std::string tx_id_str; + for (const auto &tx : ptx) + { + (*tx_ids_out).push_back(epee::string_tools::pod_to_hex(get_transaction_hash(tx.tx))); + } + } return true; } @@ -122,8 +130,20 @@ bool UnsignedTransactionImpl::checkLoadedTx(const std::function get_nu { if (!payment_id_string.empty()) payment_id_string += ", "; - payment_id_string = std::string("encrypted payment ID ") + epee::string_tools::pod_to_hex(payment_id8); - has_encrypted_payment_id = true; + + // if none of the addresses are integrated addresses, it's a dummy one + bool is_dummy = true; + for (const auto &e: cd.dests) + if (e.is_integrated) + is_dummy = false; + + if (is_dummy) + payment_id_string += std::string("dummy encrypted payment ID"); + else + { + payment_id_string = std::string("encrypted payment ID ") + epee::string_tools::pod_to_hex(payment_id8); + has_encrypted_payment_id = true; + } } else if (cryptonote::get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id)) { @@ -191,6 +211,10 @@ bool UnsignedTransactionImpl::checkLoadedTx(const std::function get_nu dests.erase(cd.change_dts.addr); } } + + if (payment_id_string.empty()) + payment_id_string = "no payment ID"; + std::string dest_string; for (auto i = dests.begin(); i != dests.end(); ) { @@ -211,7 +235,7 @@ bool UnsignedTransactionImpl::checkLoadedTx(const std::function get_nu else change_string += tr("no change"); uint64_t fee = amount - amount_to_dests; - m_confirmationMessage = (boost::format(tr("Loaded %lu transactions, for %s, fee %s, %s, %s, with min ring size %lu. %s")) % (unsigned long)get_num_txes() % cryptonote::print_money(amount) % cryptonote::print_money(fee) % dest_string % change_string % (unsigned long)min_ring_size % extra_message).str(); + m_confirmationMessage = (boost::format(tr("Loaded %lu transactions, for %s, fee %s, %s, %s, with min ring size %lu, %s. %s. Is this okay?")) % (unsigned long)get_num_txes() % cryptonote::print_money(amount) % cryptonote::print_money(fee) % dest_string % change_string % (unsigned long)min_ring_size % payment_id_string % extra_message).str(); return true; } @@ -315,4 +339,26 @@ uint64_t UnsignedTransactionImpl::minMixinCount() const return min_mixin; } +std::string UnsignedTransactionImpl::signAsString() +{ + if(m_wallet.watchOnly()) + { + m_errorString = tr("This is a watch only wallet"); + m_status = Status_Error; + return ""; + } + tools::wallet2::signed_tx_set signed_txes; + std::vector ptx; + try + { + return m_wallet.m_wallet->sign_tx_dump_to_str(m_unsigned_tx_set, ptx, signed_txes); + } + catch (const std::exception &e) + { + m_errorString = string(tr("Failed to sign transaction ")) + e.what(); + m_status = Status_Error; + return ""; + } +} + } // namespace diff --git a/src/wallet/api/unsigned_transaction.h b/src/wallet/api/unsigned_transaction.h index b07d43fb110..c55c0f91d79 100644 --- a/src/wallet/api/unsigned_transaction.h +++ b/src/wallet/api/unsigned_transaction.h @@ -52,9 +52,10 @@ class UnsignedTransactionImpl : public UnsignedTransaction std::vector recipientAddress() const override; uint64_t txCount() const override; // sign txs and save to file - bool sign(const std::string &signedFileName) override; + bool sign(const std::string &signedFileName, bool do_export_raw = false, std::vector *tx_ids_out = nullptr) override; std::string confirmationMessage() const override {return m_confirmationMessage;} uint64_t minMixinCount() const override; + std::string signAsString() override; private: // Callback function to check all loaded tx's and generate confirmationMessage diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 96ac45a01e0..97f74928056 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -30,7 +30,13 @@ #include "wallet.h" +#include "device/device.hpp" +#include "crypto/crypto.h" +#include "enote_details.h" +#include "device/device.hpp" +#include "net/net_ssl.h" #include "pending_transaction.h" +#include "string_tools.h" #include "unsigned_transaction.h" #include "transaction_history.h" #include "address_book.h" @@ -42,6 +48,7 @@ #include "mnemonics/electrum-words.h" #include "mnemonics/english.h" +#include "wallet/fee_priority.h" #include #include #include @@ -61,7 +68,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 +87,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; \ @@ -194,10 +201,11 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback } } - virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, uint64_t burnt, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time) + virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, uint64_t burnt, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time, const crypto::public_key& enote_pub_key) { std::string tx_hash = epee::string_tools::pod_to_hex(txid); + std::string enote_pub_key_str = epee::string_tools::pod_to_hex(enote_pub_key); LOG_PRINT_L3(__FUNCTION__ << ": money received. height: " << height << ", tx: " << tx_hash @@ -205,9 +213,9 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback << ", burnt: " << print_money(burnt) << ", raw_output_value: " << print_money(amount) << ", idx: " << subaddr_index); - // do not signal on received tx if wallet is not syncronized completely - if (m_listener && m_wallet->synchronized()) { - m_listener->moneyReceived(tx_hash, amount - burnt); + // do not signal on received tx if unattended wallet is not syncronized completely + if (m_listener && (m_wallet->synchronized() || !m_wallet->m_wallet->is_unattended())) { + m_listener->moneyReceived(tx_hash, amount - burnt, burnt, enote_pub_key_str, is_change, cryptonote::is_coinbase(tx)); m_listener->updated(); } } @@ -221,25 +229,27 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback << ", tx: " << tx_hash << ", amount: " << print_money(amount) << ", idx: " << subaddr_index); - // do not signal on received tx if wallet is not syncronized completely - if (m_listener && m_wallet->synchronized()) { + // do not signal on received tx if unattended wallet is not syncronized completely + if (m_listener && (m_wallet->synchronized() || !m_wallet->m_wallet->is_unattended())) { m_listener->unconfirmedMoneyReceived(tx_hash, amount); m_listener->updated(); } } virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, - uint64_t amount, const cryptonote::transaction& spend_tx, const cryptonote::subaddress_index& subaddr_index) + uint64_t amount, const cryptonote::transaction& spend_tx, const cryptonote::subaddress_index& subaddr_index, + const crypto::public_key& enote_pub_key) { // TODO; std::string tx_hash = epee::string_tools::pod_to_hex(txid); + std::string enote_pub_key_str = epee::string_tools::pod_to_hex(enote_pub_key); LOG_PRINT_L3(__FUNCTION__ << ": money spent. height: " << height << ", tx: " << tx_hash << ", amount: " << print_money(amount) << ", idx: " << subaddr_index); - // do not signal on sent tx if wallet is not syncronized completely - if (m_listener && m_wallet->synchronized()) { - m_listener->moneySpent(tx_hash, amount); + // do not signal on received tx if unattended wallet is not syncronized completely + if (m_listener && (m_wallet->synchronized() || !m_wallet->m_wallet->is_unattended())) { + m_listener->moneySpent(tx_hash, amount, enote_pub_key_str, std::make_pair(subaddr_index.major, subaddr_index.minor)); m_listener->updated(); } } @@ -294,10 +304,49 @@ 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::optional(*password); + } + } + 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; }; +WalletKeysDecryptGuard::~WalletKeysDecryptGuard() {} + +struct WalletKeysDecryptGuardImpl: public WalletKeysDecryptGuard +{ + WalletKeysDecryptGuardImpl(tools::wallet2 &w, const epee::wipeable_string &password): + u(w, &password) + {} + + ~WalletKeysDecryptGuardImpl() override = default; + + tools::wallet_keys_unlocker u; +}; + Wallet::~Wallet() {} WalletListener::~WalletListener() {} @@ -340,6 +389,20 @@ bool Wallet::paymentIdValid(const string &paiment_id) return false; } +bool Wallet::isSubaddress(const std::string &address, NetworkType nettype) +{ + cryptonote::address_parse_info info; + return get_account_address_from_str(info, static_cast(nettype), address) ? info.is_subaddress : false; +} + +std::string Wallet::getAddressFromIntegrated(const std::string &address, NetworkType nettype) +{ + cryptonote::address_parse_info info; + if (!get_account_address_from_str(info, static_cast(nettype), address)) + return ""; + return get_account_address_as_str(static_cast(nettype), info.is_subaddress, info.address); +} + bool Wallet::addressValid(const std::string &str, NetworkType nettype) { cryptonote::address_parse_info info; @@ -397,6 +460,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 @@ -424,7 +493,7 @@ void Wallet::error(const std::string &category, const std::string &str) { } ///////////////////////// WalletImpl implementation //////////////////////// -WalletImpl::WalletImpl(NetworkType nettype, uint64_t kdf_rounds) +WalletImpl::WalletImpl(NetworkType nettype, uint64_t kdf_rounds, const bool unattended /* = true */) :m_wallet(nullptr) , m_status(Wallet::Status_Ok) , m_wallet2Callback(nullptr) @@ -434,8 +503,9 @@ WalletImpl::WalletImpl(NetworkType nettype, uint64_t kdf_rounds) , m_rebuildWalletCache(false) , m_is_connected(false) , m_refreshShouldRescan(false) + , m_kdf_rounds(kdf_rounds) { - m_wallet.reset(new tools::wallet2(static_cast(nettype), kdf_rounds, true)); + m_wallet.reset(new tools::wallet2(static_cast(nettype), kdf_rounds, unattended)); m_history.reset(new TransactionHistoryImpl(this)); m_wallet2Callback.reset(new Wallet2CallbackImpl(this)); m_wallet->callback(m_wallet2Callback.get()); @@ -473,7 +543,7 @@ WalletImpl::~WalletImpl() LOG_PRINT_L1(__FUNCTION__ << " finished"); } -bool WalletImpl::create(const std::string &path, const std::string &password, const std::string &language) +bool WalletImpl::create(const std::string &path, const std::string &password, const std::string &language, bool create_address_file /* = false */, bool non_deterministic /* = false */) { clearStatus(); @@ -481,7 +551,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,12 +564,13 @@ 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); + if (!non_deterministic) + 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); - m_password = password; + recovery_val = m_wallet->generate(path, password, secret_key, /* recover */ false, non_deterministic, create_address_file); clearStatus(); } catch (const std::exception &e) { LOG_ERROR("Error creating wallet: " << e.what()); @@ -520,7 +591,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); @@ -668,8 +739,10 @@ bool WalletImpl::recoverFromKeysWithPassword(const std::string &path, LOG_PRINT_L1("Generated new view only wallet from keys"); } if(has_spendkey && !has_viewkey) { - m_wallet->generate(path, password, spendkey, true, false); + m_wallet->generate(path, password, spendkey, /* recover */ true, /* non-deterministic */ false); setSeedLanguage(language); + if (!statusOk()) + return false; LOG_PRINT_L1("Generated deterministic wallet from spend key with seed language: " + language); } @@ -678,6 +751,70 @@ bool WalletImpl::recoverFromKeysWithPassword(const std::string &path, setStatusError(string(tr("failed to generate new wallet: ")) + e.what()); return false; } + m_wallet->set_ring_database(get_default_ringdb_path(m_wallet->nettype())); + return true; +} + +bool WalletImpl::createFromJson(const std::string &json_file_path, std::string &pw_out) +{ + try + { + boost::program_options::variables_map vm; + boost::program_options::store( + boost::program_options::command_line_parser({ + nettype() == TESTNET ? "--testnet" : nettype() == STAGENET ? "--stagenet" : "", + }).options([]{ + boost::program_options::options_description options_description{}; + tools::wallet2::init_options(options_description); + return options_description; + }() + ).run(), + vm + ); + auto r = m_wallet->make_from_json(vm, m_wallet->is_unattended(), json_file_path, /* password_promper */ {}); + if (!r.first) { + setStatusError(tr("failed to generate new wallet from json")); + return false; + } + m_wallet = std::move(r.first); + pw_out = std::string(r.second.password().data(), r.second.password().size()); + LOG_PRINT_L1("Generated new wallet from json"); + } + catch (const std::exception& e) { + setStatusError(string(tr("failed to generate new wallet from json: ")) + e.what()); + return false; + } + m_wallet->set_ring_database(get_default_ringdb_path(m_wallet->nettype())); + return true; +} + +bool WalletImpl::recoverFromMultisigSeed(const std::string &path, + const std::string &password, + const std::string &language, + const std::string &multisig_seed, + const std::string seed_pass /* = "" */, + const bool do_create_address_file /* = false */) +{ + try + { + if (seed_pass.empty()) + m_wallet->generate(path, password, multisig_seed, do_create_address_file); + else + { + crypto::secret_key key; + crypto::cn_slow_hash(seed_pass.data(), seed_pass.size(), (crypto::hash&)key); + sc_reduce32((unsigned char*)key.data); + const epee::wipeable_string &msig_keys = m_wallet->decrypt(std::string(multisig_seed.data(), multisig_seed.size()), key, true); + m_wallet->generate(path, password, msig_keys, do_create_address_file); + } + setSeedLanguage(language); + LOG_PRINT_L1("Generated new multisig wallet from multisig seed"); + } + catch (const std::exception& e) { + setStatusError(string(tr("failed to generate new multisig wallet: ")) + e.what()); + return false; + } + m_wallet->set_ring_database(get_default_ringdb_path(m_wallet->nettype())); return true; } @@ -713,7 +850,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 +863,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 +898,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; @@ -808,18 +947,6 @@ std::string WalletImpl::seed(const std::string& seed_offset) const return std::string(seed.data(), seed.size()); // TODO } -std::string WalletImpl::getSeedLanguage() const -{ - return m_wallet->get_seed_language(); -} - -void WalletImpl::setSeedLanguage(const std::string &arg) -{ - if (checkBackgroundSync("cannot set seed language")) - return; - m_wallet->set_seed_language(arg); -} - int WalletImpl::status() const { boost::lock_guard l(m_statusMutex); @@ -849,7 +976,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 +992,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 +1003,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 @@ -969,13 +1096,6 @@ bool WalletImpl::init(const std::string &daemon_address, uint64_t upper_transact return doInit(daemon_address, proxy_address, upper_transaction_size_limit, use_ssl); } -void WalletImpl::setRefreshFromBlockHeight(uint64_t refresh_from_block_height) -{ - if (checkBackgroundSync("cannot change refresh height")) - return; - m_wallet->set_refresh_from_block_height(refresh_from_block_height); -} - void WalletImpl::setRecoveringFromSeed(bool recoveringFromSeed) { m_recoveringFromSeed = recoveringFromSeed; @@ -986,19 +1106,26 @@ void WalletImpl::setRecoveringFromDevice(bool recoveringFromDevice) m_recoveringFromDevice = recoveringFromDevice; } -void WalletImpl::setSubaddressLookahead(uint32_t major, uint32_t minor) +uint64_t WalletImpl::balance(uint32_t accountIndex) const { - m_wallet->set_subaddress_lookahead(major, minor); + return m_wallet->balance(accountIndex, false); } -uint64_t WalletImpl::balance(uint32_t accountIndex) const +std::map WalletImpl::balancePerSubaddress(uint32_t accountIndex /* = 0 */) const { - return m_wallet->balance(accountIndex, false); + return m_wallet->balance_per_subaddress(accountIndex, false); } -uint64_t WalletImpl::unlockedBalance(uint32_t accountIndex) const +uint64_t WalletImpl::unlockedBalance(uint32_t accountIndex /* = 0 */, + uint64_t *blocks_to_unlock /* = NULL */, + uint64_t *time_to_unlock /* = NULL */) const { - return m_wallet->unlocked_balance(accountIndex, false); + return m_wallet->unlocked_balance(accountIndex, false, blocks_to_unlock, time_to_unlock); +} + +std::map>> WalletImpl::unlockedBalancePerSubaddress(uint32_t accountIndex /* = 0 */) const +{ + return m_wallet->unlocked_balance_per_subaddress(accountIndex, false); } uint64_t WalletImpl::blockChainHeight() const @@ -1063,13 +1190,17 @@ bool WalletImpl::synchronized() const return m_synchronized; } -bool WalletImpl::refresh() +bool WalletImpl::refresh(std::uint64_t start_height /* = 0 */, + bool check_pool /* = true */, + bool try_incremental /* = false */, + std::uint64_t max_blocks /* = std::numeric_limits::max() */, + std::uint64_t *blocks_fetched_out /* = nullptr */, + bool *received_money_out /* = nullptr */) { 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 status() == Status_Ok; + bool did_error_occur; + doRefresh(start_height, check_pool, try_incremental, max_blocks, &did_error_occur, blocks_fetched_out, received_money_out); + return !did_error_occur; } void WalletImpl::refreshAsync() @@ -1079,21 +1210,26 @@ void WalletImpl::refreshAsync() m_refreshCV.notify_one(); } -bool WalletImpl::rescanBlockchain() +bool WalletImpl::rescanBlockchain(bool do_hard_rescan /* = false */, bool do_keep_key_images /* = false */, bool do_skip_refresh /* = false */) { if (checkBackgroundSync("cannot rescan blockchain")) return false; clearStatus(); m_refreshShouldRescan = true; - doRefresh(); - return status() == Status_Ok; + m_do_hard_rescan = do_hard_rescan; + m_do_keep_key_images_on_rescan = do_keep_key_images; + if (!do_skip_refresh) + doRefresh(); + return statusOk(); } -void WalletImpl::rescanBlockchainAsync() +void WalletImpl::rescanBlockchainAsync(bool do_hard_rescan /* = false */, bool do_keep_key_images /* = false */) { if (checkBackgroundSync("cannot rescan blockchain")) return; m_refreshShouldRescan = true; + m_do_hard_rescan = do_hard_rescan; + m_do_keep_key_images_on_rescan = do_keep_key_images; refreshAsync(); } @@ -1134,6 +1270,29 @@ UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_file return transaction; } +UnsignedTransaction *WalletImpl::loadUnsignedTxFromStr(const std::string &unsigned_tx_str) +{ + clearStatus(); + std::unique_ptr transaction(new UnsignedTransactionImpl(*this)); + if (checkBackgroundSync("cannot load tx") || !m_wallet->parse_unsigned_tx_from_str(unsigned_tx_str, transaction->m_unsigned_tx_set)) + { + setStatusError(tr("Failed to load unsigned transaction")); + transaction->m_status = UnsignedTransaction::Status::Status_Error; + transaction->m_errorString = errorString(); + + return transaction.release(); + } + + // Check tx data and construct confirmation message + std::string extra_message; + if (!std::get<2>(transaction->m_unsigned_tx_set.transfers).empty()) + extra_message = (boost::format("%u outputs to import. ") % (unsigned)std::get<2>(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); + setStatus(transaction->status(), transaction->errorString()); + + return transaction.release(); +} + bool WalletImpl::submitTransaction(const string &fileName) { clearStatus(); if (checkBackgroundSync("cannot submit tx")) @@ -1142,10 +1301,10 @@ 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; } - + if(!transaction->commit()) { setStatusError(transaction->m_errorString); return false; @@ -1156,7 +1315,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; @@ -1181,7 +1340,31 @@ bool WalletImpl::exportKeyImages(const string &filename, bool all) return true; } -bool WalletImpl::importKeyImages(const string &filename) +std::string WalletImpl::exportKeyImagesAsString(bool all /* = false */) +{ + clearStatus(); + if (watchOnly()) + { + setStatusError(tr("Wallet is view only")); + return ""; + } + if (checkBackgroundSync("cannot export key images")) + return ""; + + try + { + return m_wallet->export_key_images_to_str(all); + } + catch (const std::exception &e) + { + LOG_ERROR("Error exporting key images: " << e.what()); + setStatusError(e.what()); + return ""; + } + return ""; +} + +bool WalletImpl::importKeyImages(const std::string &filename, std::uint64_t *spent_out /* = nullptr */, std::uint64_t *unspent_out /* = nullptr */, std::uint64_t *import_height /* = nullptr */) { if (checkBackgroundSync("cannot import key images")) return false; @@ -1191,10 +1374,16 @@ bool WalletImpl::importKeyImages(const string &filename) } try { - uint64_t spent = 0, unspent = 0; - uint64_t height = m_wallet->import_key_images(filename, spent, unspent); - LOG_PRINT_L2("Signed key images imported to height " << height << ", " - << print_money(spent) << " spent, " << print_money(unspent) << " unspent"); + uint64_t spent = 0, unspent = 0, height; + if (!spent_out) + spent_out = &spent; + if (!unspent_out) + unspent_out = &unspent; + if (!import_height) + import_height = &height; + *import_height = m_wallet->import_key_images(filename, *spent_out, *unspent_out); + LOG_PRINT_L2("Signed key images imported to height " << *import_height << ", " + << print_money(*spent_out) << " spent, " << print_money(*unspent_out) << " unspent"); } catch (const std::exception &e) { @@ -1206,6 +1395,32 @@ bool WalletImpl::importKeyImages(const string &filename) return true; } +bool WalletImpl::importKeyImagesFromStr(const std::string &data) +{ + clearStatus(); + if (checkBackgroundSync("cannot import key images")) + return false; + if (!trustedDaemon()) { + setStatusError(tr("Key images can only be imported with a trusted daemon")); + return false; + } + try + { + uint64_t spent = 0, unspent = 0; + uint64_t height = m_wallet->import_key_images_from_str(data, spent, unspent); + LOG_PRINT_L2("Signed key images imported to height " << height << ", " + << print_money(spent) << " spent, " << print_money(unspent) << " unspent"); + } + catch (const std::exception &e) + { + LOG_ERROR("Error importing key images: " << e.what()); + setStatusError(string(tr("Failed to import key images: ")) + e.what()); + return false; + } + + return true; +} + bool WalletImpl::exportOutputs(const string &filename, bool all) { if (checkBackgroundSync("cannot export outputs")) @@ -1260,6 +1475,8 @@ bool WalletImpl::importOutputs(const string &filename) try { size_t n_outputs = m_wallet->import_outputs_from_str(data); + if (!statusOk()) + throw runtime_error(errorString()); LOG_PRINT_L2(std::to_string(n_outputs) << " outputs imported"); } catch (const std::exception &e) @@ -1272,7 +1489,7 @@ bool WalletImpl::importOutputs(const string &filename) return true; } -bool WalletImpl::scanTransactions(const std::vector &txids) +bool WalletImpl::scanTransactions(const std::vector &txids, bool *wont_reprocess_recent_txs_via_untrusted_daemon /* = nullptr */ ) { if (checkBackgroundSync("cannot scan transactions")) return false; @@ -1301,6 +1518,8 @@ bool WalletImpl::scanTransactions(const std::vector &txids) } catch (const tools::error::wont_reprocess_recent_txs_via_untrusted_daemon &e) { + if (wont_reprocess_recent_txs_via_untrusted_daemon) + *wont_reprocess_recent_txs_via_untrusted_daemon = true; setStatusError(e.what()); return false; } @@ -1476,7 +1695,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"); } @@ -1576,7 +1795,7 @@ bool WalletImpl::hasMultisigPartialKeyImages() const { return false; } -PendingTransaction* WalletImpl::restoreMultisigTransaction(const string& signData) { +PendingTransaction* WalletImpl::restoreMultisigTransaction(const string& signData, bool ask_for_confirmation /* = false */) { try { clearStatus(); checkMultisigWalletReady(m_wallet); @@ -1587,7 +1806,7 @@ PendingTransaction* WalletImpl::restoreMultisigTransaction(const string& signDat } tools::wallet2::multisig_tx_set txSet; - if (!m_wallet->load_multisig_tx(binary, txSet, {})) { + if (!m_wallet->load_multisig_tx(binary, txSet, {}, ask_for_confirmation)) { throw runtime_error("couldn't parse multisig transaction data"); } @@ -1595,6 +1814,13 @@ PendingTransaction* WalletImpl::restoreMultisigTransaction(const string& signDat ptx->m_pending_tx = txSet.m_ptx; ptx->m_signers = txSet.m_signers; + // Check tx data and construct confirmation message + if (ask_for_confirmation) { + std::string extra_message; + ptx->checkLoadedTx([&ptx](){return ptx->txCount();}, [&ptx](size_t n)->const tools::wallet2::tx_construction_data&{return ptx->m_pending_tx[n].construction_data;}, extra_message); + setStatus(ptx->status(), ptx->errorString()); + } + return ptx; } catch (exception& e) { LOG_ERROR("Error on restoring multisig transaction: " << e.what()); @@ -1614,7 +1840,7 @@ PendingTransaction* WalletImpl::restoreMultisigTransaction(const string& signDat // - unconfirmed_transfer_details; // - confirmed_transfer_details) -PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector &dst_addr, const string &payment_id, optional> amount, uint32_t mixin_count, PendingTransaction::Priority priority, uint32_t subaddr_account, std::set subaddr_indices) +PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector &dst_addr, const string &payment_id, optional> amount, uint32_t mixin_count, PendingTransaction::Priority priority, uint32_t subaddr_account, std::set subaddr_indices /* = {} */, std::set subtract_fee_from_outputs /* = {} */, const std::string key_image /* = "" */, const size_t outputs /* = 1 */, const std::uint64_t below /* = 0 */) { clearStatus(); @@ -1627,6 +1853,9 @@ 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,15 +1920,30 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector 0 ? mixin_count : m_wallet->default_mixin(); - fake_outs_count = m_wallet->adjust_mixin(mixin_count); + size_t fake_outs_count = mixin_count > 0 ? mixin_count : defaultMixin(); + fake_outs_count = m_wallet->adjust_mixin(fake_outs_count); if (amount) { transaction->m_pending_tx = m_wallet->create_transactions_2(dsts, fake_outs_count, adjusted_priority, - extra, subaddr_account, subaddr_indices); - } else { - transaction->m_pending_tx = m_wallet->create_transactions_all(0, info.address, info.is_subaddress, 1, fake_outs_count, + extra, subaddr_account, subaddr_indices, subtract_fee_from_outputs); + } + else if (!key_image.empty()) { // sweep_single + crypto::key_image ki; + if (!epee::string_tools::hex_to_pod(key_image, ki)) + { + setStatusError(tr("Failed to parse key image")); + break; + } + transaction->m_pending_tx = m_wallet->create_transactions_single(ki, + info.address, + info.is_subaddress, + outputs, + fake_outs_count, + tools::fee_priority_utilities::from_integral(priority), + extra); + } else { // sweep_account, sweep_all, sweep_below + transaction->m_pending_tx = m_wallet->create_transactions_all(below, info.address, info.is_subaddress, outputs, fake_outs_count, adjusted_priority, extra, subaddr_account, subaddr_indices); } @@ -1711,7 +1955,6 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vectorm_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.")); @@ -1776,8 +2019,11 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vectorm_status, transaction->m_errorString); - // Resume refresh thread - startRefresh(); + // Resume refresh thread for unattended wallet (like GUI). + // A non-unattended wallet (like CLI) could be waiting for confirmation prompt + // and a refresh could mess up the output + if (m_wallet->is_unattended()) + startRefresh(); return transaction; } @@ -1805,7 +2051,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.")); @@ -1817,7 +2062,7 @@ PendingTransaction *WalletImpl::createSweepUnmixableTransaction() setStatusError(""); std::ostringstream writer; - writer << boost::format(tr("not enough money to transfer, available only %s, sent amount %s")) % + writer << boost::format(tr("not enough unlocked money to transfer, available only %s, sent amount %s")) % print_money(e.available()) % print_money(e.tx_amount()); setStatusError(writer.str()); @@ -1886,16 +2131,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(static_cast(priority)), m_wallet->get_fee_quantization_mask()); } @@ -1959,7 +2204,10 @@ bool WalletImpl::setUserNote(const std::string &txid, const std::string ¬e) return false; cryptonote::blobdata txid_data; if(!epee::string_tools::parse_hexstr_to_binbuff(txid, txid_data) || txid_data.size() != sizeof(crypto::hash)) + { + setStatusError(tr("failed to parse tx_id")); return false; + } const crypto::hash htxid = *reinterpret_cast(txid_data.data()); m_wallet->set_tx_note(htxid, note); @@ -1972,7 +2220,10 @@ std::string WalletImpl::getUserNote(const std::string &txid) const return ""; cryptonote::blobdata txid_data; if(!epee::string_tools::parse_hexstr_to_binbuff(txid, txid_data) || txid_data.size() != sizeof(crypto::hash)) + { + setStatusError(tr("failed to parse tx_id")); return ""; + } const crypto::hash htxid = *reinterpret_cast(txid_data.data()); return m_wallet->get_tx_note(htxid); @@ -2218,13 +2469,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,17 +2491,27 @@ 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 +bool WalletImpl::verifySignedMessage(const std::string &message, + const std::string &address, + const std::string &signature, + bool *is_old_out /* = nullptr */, + std::string *signature_type_out /* = nullptr */) const { cryptonote::address_parse_info info; if (!cryptonote::get_account_address_from_str(info, m_wallet->nettype(), address)) return false; - - return m_wallet->verify(message, info.address, signature).valid; + tools::wallet2::message_signature_result_t result = m_wallet->verify(message, info.address, signature); + if (is_old_out) + *is_old_out = result.old; + if (signature_type_out) + *signature_type_out = (result.type == tools::wallet2::sign_with_spend_key + ? "spend key" : result.type == tools::wallet2::sign_with_view_key + ? "view key" : "unknown key combination (suspicious)"); + return result.valid; } std::string WalletImpl::signMultisigParticipant(const std::string &message) const @@ -2295,9 +2558,13 @@ bool WalletImpl::verifyMessageWithPublicKey(const std::string &message, const st return false; } -bool WalletImpl::connectToDaemon() +bool WalletImpl::connectToDaemon(uint32_t *version /* = NULL*/, + bool *ssl /* = NULL */, + uint32_t timeout /* = 20000 */, // TODO : can we put DEFAULT_CONNECTION_TIMEOUT_MILLIS into wallet2_api.h and have it as default param? + bool *wallet_is_outdated /* = NULL */, + bool *daemon_is_outdated /* = NULL */) { - bool result = m_wallet->check_connection(NULL, NULL, DEFAULT_CONNECTION_TIMEOUT_MILLIS); + bool result = m_wallet->check_connection(version, ssl, timeout, wallet_is_outdated, daemon_is_outdated); if (!result) { setStatusError("Error connecting to daemon at " + m_wallet->get_daemon_address()); } else { @@ -2414,8 +2681,16 @@ void WalletImpl::refreshThreadFunc() LOG_PRINT_L3(__FUNCTION__ << ": refresh thread stopped"); } -void WalletImpl::doRefresh() +void WalletImpl::doRefresh(std::uint64_t start_height /* = 0 */, + bool check_pool /* = true */, + bool try_incremental /* = false */, + std::uint64_t max_blocks /* = std::numeric_limits::max() */, + bool *error_out /* = nullptr */, + std::uint64_t *blocks_fetched_out /* = nullptr */, + bool *received_money_out /* = nullptr */) { + if (error_out) + *error_out = false; bool rescan = m_refreshShouldRescan.exchange(false); // synchronizing async and sync refresh calls boost::lock_guard guarg(m_refreshMutex2); @@ -2425,8 +2700,16 @@ void WalletImpl::doRefresh() // Disable refresh if wallet is disconnected or daemon isn't synced. if (daemonSynced()) { if(rescan) - m_wallet->rescan_blockchain(false); - m_wallet->refresh(trustedDaemon()); + m_wallet->rescan_blockchain(m_do_hard_rescan, /* refresh */ false, m_do_keep_key_images_on_rescan); + std::uint64_t blocks_fetched_ignored; + bool received_money_ignored; + m_wallet->refresh(trustedDaemon(), + start_height, + (blocks_fetched_out ? *blocks_fetched_out : blocks_fetched_ignored), + (received_money_out ? *received_money_out : received_money_ignored), + check_pool, + try_incremental, + max_blocks); m_synchronized = m_wallet->is_synced(); // assuming if we have empty history, it wasn't initialized yet // for further history changes client need to update history in @@ -2437,11 +2720,40 @@ void WalletImpl::doRefresh() } else { LOG_PRINT_L3(__FUNCTION__ << ": skipping refresh - daemon is not synced"); } - } catch (const std::exception &e) { - setStatusError(e.what()); + } catch (const tools::error::daemon_busy&) { + setStatusError(tr("daemon is busy. Please try again later.")); + break; + } catch (const tools::error::no_connection_to_daemon&) { + setStatusError(tr("no connection to daemon. Please make sure daemon is running.")); + break; + } catch (const tools::error::deprecated_rpc_access&) { + setStatusError(tr("Daemon requires deprecated RPC payment. See https://github.com/monero-project/monero/issues/8722")); + break; + } catch (const tools::error::wallet_rpc_error& e) { + LOG_ERROR("RPC error: " << e.to_string()); + setStatusError(std::string(tr("RPC error: ")) + e.what()); + break; + } catch (const tools::error::refresh_error& e) { + LOG_ERROR("refresh error: " << e.to_string()); + setStatusError(std::string(tr("refresh error: ")) + e.what()); + break; + } catch (const tools::error::wallet_internal_error& e) { + LOG_ERROR("internal error: " << e.to_string()); + setStatusError(std::string(tr("internal error: ")) + e.what()); + break; + } catch (const std::exception& e) { + LOG_ERROR("unexpected error: " << e.what()); + setStatusError(std::string(tr("unexpected error: ")) + e.what()); + break; + } catch (...) { + LOG_ERROR("unknown error"); + setStatusError(tr("unknown error")); break; }while(!rescan && (rescan=m_refreshShouldRescan.exchange(false))); // repeat if not rescanned and rescan was requested + if (error_out) + *error_out = !statusOk(); + if (m_wallet2Callback->getListener()) { m_wallet2Callback->getListener()->refreshed(); } @@ -2502,6 +2814,11 @@ void WalletImpl::pendingTxPostProcess(PendingTransactionImpl * pending) m_wallet->cold_sign_tx(pending->m_pending_tx, exported_txs, dsts_info, pending->m_tx_device_aux); pending->m_key_images = exported_txs.key_images; pending->m_pending_tx = exported_txs.ptx; + + std::string extra_message; + if (!exported_txs.key_images.empty()) + extra_message = (boost::format("%u key images to import. ") % (unsigned)exported_txs.key_images.size()).str(); + pending->checkLoadedTx([&pending](){return pending->txCount();}, [&pending](size_t n)->const tools::wallet2::tx_construction_data&{return pending->m_pending_tx[n].construction_data;}, extra_message); } bool WalletImpl::doInit(const string &daemon_address, const std::string &proxy_address, uint64_t upper_transaction_size_limit, bool ssl) @@ -2513,11 +2830,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); @@ -2564,21 +2881,61 @@ std::string WalletImpl::getDefaultDataDir() const bool WalletImpl::rescanSpent() { - clearStatus(); - if (checkBackgroundSync("cannot rescan spent")) - return false; - if (!trustedDaemon()) { - 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()); - setStatusError(e.what()); - return false; - } - return true; + clearStatus(); + if (checkBackgroundSync("cannot rescan spent")) + return false; + if (!trustedDaemon()) { + setStatusError(tr("Rescan spent can only be used with a trusted daemon")); + return false; + } + try { + m_wallet->rescan_spent(); + } + catch (const tools::error::daemon_busy&) + { + setStatusError(tr("daemon is busy. Please try again later.")); + return false; + } + catch (const tools::error::no_connection_to_daemon&) + { + setStatusError(tr("no connection to daemon. Please make sure daemon is running.")); + return false; + } + catch (const tools::error::deprecated_rpc_access&) + { + setStatusError(tr("Daemon requires deprecated RPC payment. See https://github.com/monero-project/monero/issues/8722")); + return false; + } + catch (const tools::error::is_key_image_spent_error&) + { + setStatusError(tr("failed to get spent status")); + return false; + } + catch (const tools::error::wallet_rpc_error& e) + { + LOG_ERROR("RPC error: " << e.to_string()); + setStatusError(std::string(tr("RPC error: ")) + e.to_string()); + return false; + } + catch (const std::exception& e) + { + LOG_ERROR(__FUNCTION__ << "unexpected error: " << e.what()); + setStatusError(std::string(tr("unexpected error: ")) + e.what()); + return false; + } + catch (...) + { + LOG_ERROR(__FUNCTION__ << "unknown error"); + setStatusError(tr("unknonw error: ")); + return false; + } + + return true; +} + +NetworkType WalletImpl::nettype() const +{ + return static_cast(m_wallet->nettype()); } void WalletImpl::setOffline(bool offline) @@ -2598,8 +2955,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) { @@ -2769,6 +3137,11 @@ bool WalletImpl::isKeysFileLocked() return m_wallet->is_keys_file_locked(); } +std::unique_ptr WalletImpl::createKeysDecryptGuard(const std::string_view &password) +{ + return std::unique_ptr(new WalletKeysDecryptGuardImpl(*m_wallet, epee::wipeable_string(password.data(), password.size()))); +} + uint64_t WalletImpl::coldKeyImageSync(uint64_t &spent, uint64_t &unspent) { return m_wallet->cold_key_image_sync(spent, unspent); @@ -2776,6 +3149,7 @@ uint64_t WalletImpl::coldKeyImageSync(uint64_t &spent, uint64_t &unspent) void WalletImpl::deviceShowAddress(uint32_t accountIndex, uint32_t addressIndex, const std::string &paymentId) { + clearStatus(); boost::optional payment_id_param = boost::none; if (!paymentId.empty()) { @@ -2783,7 +3157,8 @@ void WalletImpl::deviceShowAddress(uint32_t accountIndex, uint32_t addressIndex, bool res = tools::wallet2::parse_short_payment_id(paymentId, payment_id); if (!res) { - throw runtime_error("Invalid payment ID"); + setStatusError(tr("Invalid payment ID")); + return; } payment_id_param = payment_id; } @@ -2818,4 +3193,1383 @@ uint64_t WalletImpl::getBytesSent() return m_wallet->get_bytes_sent(); } +//------------------------------------------------------------------------------------------------------------------- +std::string WalletImpl::getMultisigSeed(const std::string &seed_offset) const +{ + clearStatus(); + + if (checkBackgroundSync("cannot get multisig seed")) + return std::string(); + + 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(const std::string &key_image) +{ + clearStatus(); + + crypto::key_image ki_pod; + if (!epee::string_tools::hex_to_pod(key_image, ki_pod)) + { + setStatusError((boost::format(tr("Failed to parse key image `%s`")) % key_image).str()); + return; + } + + try + { + m_wallet->freeze(ki_pod); + } + 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::freezeByPubKey(const std::string &enote_pub_key) +{ + clearStatus(); + + crypto::public_key pk; + if (!epee::string_tools::hex_to_pod(enote_pub_key, pk)) + { + setStatusError((boost::format(tr("Failed to parse public key `%s`")) % enote_pub_key).str()); + return; + } + + try + { + m_wallet->freeze(m_wallet->get_output_index(pk)); + } + catch (const std::exception &e) + { + LOG_ERROR(__FUNCTION__ << " error: " << e.what()); + setStatusError((boost::format(tr("Failed to freeze enote with public key `%s`: %s")) % enote_pub_key % e.what()).str()); + } +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::thaw(const std::string &key_image) +{ + clearStatus(); + + crypto::key_image ki_pod; + if (!epee::string_tools::hex_to_pod(key_image, ki_pod)) + { + setStatusError((boost::format(tr("Failed to parse key image `%s`")) % key_image).str()); + return; + } + + try + { + m_wallet->thaw(ki_pod); + } + 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()); + } +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::thawByPubKey(const std::string &enote_pub_key) +{ + clearStatus(); + + crypto::public_key pk; + if (!epee::string_tools::hex_to_pod(enote_pub_key, pk)) + { + setStatusError((boost::format(tr("Failed to parse public key `%s`")) % enote_pub_key).str()); + return; + } + + try + { + m_wallet->thaw(m_wallet->get_output_index(pk)); + } + catch (const std::exception &e) + { + LOG_ERROR(__FUNCTION__ << " error: " << e.what()); + setStatusError((boost::format(tr("Failed to thaw enote with public key `%s`: %s")) % enote_pub_key % e.what()).str()); + } +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::isFrozen(const std::string &key_image) const +{ + clearStatus(); + + crypto::key_image ki_pod; + if (!epee::string_tools::hex_to_pod(key_image, ki_pod)) + { + setStatusError((boost::format(tr("Failed to parse key image `%s`")) % key_image).str()); + return false; + } + + try + { + return m_wallet->frozen(ki_pod); + } + 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.is_unattended = m_wallet->is_unattended(); + wallet_state.daemon_address = m_wallet->get_daemon_address(); + wallet_state.ring_database = m_wallet->get_ring_database(); + wallet_state.n_enotes = m_wallet->get_num_transfer_details(); + + return wallet_state; +} +//------------------------------------------------------------------------------------------------------------------- +Wallet::DeviceState WalletImpl::getDeviceState() const +{ + DeviceState device_state{}; + auto &device = m_wallet->get_account().get_device(); + + device_state.key_on_device = m_wallet->key_on_device(); + device_state.device_name = device.get_name(); + device_state.has_tx_cold_sign = device.has_tx_cold_sign(); + device_state.has_ki_live_refresh = device.has_ki_live_refresh(); + device_state.has_ki_cold_sync = device.has_ki_cold_sync(); + device_state.last_ki_sync = m_wallet->get_device_last_key_image_sync(); + device_state.protocol = static_cast(device.device_protocol()); + device_state.device_type = static_cast(device.get_type()); + + return device_state; +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::rewriteWalletFile(const std::string &wallet_name, const std::string_view &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_view &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()); + } +} +//------------------------------------------------------------------------------------------------------------------- +std::vector> WalletImpl::getEnoteDetails() const +{ + std::vector> enotes_details{}; + std::size_t n_transfers = m_wallet->get_num_transfer_details(); + enotes_details.reserve(n_transfers); + + for (std::size_t i = 0; i < n_transfers; ++i) + { + enotes_details.push_back(getEnoteDetails(i)); + } + return enotes_details; +} +//------------------------------------------------------------------------------------------------------------------- +std::unique_ptr WalletImpl::getEnoteDetails(const std::string &enote_pub_key) const +{ + clearStatus(); + + crypto::public_key pk{}; + if (!epee::string_tools::hex_to_pod(enote_pub_key, pk)) + { + setStatusError((boost::format(tr("Failed to parse public key `%s`")) % enote_pub_key).str()); + return nullptr; + } + + std::size_t enote_index{}; + try { enote_index = m_wallet->get_output_index(pk); } + catch (const exception &e) + { + setStatusError(std::string(tr("Could not find index of enote: ")) + e.what()); + return nullptr; + } + return getEnoteDetails(enote_index); +} +//------------------------------------------------------------------------------------------------------------------- +std::unique_ptr WalletImpl::getEnoteDetails(const std::size_t enote_index) const +{ + clearStatus(); + + std::unique_ptr enote_details = nullptr; + tools::wallet2::transfer_details td{}; + try { td = m_wallet->get_transfer_details(enote_index); } + catch (const exception &e) + { + setStatusError(std::string(tr("Could not find enote details for index: ")) + std::to_string(enote_index)); + return nullptr; + } + + std::unique_ptr ed(new EnoteDetailsImpl); + crypto::public_key enote_pub_key{}; + cryptonote::get_output_public_key(td.m_tx.vout[td.m_internal_output_index], enote_pub_key); + auto view_tag = cryptonote::get_output_view_tag(td.m_tx.vout[td.m_internal_output_index]); + + ed->m_onetime_address = epee::string_tools::pod_to_hex(enote_pub_key); + ed->m_view_tag = view_tag ? epee::string_tools::pod_to_hex(*view_tag) : ""; + ed->m_payment_id = getPaymentIdFromExtra(td.m_tx.extra); + ed->m_block_height = td.m_block_height; + ed->m_unlock_time = td.m_tx.unlock_time; + ed->m_is_unlocked = m_wallet->is_transfer_unlocked(td); + 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::TxProtocol::TxProtocol_RingCT : EnoteDetails::TxProtocol::TxProtocol_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()); + ed->m_subaddress_index_major = td.m_subaddr_index.major; + ed->m_subaddress_index_minor = td.m_subaddr_index.minor; + 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 = std::move(ed); + return enote_details; +} +//------------------------------------------------------------------------------------------------------------------- +std::string WalletImpl::convertMultisigTxToStr(const PendingTransaction &multisig_ptx) const +{ + clearStatus(); + + if (checkBackgroundSync("cannot get multisig seed")) + return std::string(); + + 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(); + + if (checkBackgroundSync("cannot get multisig seed")) + return false; + + 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; +} +//------------------------------------------------------------------------------------------------------------------- +PendingTransaction* WalletImpl::parseTxFromStr(const std::string &signed_tx_str) +{ + clearStatus(); + std::unique_ptr ptx(new PendingTransactionImpl(*this)); + std::shared_ptr signed_tx_set_out = std::make_shared(); + bool r = m_wallet->parse_tx_from_str(signed_tx_str, + ptx->m_pending_tx, + /* accept_func = */ NULL, + signed_tx_set_out.get(), + /* do_handle_key_images = */ false); + if (!r) + { + setStatusError(tr("Failed to parse signed tx from str")); + ptx->m_status = PendingTransaction::Status::Status_Error; + ptx->m_errorString = errorString(); + return ptx.release(); + } + + ptx->m_tx_key_images = signed_tx_set_out->tx_key_images; + + // Check tx data and construct confirmation message + std::string extra_message; + ptx->checkLoadedTx([&ptx](){return ptx->txCount();}, [&ptx](size_t n)->const tools::wallet2::tx_construction_data & {return ptx->m_pending_tx[n].construction_data;}, extra_message); + setStatus(ptx->status(), ptx->errorString()); + + return ptx.release(); +} +//------------------------------------------------------------------------------------------------------------------- +PendingTransaction* WalletImpl::parseMultisigTxFromStr(const std::string &multisig_tx_str) +{ + clearStatus(); + + std::unique_ptr ptx(new PendingTransactionImpl(*this)); + try + { + checkMultisigWalletReady(m_wallet); + + 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->m_pending_tx = multisig_tx.m_ptx; + ptx->m_signers = multisig_tx.m_signers; + } + catch (const exception &e) + { + setStatusError(e.what()); + return nullptr; + } + + return ptx.release(); +} +//------------------------------------------------------------------------------------------------------------------- +std::uint64_t WalletImpl::getFeeMultiplier(std::uint32_t priority, int fee_algorithm) const +{ + clearStatus(); + + try + { + return m_wallet->get_fee_multiplier(tools::fee_priority_utilities::from_integral(priority), + static_cast(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 tools::fee_priority_utilities::as_integral(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(); + if (checkBackgroundSync("cannot export enotes")) + return ""; + if (m_wallet->key_on_device()) + { + setStatusError(string(tr("Not supported on HW wallets."))); + return ""; + } + + 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(); + if (checkBackgroundSync("cannot import enotes")) + return 0; + if (m_wallet->key_on_device()) + { + setStatusError(string(tr("Not supported on HW wallets."))); + return 0; + } + + try + { + return m_wallet->import_outputs_from_str(enotes_str); + } + catch (const std::exception &e) + { + setStatusError(string(tr("Failed to import enotes from 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) }; +} +//------------------------------------------------------------------------------------------------------------------- +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); +} +//------------------------------------------------------------------------------------------------------------------- +std::string WalletImpl::getDeviceDerivationPath() const +{ + return m_wallet->device_derivation_path(); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setDeviceDerivationPath(std::string device_derivation_path) +{ + m_wallet->device_derivation_path(device_derivation_path); +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::setDaemon(const std::string &daemon_address, + const std::string &daemon_username /* = "" */, + const std::string &daemon_password /* = "" */, + bool trusted_daemon /* = false */, + Wallet::SSLSupport ssl_support /* = Wallet::SSLSupport::SSLSupport_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), ssl_ca_file_path}; + + ssl_options.support = static_cast(ssl_support); + + 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; +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::verifyPassword(const std::string_view &password) +{ + clearStatus(); + + bool r = false; + bool was_locked = false; + + if (isKeysFileLocked()) + { + was_locked = true; + unlockKeysFile(); + } + + try + { + bool no_spend_key = getDeviceState().protocol == DeviceState::DeviceProtocol::DeviceProtocol_Cold || watchOnly() || multisig().isMultisig || isBackgroundWallet(); + r = tools::wallet2::verify_password(keysFilename(), + epee::wipeable_string(password.data(), password.size()), + no_spend_key, + hw::get_device("default"), + m_kdf_rounds); + } + catch (const std::exception &e) + { + setStatusError(string(tr("Failed to verify password: ")) + e.what()); + } + + if (was_locked) + lockKeysFile(); + + return r; +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::encryptKeys(const std::string_view &password) +{ + m_wallet->encrypt_keys(epee::wipeable_string(password.data(), password.size())); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::decryptKeys(const std::string_view &password) +{ + m_wallet->decrypt_keys(epee::wipeable_string(password.data(), password.size())); +} +//------------------------------------------------------------------------------------------------------------------- +std::uint64_t WalletImpl::getMinRingSize() const +{ + try { return m_wallet->get_min_ring_size(); } + catch (const std::exception &e) + { setStatusError(std::string(tr("failed to get min ring size")) + e.what()); } + return 0; +} +std::uint64_t WalletImpl::getMaxRingSize() const +{ + try { return m_wallet->get_max_ring_size(); } + catch (const std::exception &e) + { setStatusError(std::string(tr("failed to get max ring size")) + e.what()); } + return 0; +} +std::uint64_t WalletImpl::adjustMixin(const std::uint64_t fake_outs_count) const +{ + return m_wallet->adjust_mixin(fake_outs_count); +} +//------------------------------------------------------------------------------------------------------------------- +std::uint64_t WalletImpl::getDaemonAdjustedTime() const +{ + return m_wallet->get_daemon_adjusted_time(); +} +//------------------------------------------------------------------------------------------------------------------- +std::uint64_t WalletImpl::getLastBlockReward() const +{ + return m_wallet->get_last_block_reward(); +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::hasUnknownKeyImages() const +{ + return m_wallet->has_unknown_key_images(); +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::getExplicitRefreshFromBlockHeight() const +{ + return m_wallet->explicit_refresh_from_block_height(); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setExplicitRefreshFromBlockHeight(bool do_explicit_refresh) +{ + m_wallet->explicit_refresh_from_block_height(do_explicit_refresh); +} +//------------------------------------------------------------------------------------------------------------------- +// Wallet Settings getter/setter +//------------------------------------------------------------------------------------------------------------------- +std::string WalletImpl::getSeedLanguage() const +{ + return m_wallet->get_seed_language(); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setSeedLanguage(const std::string &arg) +{ + if (checkBackgroundSync("cannot set seed language")) + return; + + 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); +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::getAlwaysConfirmTransfers() const +{ + return m_wallet->always_confirm_transfers(); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setAlwaysConfirmTransfers(bool do_always_confirm) +{ + m_wallet->always_confirm_transfers(do_always_confirm); +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::getPrintRingMembers() const +{ + return m_wallet->print_ring_members(); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setPrintRingMembers(bool do_print_ring_members) +{ + m_wallet->print_ring_members(do_print_ring_members); +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::getStoreTxInfo() const +{ + return m_wallet->store_tx_info(); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setStoreTxInfo(bool do_store_tx_info) +{ + m_wallet->store_tx_info(do_store_tx_info); +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::getAutoRefresh() const +{ + return m_wallet->auto_refresh(); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setAutoRefresh(bool do_auto_refresh) +{ + m_wallet->auto_refresh(do_auto_refresh); +} +//------------------------------------------------------------------------------------------------------------------- +Wallet::RefreshType WalletImpl::getRefreshType() const +{ + return static_cast(m_wallet->get_refresh_type()); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setRefreshType(Wallet::RefreshType refresh_type) +{ + m_wallet->set_refresh_type(static_cast(refresh_type)); +} +//------------------------------------------------------------------------------------------------------------------- +std::uint32_t WalletImpl::getDefaultPriority() const +{ + return tools::fee_priority_utilities::as_integral(m_wallet->get_default_priority()); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setDefaultPriority(std::uint32_t default_priority) +{ + m_wallet->set_default_priority(tools::fee_priority_utilities::from_integral(default_priority)); +} +//------------------------------------------------------------------------------------------------------------------- +Wallet::AskPasswordType WalletImpl::getAskPasswordType() const +{ + return static_cast(m_wallet->ask_password()); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setAskPasswordType(AskPasswordType ask_password_type) +{ + m_wallet->ask_password(static_cast(ask_password_type)); +} +//------------------------------------------------------------------------------------------------------------------- +std::uint64_t WalletImpl::getMaxReorgDepth() const +{ + return m_wallet->max_reorg_depth(); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setMaxReorgDepth(std::uint64_t max_reorg_depth) +{ + m_wallet->max_reorg_depth(max_reorg_depth); +} +//------------------------------------------------------------------------------------------------------------------- +std::uint32_t WalletImpl::getMinOutputCount() const +{ + return m_wallet->get_min_output_count(); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setMinOutputCount(std::uint32_t min_output_count) +{ + m_wallet->set_min_output_count(min_output_count); +} +//------------------------------------------------------------------------------------------------------------------- +std::uint64_t WalletImpl::getMinOutputValue() const +{ + return m_wallet->get_min_output_value(); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setMinOutputValue(std::uint64_t min_output_value) +{ + m_wallet->set_min_output_value(min_output_value); +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::getMergeDestinations() const +{ + return m_wallet->merge_destinations(); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setMergeDestinations(bool do_merge_destinations) +{ + m_wallet->merge_destinations(do_merge_destinations); +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::getConfirmBacklog() const +{ + return m_wallet->confirm_backlog(); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setConfirmBacklog(bool do_confirm_backlog) +{ + m_wallet->confirm_backlog(do_confirm_backlog); +} +//------------------------------------------------------------------------------------------------------------------- +std::uint32_t WalletImpl::getConfirmBacklogThreshold() const +{ + return m_wallet->get_confirm_backlog_threshold(); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setConfirmBacklogThreshold(std::uint32_t confirm_backlog_threshold) +{ + m_wallet->set_confirm_backlog_threshold(confirm_backlog_threshold); +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::getConfirmExportOverwrite() const +{ + return m_wallet->confirm_export_overwrite(); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setConfirmExportOverwrite(bool do_confirm_export_overwrite) +{ + m_wallet->confirm_export_overwrite(do_confirm_export_overwrite); +} +//------------------------------------------------------------------------------------------------------------------- +uint64_t WalletImpl::getRefreshFromBlockHeight() const +{ + return m_wallet->get_refresh_from_block_height(); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setRefreshFromBlockHeight(uint64_t refresh_from_block_height) +{ + if (checkBackgroundSync("cannot change refresh height")) + return; + m_wallet->set_refresh_from_block_height(refresh_from_block_height); +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::getAutoLowPriority() const +{ + return m_wallet->auto_low_priority(); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setAutoLowPriority(bool use_auto_low_priority) +{ + m_wallet->auto_low_priority(use_auto_low_priority); +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::getSegregatePreForkOutputs() const +{ + return m_wallet->segregate_pre_fork_outputs(); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setSegregatePreForkOutputs(bool do_segregate) +{ + m_wallet->segregate_pre_fork_outputs(do_segregate); +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::getKeyReuseMitigation2() const +{ + return m_wallet->key_reuse_mitigation2(); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setKeyReuseMitigation2(bool mitigation) +{ + m_wallet->key_reuse_mitigation2(mitigation); +} +//------------------------------------------------------------------------------------------------------------------- +std::pair WalletImpl::getSubaddressLookahead() const +{ + return m_wallet->get_subaddress_lookahead(); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setSubaddressLookahead(uint32_t major, uint32_t minor) +{ + m_wallet->set_subaddress_lookahead(major, minor); +} +//------------------------------------------------------------------------------------------------------------------- +std::uint64_t WalletImpl::getSegregationHeight() const +{ + return m_wallet->segregation_height(); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setSegregationHeight(std::uint64_t height) +{ + m_wallet->segregation_height(height); +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::getIgnoreFractionalOutputs() const +{ + return m_wallet->ignore_fractional_outputs(); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setIgnoreFractionalOutputs(bool do_ignore_fractional_outputs) +{ + m_wallet->ignore_fractional_outputs(do_ignore_fractional_outputs); +} +//------------------------------------------------------------------------------------------------------------------- +std::uint64_t WalletImpl::getIgnoreOutputsAbove() const +{ + return m_wallet->ignore_outputs_above(); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setIgnoreOutputsAbove(std::uint64_t ignore_outputs_above) +{ + m_wallet->ignore_outputs_above(ignore_outputs_above); +} +//------------------------------------------------------------------------------------------------------------------- +std::uint64_t WalletImpl::getIgnoreOutputsBelow() const +{ + return m_wallet->ignore_outputs_below(); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setIgnoreOutputsBelow(std::uint64_t ignore_outputs_below) +{ + m_wallet->ignore_outputs_below(ignore_outputs_below); +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::getTrackUses() const +{ + return m_wallet->track_uses(); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setTrackUses(bool do_track_uses) +{ + m_wallet->track_uses(do_track_uses); +} +//------------------------------------------------------------------------------------------------------------------- +Wallet::BackgroundMiningSetupType WalletImpl::getSetupBackgroundMining() const +{ + return static_cast(m_wallet->setup_background_mining()); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setSetupBackgroundMining(Wallet::BackgroundMiningSetupType background_mining_setup) +{ + m_wallet->setup_background_mining(static_cast(background_mining_setup)); +} +//------------------------------------------------------------------------------------------------------------------- +std::string WalletImpl::getDeviceName() const +{ + return m_wallet->device_name(); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setDeviceName(const std::string &device_name) +{ + m_wallet->device_name(device_name); +} +//------------------------------------------------------------------------------------------------------------------- +Wallet::ExportFormat WalletImpl::getExportFormat() const +{ + return static_cast(m_wallet->export_format()); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setExportFormat(Wallet::ExportFormat export_format) +{ + return m_wallet->set_export_format(static_cast(export_format)); +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::getShowWalletNameWhenLocked() const +{ + return m_wallet->show_wallet_name_when_locked(); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setShowWalletNameWhenLocked(bool do_show_wallet_name) +{ + m_wallet->show_wallet_name_when_locked(do_show_wallet_name); +} +//------------------------------------------------------------------------------------------------------------------- +std::uint32_t WalletImpl::getInactivityLockTimeout() const +{ + return m_wallet->inactivity_lock_timeout(); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setInactivityLockTimeout(std::uint32_t seconds) +{ + m_wallet->inactivity_lock_timeout(seconds); +} +//------------------------------------------------------------------------------------------------------------------- +bool WalletImpl::getEnableMultisig() const +{ + return m_wallet->is_multisig_enabled(); +} +//------------------------------------------------------------------------------------------------------------------- +void WalletImpl::setEnableMultisig(bool do_enable_multisig) +{ + m_wallet->enable_multisig(do_enable_multisig); +} +//------------------------------------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------------------------------------- +// PRIVATE +//------------------------------------------------------------------------------------------------------------------- +std::size_t WalletImpl::getEnoteIndex(const std::string &key_image) const +{ + std::vector> enote_details = getEnoteDetails(); + 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; +} +//------------------------------------------------------------------------------------------------------------------- +std::string WalletImpl::getPaymentIdFromExtra(const std::vector &tx_extra) const +{ + std::vector tx_extra_fields; + parse_tx_extra(tx_extra, tx_extra_fields); // failure ok + tx_extra_nonce extra_nonce; + tx_extra_pub_key extra_pub_key; + crypto::hash8 payment_id8 = crypto::null_hash8; + crypto::hash payment_id = crypto::null_hash; + if (find_tx_extra_field_by_type(tx_extra_fields, extra_pub_key)) + { + const crypto::public_key &tx_pub_key = extra_pub_key.pub_key; + if (find_tx_extra_field_by_type(tx_extra_fields, extra_nonce)) + { + if (get_encrypted_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id8)) + { + m_wallet->get_account().get_device().decrypt_payment_id(payment_id8, tx_pub_key, m_wallet->get_account().get_keys().m_view_secret_key); + return epee::string_tools::pod_to_hex(payment_id8); + } + else if (cryptonote::get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, payment_id)) + { + return epee::string_tools::pod_to_hex(payment_id); + } + } + } + return std::string(); +} +//------------------------------------------------------------------------------------------------------------------- +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 d48d7f130e3..9dcea0b5f1a 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; @@ -53,10 +54,10 @@ struct Wallet2CallbackImpl; class WalletImpl : public Wallet { public: - WalletImpl(NetworkType nettype = MAINNET, uint64_t kdf_rounds = 1); + WalletImpl(NetworkType nettype = MAINNET, uint64_t kdf_rounds = 1, const bool unattended = true); ~WalletImpl(); bool create(const std::string &path, const std::string &password, - const std::string &language); + const std::string &language, bool create_address_file = false, bool non_deterministic = false); bool createWatchOnly(const std::string &path, const std::string &password, const std::string &language) const override; bool open(const std::string &path, const std::string &password); @@ -77,15 +78,19 @@ class WalletImpl : public Wallet const std::string &address_string, const std::string &viewkey_string, const std::string &spendkey_string = ""); + bool createFromJson(const std::string &json_file_path, std::string &pw_out); + bool recoverFromMultisigSeed(const std::string &path, + const std::string &password, + const std::string &language, + const std::string &multisig_seed, + const std::string seed_pass = "", + const bool do_create_address_file = false); bool recoverFromDevice(const std::string &path, const std::string &password, const std::string &device_name); Device getDeviceType() const override; bool close(bool store = true); std::string seed(const std::string& seed_offset = "") const override; - std::string getSeedLanguage() const override; - void setSeedLanguage(const std::string &arg) override; - // void setListener(Listener *) {} int status() const override; std::string errorString() const override; void statusWithErrorString(int& status, std::string& errorString) const override; @@ -106,34 +111,34 @@ class WalletImpl : public Wallet std::string filename() const override; std::string keysFilename() const override; bool init(const std::string &daemon_address, uint64_t upper_transaction_size_limit = 0, const std::string &daemon_username = "", const std::string &daemon_password = "", bool use_ssl = false, bool lightWallet = false, const std::string &proxy_address = "") override; - bool connectToDaemon() override; + bool connectToDaemon(uint32_t *version = NULL, bool *ssl = NULL, uint32_t timeout = 20000, bool *wallet_is_outdated = NULL, bool *daemon_is_outdated = NULL) override; ConnectionStatus connected() const override; void setTrustedDaemon(bool arg) override; bool trustedDaemon() const override; bool setProxy(const std::string &address) override; uint64_t balance(uint32_t accountIndex = 0) const override; - uint64_t unlockedBalance(uint32_t accountIndex = 0) const override; + std::map balancePerSubaddress(uint32_t accountIndex = 0) const override; + uint64_t unlockedBalance(uint32_t accountIndex = 0, std::uint64_t *blocks_to_unlock = NULL, std::uint64_t *time_to_unlock = NULL) const override; + std::map>> unlockedBalancePerSubaddress(uint32_t accountIndex = 0) const override; uint64_t blockChainHeight() const override; uint64_t approximateBlockChainHeight() const override; uint64_t estimateBlockChainHeight() const override; uint64_t daemonBlockChainHeight() const override; uint64_t daemonBlockChainTargetHeight() const override; + bool daemonSynced() const override; bool synchronized() const override; - bool refresh() override; + bool refresh(std::uint64_t start_height = 0, bool check_pool = true, bool try_incremental = false, std::uint64_t max_blocks = std::numeric_limits::max(), std::uint64_t *blocks_fetched_out = nullptr, bool *received_money_out = nullptr) override; void refreshAsync() override; - bool rescanBlockchain() override; - void rescanBlockchainAsync() override; + bool rescanBlockchain(bool do_hard_rescan = true, bool do_keep_key_images = false, bool do_skip_refresh = false) override; + void rescanBlockchainAsync(bool do_hard_rescan = true, bool do_keep_key_images = false) override; void setAutoRefreshInterval(int millis) override; int autoRefreshInterval() const override; - void setRefreshFromBlockHeight(uint64_t refresh_from_block_height) override; - uint64_t getRefreshFromBlockHeight() const override { return m_wallet->get_refresh_from_block_height(); }; void setRecoveringFromSeed(bool recoveringFromSeed) override; void setRecoveringFromDevice(bool recoveringFromDevice) override; - void setSubaddressLookahead(uint32_t major, uint32_t minor) override; bool watchOnly() const override; bool isDeterministic() const override; bool rescanSpent() override; - NetworkType nettype() const override {return static_cast(m_wallet->nettype());} + NetworkType nettype() const override; void hardForkInfo(uint8_t &version, uint64_t &earliest_height) const override; bool useForkRules(uint8_t version, int64_t early_blocks) const override; @@ -152,13 +157,17 @@ class WalletImpl : public Wallet bool exportMultisigImages(std::string& images) override; size_t importMultisigImages(const std::vector& images) override; bool hasMultisigPartialKeyImages() const override; - PendingTransaction* restoreMultisigTransaction(const std::string& signData) override; + PendingTransaction* restoreMultisigTransaction(const std::string& signData, bool ask_for_confirmation = false) override; PendingTransaction * createTransactionMultDest(const std::vector &dst_addr, const std::string &payment_id, optional> amount, uint32_t mixin_count, PendingTransaction::Priority priority = PendingTransaction::Priority_Low, uint32_t subaddr_account = 0, - std::set subaddr_indices = {}) override; + std::set subaddr_indices = {}, + std::set subtract_fee_from_outputs = {}, + const std::string key_image = "", + const size_t outputs = 1, + const std::uint64_t below = 0) override; PendingTransaction * createTransaction(const std::string &dst_addr, const std::string &payment_id, optional amount, uint32_t mixin_count, PendingTransaction::Priority priority = PendingTransaction::Priority_Low, @@ -167,11 +176,14 @@ class WalletImpl : public Wallet virtual PendingTransaction * createSweepUnmixableTransaction() override; bool submitTransaction(const std::string &fileName) override; virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename) override; + UnsignedTransaction * loadUnsignedTxFromStr(const std::string &unsigned_tx_str) override; bool exportKeyImages(const std::string &filename, bool all = false) override; - bool importKeyImages(const std::string &filename) override; + std::string exportKeyImagesAsString(bool all = false) override; + bool importKeyImages(const std::string &filename, std::uint64_t *spent_out = nullptr, std::uint64_t *unspent_out = nullptr, std::uint64_t *import_height = nullptr) override; + bool importKeyImagesFromStr(const std::string &data) override; bool exportOutputs(const std::string &filename, bool all = false) override; bool importOutputs(const std::string &filename) override; - bool scanTransactions(const std::vector &txids) override; + bool scanTransactions(const std::vector &txids, bool *wont_reprocess_recent_txs_via_untrusted_daemon = nullptr) override; bool setupBackgroundSync(const BackgroundSyncType background_sync_type, const std::string &wallet_password, const optional &background_cache_password = optional()) override; BackgroundSyncType getBackgroundSyncType() const override; @@ -207,8 +219,8 @@ 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 bool verifySignedMessage(const std::string &message, const std::string &address, const std::string &signature) const 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, bool *is_old_out = nullptr, std::string *signature_type_out = nullptr) 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; virtual void startRefresh() override; @@ -228,26 +240,169 @@ class WalletImpl : public Wallet virtual bool lockKeysFile() override; virtual bool unlockKeysFile() override; virtual bool isKeysFileLocked() override; + std::unique_ptr createKeysDecryptGuard(const std::string_view &password) override; virtual uint64_t coldKeyImageSync(uint64_t &spent, uint64_t &unspent) override; virtual void deviceShowAddress(uint32_t accountIndex, uint32_t addressIndex, const std::string &paymentId) override; virtual bool reconnectDevice() override; 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(const std::string &key_image) override; + void freezeByPubKey(const std::string &enote_pub_key) override; + void thaw(const std::string &key_image) override; + void thawByPubKey(const std::string &enote_pub_key) 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; + DeviceState getDeviceState() const override; + void rewriteWalletFile(const std::string &wallet_name, const std::string_view &password) override; + void writeWatchOnlyWallet(const std::string_view &password, std::string &new_keys_file_name) override; + void refreshPoolOnly(bool refreshed = false, bool try_incremental = false) override; + std::vector> getEnoteDetails() const override; + std::unique_ptr getEnoteDetails(const std::string &enote_pub_key) const override; + std::unique_ptr getEnoteDetails(const std::size_t enote_index) const override; + std::string convertMultisigTxToStr(const PendingTransaction &multisig_ptx) const override; + bool saveMultisigTx(const PendingTransaction &multisig_ptx, const std::string &filename) const override; + PendingTransaction* parseTxFromStr(const std::string &signed_tx_str) override; + PendingTransaction* parseMultisigTxFromStr(const std::string &multisig_tx_str) 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; + 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; + std::string getDeviceDerivationPath() const override; + void setDeviceDerivationPath(std::string device_derivation_path) override; + bool setDaemon(const std::string &daemon_address, const std::string &daemon_username = "", const std::string &daemon_password = "", bool trusted_daemon = false, Wallet::SSLSupport ssl_support = Wallet::SSLSupport::SSLSupport_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; + bool verifyPassword(const std::string_view &password) override; + void encryptKeys(const std::string_view &password) override; + void decryptKeys(const std::string_view &password) override; + std::uint64_t getMinRingSize() const override; + std::uint64_t getMaxRingSize() const override; + std::uint64_t adjustMixin(const std::uint64_t fake_outs_count) const override; + + std::uint64_t getDaemonAdjustedTime() const override; + std::uint64_t getLastBlockReward() const override; + bool hasUnknownKeyImages() const override; + + bool getExplicitRefreshFromBlockHeight() const override; + void setExplicitRefreshFromBlockHeight(bool do_explicit_refresh) override; + + // Wallet Settings getter/setter + std::string getSeedLanguage() const override; + void setSeedLanguage(const std::string &arg) override; + bool getAlwaysConfirmTransfers() const override; + void setAlwaysConfirmTransfers(bool do_always_confirm) override; + bool getPrintRingMembers() const override; + void setPrintRingMembers(bool do_print_ring_members) override; + bool getStoreTxInfo() const override; + void setStoreTxInfo(bool do_store_tx_info) override; + bool getAutoRefresh() const override; + void setAutoRefresh(bool do_auto_refresh) override; + RefreshType getRefreshType() const override; + void setRefreshType(RefreshType refresh_type) override; + std::uint32_t getDefaultPriority() const override; + void setDefaultPriority(std::uint32_t default_priority) override; + AskPasswordType getAskPasswordType() const override; + void setAskPasswordType(AskPasswordType ask_password_type) override; + std::uint64_t getMaxReorgDepth() const override; + void setMaxReorgDepth(std::uint64_t max_reorg_depth) override; + std::uint32_t getMinOutputCount() const override; + void setMinOutputCount(std::uint32_t min_output_count) override; + std::uint64_t getMinOutputValue() const override; + void setMinOutputValue(std::uint64_t min_output_value) override; + bool getMergeDestinations() const override; + void setMergeDestinations(bool do_merge_destinations) override; + std::uint32_t getConfirmBacklogThreshold() const override; + bool getConfirmBacklog() const override; + void setConfirmBacklog(bool do_confirm_backlog) override; + void setConfirmBacklogThreshold(std::uint32_t confirm_backlog_threshold) override; + bool getConfirmExportOverwrite() const override; + void setConfirmExportOverwrite(bool do_confirm_export_overwrite) override; + uint64_t getRefreshFromBlockHeight() const override; + void setRefreshFromBlockHeight(uint64_t refresh_from_block_height) override; + bool getAutoLowPriority() const override; + void setAutoLowPriority(bool use_auto_low_priority) override; + bool getSegregatePreForkOutputs() const override; + void setSegregatePreForkOutputs(bool do_segregate) override; + bool getKeyReuseMitigation2() const override; + void setKeyReuseMitigation2(bool mitigation) override; + std::pair getSubaddressLookahead() const override; + void setSubaddressLookahead(uint32_t major, uint32_t minor) override; + std::uint64_t getSegregationHeight() const override; + void setSegregationHeight(std::uint64_t height) override; + bool getIgnoreFractionalOutputs() const override; + void setIgnoreFractionalOutputs(bool do_ignore_fractional_outputs) override; + std::uint64_t getIgnoreOutputsAbove() const override; + void setIgnoreOutputsAbove(std::uint64_t ignore_outputs_above) override; + std::uint64_t getIgnoreOutputsBelow() const override; + void setIgnoreOutputsBelow(std::uint64_t ignore_outputs_below) override; + bool getTrackUses() const override; + void setTrackUses(bool do_track_uses) override; + BackgroundMiningSetupType getSetupBackgroundMining() const override; + void setSetupBackgroundMining(BackgroundMiningSetupType background_mining_setup) override; + std::string getDeviceName() const override; + void setDeviceName(const std::string &device_name) override; + ExportFormat getExportFormat() const override; + void setExportFormat(ExportFormat export_format) override; + bool getShowWalletNameWhenLocked() const override; + void setShowWalletNameWhenLocked(bool do_show_wallet_name) override; + std::uint32_t getInactivityLockTimeout() const override; + void setInactivityLockTimeout(std::uint32_t seconds) override; + bool getEnableMultisig() const override; + void setEnableMultisig(bool do_enable_multisig) override; + 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; + void doRefresh(std::uint64_t start_height = 0, bool check_pool = true, bool try_incremental = false, std::uint64_t max_blocks = std::numeric_limits::max(), bool *error_out = nullptr, std::uint64_t *blocks_fetched_out = nullptr, bool *received_money_out = nullptr); void stopRefresh(); bool isNewWallet() const; void pendingTxPostProcess(PendingTransactionImpl * pending); 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: getPaymentIdFromExtra - + * param: tx_extra - as raw bytes + * return: payment_id if succeeded, else empty string + * (size is either A) 16 for 8 byte short encrypted payment IDs, + * B) 64 for obsolete 32 byte long unencrypted payment IDs) + */ + std::string getPaymentIdFromExtra(const std::vector &tx_extra) const; + /** + * brief: statusOk - + * return: true if status is ok, else false + */ + bool statusOk() const; + private: friend class PendingTransactionImpl; friend class UnsignedTransactionImpl; @@ -278,6 +433,8 @@ class WalletImpl : public Wallet std::atomic m_refreshThreadDone; std::atomic m_refreshIntervalMillis; std::atomic m_refreshShouldRescan; + std::atomic m_do_hard_rescan; + std::atomic m_do_keep_key_images_on_rescan; // synchronizing refresh loop; boost::mutex m_refreshMutex; @@ -295,6 +452,8 @@ class WalletImpl : public Wallet // cache connection status to avoid unnecessary RPC calls mutable std::atomic m_is_connected; boost::optional m_daemon_login{}; + // number of rounds for key derivation function for wallet password + std::uint64_t m_kdf_rounds; }; diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index 2bedcc7d280..2038adf28ad 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -35,12 +35,18 @@ #include #include #include +#include +#include +#include #include #include #include #include +#include +#include #include + // Public interface for libwallet library namespace Monero { @@ -59,6 +65,74 @@ enum NetworkType : uint8_t { template using optional = std::optional; +/** +* brief: EnoteDetails - Container for all the necessary information that belongs to an enote +*/ +struct EnoteDetails +{ + enum class TxProtocol { + TxProtocol_CryptoNote, + TxProtocol_RingCT + }; + + virtual ~EnoteDetails() = 0; + //! enote public key (Ko), as hex string + virtual std::string onetimeAddress() const = 0; + //! view tag, as hex string + virtual std::string viewTag() const = 0; + //! payment id, as 16 char (8 bytes, short encrypted) or 64 char (32 bytes, long unencrypted [DEPRECATED]) hex string + virtual std::string paymentId() const = 0; + //! blockchain height at which this enote was received + virtual std::uint64_t blockHeight() const = 0; + //! when enote will become spendable + // NOTE: there was a change in tx relay rule that prevents to make txs with arbitrary lock times: https://github.com/monero-project/monero/pull/9151 + // txs that were created before the new rule can still be locked, unlockTime() is represented either as block index if unlockTime() < CRYPTONOTE_MAX_BLOCK_NUMBER or else as time + // now with the new rule, txs will have this set to either 0 for normal txs + // or blockHeight() + CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW for coinbase txs + // NOTE: if unlockTime() == 0, the blockchain still has to reach blockHeight() + CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE before this enote is actually unlocked + virtual std::uint64_t unlockTime() const = 0; + //! return true if enote is unlocked + virtual bool isUnlocked() const = 0; + //! tx id, as hex string + virtual std::string txId() const = 0; + //! index in tx + virtual std::uint64_t internalEnoteIndex() const = 0; + //! index in db + virtual std::uint64_t globalEnoteIndex() const = 0; + //! return true if enote is spent + virtual bool isSpent() const = 0; + //! return true if enote is frozen + virtual bool isFrozen() const = 0; + //! blockchain height at which enote was spent, set to 0 if enote hasn't been spent yet + virtual std::uint64_t spentHeight() const = 0; + //! key image, as hex string + virtual std::string keyImage() const = 0; + //! x, blinding factor in amount commitment C = x G + a H, as hex string + virtual std::string mask() const = 0; + //! amount in piconero + virtual std::uint64_t amount() const = 0; + //! protocol version : TxProtocol_CryptoNote / TxProtocol_RingCT + virtual TxProtocol protocolVersion() const = 0; + //! return true if key image is known + virtual bool isKeyImageKnown() const = 0; + //! return true if: + // a) for view wallets: we want to request the key image for this enote + // b) for cold wallets: the key image for this enote was requested + virtual bool isKeyImageRequest() const = 0; + //! public key index in tx_extra + virtual std::uint64_t pkIndex() const = 0; + //! show uses of this enote as decoy in the blockchain in the format [ [block_height, tx_id], ... ] + // Note: tracking this is disabled by default, use `Wallet::setTrackUses(true)` to enable tracking + virtual std::vector> uses() const = 0; + //! account index + virtual std::uint32_t subaddressIndexMajor() const = 0; + //! subaddress index + virtual std::uint32_t subaddressIndexMinor() const = 0; + + // Multisig + virtual bool isKeyImagePartial() const = 0; +}; + /** * @brief Transaction-like interface for sending money */ @@ -71,21 +145,25 @@ struct PendingTransaction }; enum Priority { - Priority_Default = 0, - Priority_Low = 1, - Priority_Medium = 2, - Priority_High = 3, + Priority_Default = 0, + Priority_Low = 1, // unimportant + Priority_Medium = 2, // normal + Priority_High = 3, // elevated + Priority_VeryHigh = 4, // priority Priority_Last }; virtual ~PendingTransaction() = 0; virtual int status() const = 0; virtual std::string errorString() const = 0; + virtual std::string confirmationMessage() const = 0; // commit transaction or save to file if filename is provided. virtual bool commit(const std::string &filename = "", bool overwrite = false) = 0; virtual uint64_t amount() const = 0; virtual uint64_t dust() const = 0; + virtual uint64_t dustInFee() const = 0; virtual uint64_t fee() const = 0; + virtual uint64_t change() const = 0; virtual std::vector txid() const = 0; /*! * \brief txCount - number of transactions current transaction will be splitted to @@ -94,6 +172,67 @@ struct PendingTransaction virtual uint64_t txCount() const = 0; virtual std::vector subaddrAccount() const = 0; virtual std::vector> subaddrIndices() const = 0; + /** + * brief: convertTxToStr - convert `PendingTransaction` to octal escape sequence string of `UnsignedTransaction` + * encrypted with the wallet's secret view key + * (same content which would be saved to file with `ptx->commit(filename)`) + * which then can be read with `loadUnsignedTxFromStr()` + * return: unsigned tx data as encrypted octal escape sequence string if succeeded, else empty string + * note: sets status error on failure + * - to get a hex string call: + * `epee::string_tools::buff_to_hex_nodelimer(ptx->convertTxToStr())` + */ + virtual std::string convertTxToStr() = 0; + /** + * brief: convertTxToRawBlobStr - convert each tx in `PendingTransaction` to octal escape sequence string + * which can be turned into hex string and used as arguments for `tx_as_hex` + * for daemon RPC `/send_raw_transaction` + * return: serialized tx data as octal escape sequence strings if succeeded, else empty vector + * note: sets status error on failure + * - to get a hex string call: + * `epee::string_tools::buff_to_hex_nodelimer(ptx->convertTxToRawBlobStr())` + */ + virtual std::vector convertTxToRawBlobStr() = 0; + /** + * brief: getWorstFeePerByte - needed when checking for backlog + * return: worst fee per bytes + */ + virtual double getWorstFeePerByte() const = 0; + /** + * brief: vinOffsets - relative indices + offsets for all real inputs and decoys for each ring and each tx + * return: enote indices + [ tx_idx : [ ring_idx : [ enote_idx_0 : global_idx, ..., enote_idx_ : offset ], ... ], ... ] + * note: - there is one ring for each input enote + * enote_idx_0 is the global enote index in the db, every following enote_idx_ is the offset from enote_idx_0 + * - these indices (after being converted to absolute indices) are used by RPC /get_outs.bin + * - to get the real inputs indices use constructionDataRealOutputIndices() + */ + virtual std::vector>> vinOffsets() const = 0; + /** + * brief: constructionDataRealOutputIndices - + * return: indices of real input enotes in ring per tx + * [ tx_idx : [ ring_idx : real_input_idx_in_ring , ... ], ... ] + */ + virtual std::vector> constructionDataRealOutputIndices() const = 0; + /** + * brief: vinAmounts - + * return: enote amounts + */ + virtual std::vector> vinAmounts() const = 0; + /** + * brief: getEnoteDetailsIn - + * return: enote details for all enotes that are used as inputs per tx + * [ tx_idx : [ enote_idx : EnoteDetails, ... ], ... ] + */ + virtual std::vector>> getEnoteDetailsIn() const =0; + /** + * brief: finishParsingTx - remember cold key images for parsed tx, for when we get those txes from the blockchain + * call this after both: + * 1.) this ptx was created with Wallet::parseTxFromStr() + * 2.) user accepted the ptx->confirmationMessage() + * return: true on success + */ + virtual bool finishParsingTx() = 0; /** * @brief multisigSignData @@ -112,12 +251,23 @@ struct PendingTransaction * pendingTransaction->commit(); */ virtual std::string multisigSignData() = 0; - virtual void signMultisigTx() = 0; + /** + * @brief signMultisigTx + * @outparam txids - only gets set if multisig tx is fully signed, else empty if partially signed + */ + virtual void signMultisigTx(std::vector *txids = nullptr) = 0; /** * @brief signersKeys * @return vector of base58-encoded signers' public keys */ virtual std::vector signersKeys() const = 0; + /** + * @brief finishRestoringMultisigTransaction - call this if: + * 1.) this ptx was created with Wallet::restoreMultisigTransaction(..., ask_for_confirmation = true) + * and then + * 2.) user accepted the ptx->confirmationMessage() + */ + virtual void finishRestoringMultisigTransaction() = 0; }; /** @@ -150,9 +300,19 @@ struct UnsignedTransaction /*! * @brief sign - Sign txs and saves to file * @param signedFileName + * @param do_export_raw - export signed transaction as raw unencrypted hex + * along normal + * as "_raw[_]" (Default: false) + * @param tx_ids_out * return - true on success */ - virtual bool sign(const std::string &signedFileName) = 0; + virtual bool sign(const std::string &signedFileName, bool do_export_raw = false, std::vector *tx_ids_out = nullptr) = 0; + /** + * brief: signAsString - convert UnsignedTransaction to hex string (same content which would be saved to file with sign(filename)) + * return: signed tx data as encrypted hex string if succeeded, else empty string + * note: sets status error on failure + */ + virtual std::string signAsString() = 0; }; /** @@ -165,6 +325,13 @@ struct TransactionInfo Direction_Out }; + enum class TxState { + TxState_Pending, + TxState_PendingInPool, + TxState_Failed, + TxState_Confirmed + }; + struct Transfer { Transfer(uint64_t _amount, const std::string &address); const uint64_t amount; @@ -173,9 +340,12 @@ 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 bool isUnlocked() const = 0; virtual uint64_t amount() const = 0; virtual uint64_t fee() const = 0; virtual uint64_t blockHeight() const = 0; @@ -191,6 +361,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 @@ -296,7 +470,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;} @@ -344,15 +517,21 @@ struct WalletListener * @brief moneySpent - called when money spent * @param txId - transaction id * @param amount - amount + * @param enote_pub_key - enote public key + * @param subaddr_index - accounnt index (major) and subaddress index (minor) */ - virtual void moneySpent(const std::string &txId, uint64_t amount) = 0; + virtual void moneySpent(const std::string &txId, uint64_t amount, const std::string &enote_pub_key, std::pair subaddr_index = {}) = 0; /** * @brief moneyReceived - called when money received * @param txId - transaction id - * @param amount - amount + * @param amount - amount (after subtracting burnt amount) + * @param burnt - amount burnt + * @param enote_pub_key - enote public key + * @param is_change - whether output is change + * @param is_coinbase - whether output is coinbase */ - virtual void moneyReceived(const std::string &txId, uint64_t amount) = 0; + virtual void moneyReceived(const std::string &txId, uint64_t amount, const uint64_t burnt, const std::string &enote_pub_key, const bool is_change = false, const bool is_coinbase = false) = 0; /** * @brief unconfirmedMoneyReceived - called when payment arrived in tx pool @@ -412,8 +591,30 @@ 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; }; +/** + * @brief RAII interface for decrypting in-memory wallet spend-keys + */ +struct WalletKeysDecryptGuard +{ + /** + * @brief Re-encrypt spend-keys on destruct unless other WalletKeysDecryptGuard is in scope for same wallet + */ + virtual ~WalletKeysDecryptGuard() = 0; +}; /** * @brief Interface for wallet operations. @@ -444,10 +645,69 @@ struct Wallet BackgroundSync_CustomPassword = 2 }; + enum class AskPasswordType { + AskPassword_Never = 0, + AskPassword_OnAction = 1, + AskPassword_ToDecrypt = 2, + }; + + enum class RefreshType { + Refresh_Full = 0, + Refresh_OptimizeCoinbase = 1, + Refresh_NoCoinbase = 2, + Refresh_Default = Refresh_OptimizeCoinbase, + }; + + enum class BackgroundMiningSetupType { + BackgroundMining_Maybe = 0, + BackgroundMining_Yes = 1, + BackgroundMining_No = 2, + }; + + enum class ExportFormat { + ExportFormat_Binary = 0, + ExportFormat_Ascii = 1, + }; + + enum class SSLSupport { + SSLSupport_Disabled = 0, + SSLSupport_Enabled = 1, + SSLSupport_Autodetect = 2, + }; + + struct DeviceState { + enum class DeviceProtocol { + DeviceProtocol_Default = 0, + DeviceProtocol_Proxy = 1, + DeviceProtocol_Cold = 2, + }; + enum class DeviceType { + DeviceType_Software = 0, + DeviceType_Ledger = 1, + DeviceType_Trezor = 2, + }; + + bool key_on_device; + std::string device_name; + bool has_tx_cold_sign; + bool has_ki_live_refresh; + bool has_ki_cold_sync; + std::uint64_t last_ki_sync; + DeviceProtocol protocol; + DeviceType device_type; + }; + + struct WalletState { + // is wallet file format deprecated + bool is_deprecated; + bool is_unattended; + std::string daemon_address; + std::string ring_database; + std::uint64_t n_enotes; + }; + virtual ~Wallet() = 0; virtual std::string seed(const std::string& seed_offset = "") const = 0; - 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; //deprecated: use safe alternative statusWithErrorString //! in case error status, returns error string @@ -556,19 +816,6 @@ struct Wallet */ virtual bool createWatchOnly(const std::string &path, const std::string &password, const std::string &language) const = 0; - /*! - * \brief setRefreshFromBlockHeight - start refresh from block height on recover - * - * \param refresh_from_block_height - blockchain start height - */ - virtual void setRefreshFromBlockHeight(uint64_t refresh_from_block_height) = 0; - - /*! - * \brief getRestoreHeight - get wallet creation height - * - */ - virtual uint64_t getRefreshFromBlockHeight() const = 0; - /*! * \brief setRecoveringFromSeed - set state recover form seed * @@ -583,19 +830,16 @@ struct Wallet */ virtual void setRecoveringFromDevice(bool recoveringFromDevice) = 0; - /*! - * \brief setSubaddressLookahead - set size of subaddress lookahead - * - * \param major - size fot the major index - * \param minor - size fot the minor index - */ - virtual void setSubaddressLookahead(uint32_t major, uint32_t minor) = 0; - /** - * @brief connectToDaemon - connects to the daemon. TODO: check if it can be removed - * @return + * @brief connectToDaemon - connects to the daemon. + * outparam: version - + * outparam: ssl - + * param: timeout - + * outparam: wallet_is_outdated - + * outparam: daemon_is_outdated - + * @return true if connected to daemon */ - virtual bool connectToDaemon() = 0; + virtual bool connectToDaemon(uint32_t *version = NULL, bool *ssl = NULL, uint32_t timeout = 20000, bool *wallet_is_outdated = NULL, bool *daemon_is_outdated = NULL) = 0; /** * @brief connected - checks if the wallet connected to the daemon @@ -606,13 +850,15 @@ struct Wallet virtual bool trustedDaemon() const = 0; virtual bool setProxy(const std::string &address) = 0; virtual uint64_t balance(uint32_t accountIndex = 0) const = 0; + virtual std::map balancePerSubaddress(uint32_t accountIndex = 0) const = 0; uint64_t balanceAll() const { uint64_t result = 0; for (uint32_t i = 0; i < numSubaddressAccounts(); ++i) result += balance(i); return result; } - virtual uint64_t unlockedBalance(uint32_t accountIndex = 0) const = 0; + virtual uint64_t unlockedBalance(uint32_t accountIndex = 0, uint64_t *blocks_to_unlock = NULL, uint64_t *time_to_unlock = NULL) const = 0; + virtual std::map>> unlockedBalancePerSubaddress(uint32_t accountIndex = 0) const = 0; uint64_t unlockedBalanceAll() const { uint64_t result = 0; for (uint32_t i = 0; i < numSubaddressAccounts(); ++i) @@ -663,6 +909,8 @@ struct Wallet * status() will return Status_Error and errorString() will return verbose error description */ virtual uint64_t daemonBlockChainTargetHeight() const = 0; + //! return: true if daemon is synced + virtual bool daemonSynced() const = 0; /** * @brief synchronized - checks if wallet was ever synchronized @@ -675,6 +923,8 @@ struct Wallet static uint64_t amountFromDouble(double amount); static std::string genPaymentId(); static bool paymentIdValid(const std::string &paiment_id); + static bool isSubaddress(const std::string &address, NetworkType nettype); + static std::string getAddressFromIntegrated(const std::string &address, NetworkType nettype); static bool addressValid(const std::string &str, NetworkType nettype); static bool addressValid(const std::string &str, bool testnet) // deprecated { @@ -691,6 +941,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); @@ -710,9 +968,20 @@ struct Wallet /** * @brief refresh - refreshes the wallet, updating transactions from daemon + * @param start_height - (Default: 0) + * @param check_pool - wether to also scan tx pool (Default: true) + * @param try_incremental - if daemon supports it, only get txs from the pool which the wallet hasn't seen before (Default: false) + * @param max_blocks - refresh returns when blocks fetched reaches max_blocks (Default: std::numeric_limits::max()) + * @outparam blocks_fetched_out - number of blocks fetched during refresh (Default: nullptr) + * @outparam received_money_out - true if the wallet received money in the blocks fetched during refresh (Default: nullptr) * @return - true if refreshed successfully; */ - virtual bool refresh() = 0; + virtual bool refresh(std::uint64_t start_height = 0, + bool check_pool = true, + bool try_incremental = false, + std::uint64_t max_blocks = std::numeric_limits::max(), + std::uint64_t *blocks_fetched_out = nullptr, + bool *received_money_out = nullptr) = 0; /** * @brief refreshAsync - refreshes wallet asynchronously. @@ -721,14 +990,25 @@ struct Wallet /** * @brief rescanBlockchain - rescans the wallet, updating transactions from daemon + * @param do_hard_rescan - if true you will lose any information which can not be recovered from the blockchain itself (Default: true) + * @param do_keep_key_images - keep key images, only works with soft rescan, + * if set `true` you may want to check `hashEnotes()` and `finishRescanBcKeepKeyImages()` for manual control, + * else set `do_skip_refresh = false` and the two methods get handled automatically by the backend (Default: false) + * @param do_skip_refresh - if true this functions does not call `refresh()` itself, + * so you have to call it manually and therefore can make use of the outparams (Default: false) * @return - true if refreshed successfully; + * @note - do_hard_rescan do_keep_key_images + * false false soft rescan + * false true soft rescan, keep key images + * true false hard rescan + * true true ERROR: cannot preserve key images on hard rescan */ - virtual bool rescanBlockchain() = 0; + virtual bool rescanBlockchain(bool do_hard_rescan = true, bool do_keep_key_images = false, bool do_skip_refresh = false) = 0; /** * @brief rescanBlockchainAsync - rescans wallet asynchronously, starting from genesys */ - virtual void rescanBlockchainAsync() = 0; + virtual void rescanBlockchainAsync(bool do_hard_rescan = true, bool do_keep_key_images = false) = 0; /** * @brief setAutoRefreshInterval - setup interval for automatic refresh. @@ -830,9 +1110,12 @@ struct Wallet /** * @brief restoreMultisigTransaction creates PendingTransaction from signData * @param signData encrypted unsigned transaction. Obtained with PendingTransaction::multisigSignData + * @param ask_for_confirmation - if set true the transaction doesn't get fully loaded, + * in case you want to get a confirmation text from ptx->confirmationMessage() + * and then, if the user accepts the message, call ptx->finishRestoringMultisigTransaction() to complete loading the tx (Default: false) * @return PendingTransaction */ - virtual PendingTransaction* restoreMultisigTransaction(const std::string& signData) = 0; + virtual PendingTransaction* restoreMultisigTransaction(const std::string& signData, bool ask_for_confirmation = false) = 0; /*! * \brief createTransactionMultDest creates transaction with multiple destinations. if dst_addr is an integrated address, payment_id is ignored @@ -840,9 +1123,13 @@ struct Wallet * \param payment_id optional payment_id, can be empty string * \param amount vector of amounts * \param mixin_count mixin count. if 0 passed, wallet will use default value + * \param priority fee priority, Priority_[Low|Medium|High|VeryHigh] (Default: Priority_Low) * \param subaddr_account subaddress account from which the input funds are taken * \param subaddr_indices set of subaddress indices to use for transfer or sweeping. if set empty, all are chosen when sweeping, and one or more are automatically chosen when transferring. after execution, returns the set of actually used indices - * \param priority + * \param subtract_fee_from_outputs transfer amount with fee included #8861 + * \param key_image only used for sweep_single (Default: empty string) + * \param outputs number of outputs to create, only used for sweeping, for normal transactions `outputs` get determined by `dst_addr` (Default: 1) + * \param below threshold for sweep_below, only used if `amount` is not set (Default: 0) * \return PendingTransaction object. caller is responsible to check PendingTransaction::status() * after object returned */ @@ -851,7 +1138,11 @@ struct Wallet optional> amount, uint32_t mixin_count, PendingTransaction::Priority = PendingTransaction::Priority_Low, uint32_t subaddr_account = 0, - std::set subaddr_indices = {}) = 0; + std::set subaddr_indices = {}, + std::set subtract_fee_from_outputs = {}, + const std::string key_image = "", + const size_t outputs = 1, + const std::uint64_t below = 0) = 0; /*! * \brief createTransaction creates transaction. if dst_addr is an integrated address, payment_id is ignored @@ -859,9 +1150,9 @@ struct Wallet * \param payment_id optional payment_id, can be empty string * \param amount amount * \param mixin_count mixin count. if 0 passed, wallet will use default value + * \param priority fee priority, Priority_[Low|Medium|High|VeryHigh] (Default: Priority_Low) * \param subaddr_account subaddress account from which the input funds are taken * \param subaddr_indices set of subaddress indices to use for transfer or sweeping. if set empty, all are chosen when sweeping, and one or more are automatically chosen when transferring. after execution, returns the set of actually used indices - * \param priority * \return PendingTransaction object. caller is responsible to check PendingTransaction::status() * after object returned */ @@ -886,10 +1177,23 @@ struct Wallet * after object returned */ virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename) = 0; - + /** + * brief: loadUnsignedTxFromStr - create UnsignedTransaction from unsigned tx string + * param: unsigned_tx_str - encrypted unsigned tx hex string + * return: UnsignedTransaction object. caller is responsible to check UnsignedTransaction::status() after object returned + * note: sets status error on failure + */ + virtual UnsignedTransaction * loadUnsignedTxFromStr(const std::string &unsigned_tx_str) = 0; /*! * \brief submitTransaction - submits transaction in signed tx file * \return - true on success + * \note - this submits the transaction without checking the file content and asking for confirmation + * if you want to get a confirmationMessage before commiting, use: + * 1. loadFromFile(signed_tx_file, signed_tx_str); + * 2. ptx = parseTxFromStr(signed_tx_str); + * 3. if there are no errors and user accepts ptx->confirmationMessage() + * ptx->finishParsingTx() + * 4. ptx->commit() */ virtual bool submitTransaction(const std::string &fileName) = 0; @@ -915,13 +1219,30 @@ struct Wallet * \return - true on success */ virtual bool exportKeyImages(const std::string &filename, bool all = false) = 0; + /** + * brief: exportKeyImagesAsString - export key images as string + * param: all - export all key images or only those that have not yet been exported + * return: exported key images as encrypted hex string if succeeded, else empty string + * note: sets status error on failure + */ + virtual std::string exportKeyImagesAsString(bool all = false) = 0; /*! - * \brief importKeyImages - imports key images from file + * \brief importKeyImages - imports key images from file * \param filename - * \return - true on success + * \outparam spent_out - spent amount in imported key images + * \outparam unspent_out - unspent amount in imported key images + * \outparam import_height - height of most recent imported key image + * \return - true on success + */ + virtual bool importKeyImages(const std::string &filename, std::uint64_t *spent_out = nullptr, std::uint64_t *unspent_out = nullptr, std::uint64_t *import_height = nullptr) = 0; + /** + * brief: importKeyImagesFromStr - import key images from string + * param: data - exported key images as encrypted hex string + * return: true if succeeded, else false + * note: sets status error on failure */ - virtual bool importKeyImages(const std::string &filename) = 0; + virtual bool importKeyImagesFromStr(const std::string &data) = 0; /*! * \brief importOutputs - exports outputs to file @@ -940,9 +1261,10 @@ struct Wallet /*! * \brief scanTransactions - scan a list of transaction ids, this operation may reveal the txids to the remote node and affect your privacy * \param txids - list of transaction ids + * \param wont_reprocess_recent_txs_via_untrusted_daemon - allows you to catch the wallet error with the same name (used by CLI to show certain error message) * \return - true on success */ - virtual bool scanTransactions(const std::vector &txids) = 0; + virtual bool scanTransactions(const std::vector &txids, bool *wont_reprocess_recent_txs_via_untrusted_daemon = nullptr) = 0; /*! * \brief setupBackgroundSync - setup background sync mode with just a view key @@ -1035,20 +1357,25 @@ 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 failure + */ + 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) * \param address - the address the signature claims to be made with * \param signature - the signature + * \outparam is_old_out - true if signature uses old format (optional) + * \outparam signature_type_out - either signed by: spendkey | viewkey | unkown (suspicious); (optional) * \return true if the signature verified, false otherwise */ - virtual bool verifySignedMessage(const std::string &message, const std::string &addres, const std::string &signature) const = 0; + virtual bool verifySignedMessage(const std::string &message, const std::string &address, const std::string &signature, bool *is_old_out = nullptr, std::string *signature_type_out = nullptr) const = 0; /*! * \brief signMultisigParticipant signs given message with the multisig public signer key @@ -1102,12 +1429,15 @@ struct Wallet virtual bool setRing(const std::string &key_image, const std::vector &ring, bool relative) = 0; //! sets whether pre-fork outs are to be segregated + // DEPRECATED, use setSegregatePreForkOutputs() virtual void segregatePreForkOutputs(bool segregate) = 0; //! sets the height where segregation should occur + // DEPRECATED, use setSegregationHeight() virtual void segregationHeight(uint64_t height) = 0; //! secondary key reuse mitigation + // DEPRECATED, use setKeyReuseMitigation2() virtual void keyReuseMitigation2(bool mitigation) = 0; //! locks/unlocks the keys file; returns true on success @@ -1116,13 +1446,20 @@ struct Wallet //! returns true if the keys file is locked virtual bool isKeysFileLocked() = 0; + /** + * \brief Decrypt spend-keys if not already decrypted and create a keys decryption guard + * \param password password to decrypt in-memory spend-keys + * \return Keys decryption guard + */ + virtual std::unique_ptr createKeysDecryptGuard(const std::string_view &password) = 0; + /*! * \brief Queries backing device for wallet keys * \return Device they are on */ virtual Device getDeviceType() const = 0; - //! cold-device protocol key image sync + //! cold-device protocol key image sync, Note: this can throw virtual uint64_t coldKeyImageSync(uint64_t &spent, uint64_t &unspent) = 0; //! shows address on device display @@ -1136,6 +1473,457 @@ 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 failure + */ + 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 failure + */ + 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: key_image - key image of enote + * param: enote_pub_key - public key of enote + * note: sets status error on failure + */ + virtual void freeze(const std::string &key_image) = 0; + virtual void freezeByPubKey(const std::string &enote_pub_key) = 0; + /** + * brief: thaw - thaw enote that is frozen, so it appears in balance and can be spent in a transaction + * param: key_image - key image of enote + * param: enote_pub_key - public key of enote + * note: sets status error on failure + */ + virtual void thaw(const std::string &key_image) = 0; + virtual void thawByPubKey(const std::string &enote_pub_key) = 0; + /** + * brief: isFrozen - check if enote is frozen + * param: key_image - key image of enote + * return : true if enote is frozen, else false + * note: sets status error on failure + */ + 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: getDeviceState - get information about the HW device + * return: DeviceState object + */ + virtual DeviceState getDeviceState() 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 failure + */ + virtual void rewriteWalletFile(const std::string &wallet_name, const std::string_view &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 failure + */ + virtual void writeWatchOnlyWallet(const std::string_view &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 failure + */ + virtual void refreshPoolOnly(bool refreshed = false, bool try_incremental = false) = 0; + /** + * brief: getEnoteDetails - get information about all enotes + * return: vector of enotes details + */ + virtual std::vector> getEnoteDetails() const = 0; + /** + * brief: getEnoteDetails - get information about a single enote + * param: enote_pub_key - ephemeral public key + * return: enote details if succeeded, else nullptr + * note: sets status error on failure + */ + virtual std::unique_ptr getEnoteDetails(const std::string &enote_pub_key) const = 0; + /** + * brief: getEnoteDetails - get information about a single enote + * param: enote_index - index of enote in internal storage + * return: enote details if succeeded, else nullptr + * note: sets status error on failure + */ + virtual std::unique_ptr getEnoteDetails(const std::size_t enote_index) 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 failure + */ + 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 failure + */ + virtual bool saveMultisigTx(const PendingTransaction &multisig_ptx, const std::string &filename) const = 0; + /** + * brief: parseTxFromStr - get transactions from encrypted signed transaction as hex string + * param: signed_tx_str - + * return: PendingTransaction object. caller is responsible to check ptx->status() + * and ptx->confirmationMessage() after object returned, and then ptx->finishParsingTx() + * if user accepts confirmationMessage + * note: sets status error on failure + */ + virtual PendingTransaction* parseTxFromStr(const std::string &signed_tx_str) = 0; + /** + * brief: parseMultisigTxFromStr - get pending multisig transaction from encrypted unsigned multisig transaction as hex string + * param: multisig_tx_str - + * return: ptx if succeeded, else nullptr + * note: sets status error on failure + */ + virtual PendingTransaction* parseMultisigTxFromStr(const std::string &multisig_tx_str) = 0; + /** + * brief: getFeeMultiplier - + * param: priority - + * param: fee_algorithm - + * return: fee multiplier + * note: sets status error on failure + */ + 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 failure + */ + 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 failure + */ + 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 failure + */ + 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 tag description for each tag and tag for each account + * as map of [tag : tag_description] and list of tag per account + * 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 accounts by index + * one account can have one tag, but one tag can be shared by many accounts + * param: account_index - major index + * param: tag - + * note: sets status error on failure + */ + 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 failure + */ + 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 quantity of enotes (default: 0xffffffff) + * return: encrypted data of exported enotes as hex string if succeeded, else empty string + * note: sets status error on failure + */ + 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 failure + */ + 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 failure + */ + 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 failure + */ + virtual std::vector> estimateBacklog(const std::vector> &fee_levels) const = 0; + /** + * brief: hashEnotes - only needed before calling `rescanBlockChain(do_hard_rescan=false, do_keep_key_images=true, do_skip_refresh=true)` + * (with the exact settings from above, or in other words: rescan soft keep key images, skip refresh) + * get hash of all enotes in local enote store up until, but excluding, `enote_idx` + * with the information returned by this method, `finishRescanBcKeepKeyImages()` can determine if a BC reorg happened during rescan + * 1. `n_hashed_enotes_pre = hashEnotes(0, hash_pre)` + * 2. `rescanBlockChain(false, true, true)` + * 3. `refresh(start_height, check_pool, try_incremental, max_blocks, fetched_blocks_out, received_money_out)` + * 4. `finishRescanBcKeepKeyImages(n_hashed_enotes_pre, hash_pre)` + * (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, if set to 0 it will hash all known enotes, similiar to using `getWalletState().n_enotes` + * outparam: hash - hash as hex string + * return: number of hashed enotes + * note: sets status error on failure + */ + virtual std::uint64_t hashEnotes(std::uint64_t enote_idx, std::string &hash) const = 0; + /** + * brief: finishRescanBcKeepKeyImages - only needed after calling `rescanBlockChain()` with `do_keep_key_images = true` + * restores key images in m_transfers from m_key_images if no BC reorg happpened during rescan + * more information in comment for `hashEnotes()` + * param: enote_idx - number of hashed enotes returned by `hashEnotes()` before rescan + * param: hash - hash returned as outparam by `hashEnotes()` before rescan + * note: sets status error on failure + */ + 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 failure + */ + 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 failure + */ + 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 failure + */ + 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 failure + */ + 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: getDeviceDerivationPath - + */ + virtual std::string getDeviceDerivationPath() const = 0; + /** + * brief: setDeviceDerivationPath - + * param: device_derivation_path - + */ + virtual void setDeviceDerivationPath(std::string device_derivation_path) = 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: false) + * param: ssl_support - SSLSupport_Disabled | SSLSupport_Enabled | SSLSupport_Autodetect (default: SSLSupport_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 failure + */ + virtual bool setDaemon(const std::string &daemon_address, + const std::string &daemon_username = "", + const std::string &daemon_password = "", + bool trusted_daemon = false, + const SSLSupport ssl_support = Wallet::SSLSupport::SSLSupport_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; + /** + * brief: verifyPassword - + * param: password - password to verify + * return: true if succeeded + * note: sets status error on failure + * This function automatically locks/unlocks the keys file as needed. + * When possible use this instead of WalletManager::verifyWalletPassword() + */ + virtual bool verifyPassword(const std::string_view &password) = 0; + /** + * brief: encryptKeys - encrypt cached secret keys + * param: password - wallet password + */ + virtual void encryptKeys(const std::string_view &password) = 0; + /** + * brief: decryptKeys - decrypt cached secret keys + * param: password - wallet password + */ + virtual void decryptKeys(const std::string_view &password) = 0; + /** + * brief: getMinRingSize - + * return: minimal ring size + */ + virtual std::uint64_t getMinRingSize() const = 0; + /** + * brief: getMaxRingSize - + * return: maximal ring size + */ + virtual std::uint64_t getMaxRingSize() const = 0; + /** + * brief: adjustMixin - clamps fake_outs_count to be in range [min_ring_size, max_ring_size] + * param: fake_outs_count - number of decoys to be used in ring + * return: adjusted fake_outs_count + */ + virtual std::uint64_t adjustMixin(const std::uint64_t fake_outs_count) const = 0; + + /** + * brief: getDaemonAdjustedTime - (see comment in src/cryptonote_core/blockchain.h for get_adjusted_time() for more details about daemon adjusted time) + * return: daemon adjusted time + * note: sets status error on failure + */ + virtual std::uint64_t getDaemonAdjustedTime() const = 0; + // return: last block reward the wallet has whitnessed + virtual std::uint64_t getLastBlockReward() const = 0; + // return: true if wallet owns enotes with unknown key images + virtual bool hasUnknownKeyImages() const = 0; + + + //! return: true if block height was explicitly set to 0 + virtual bool getExplicitRefreshFromBlockHeight() const = 0; + virtual void setExplicitRefreshFromBlockHeight(bool do_explicit_refresh) = 0; + + // Wallet Settings getter/setter + virtual std::string getSeedLanguage() const = 0; + // note: sets status error on failure + virtual void setSeedLanguage(const std::string &arg) = 0; + virtual bool getAlwaysConfirmTransfers() const = 0; + virtual void setAlwaysConfirmTransfers(bool do_always_confirm) = 0; + virtual bool getPrintRingMembers() const = 0; + virtual void setPrintRingMembers(bool do_print_ring_members) = 0; + virtual bool getStoreTxInfo() const = 0; + virtual void setStoreTxInfo(bool do_store_tx_info) = 0; + virtual bool getAutoRefresh() const = 0; + virtual void setAutoRefresh(bool do_auto_refresh) = 0; + virtual RefreshType getRefreshType() const = 0; + virtual void setRefreshType(RefreshType refresh_type) = 0; + virtual std::uint32_t getDefaultPriority() const = 0; + virtual void setDefaultPriority(std::uint32_t default_priority) = 0; + virtual AskPasswordType getAskPasswordType() const = 0; + virtual void setAskPasswordType(AskPasswordType ask_password_type) = 0; + virtual std::uint64_t getMaxReorgDepth() const = 0; + virtual void setMaxReorgDepth(std::uint64_t max_reorg_depth) = 0; + virtual std::uint32_t getMinOutputCount() const = 0; + virtual void setMinOutputCount(std::uint32_t min_output_count) = 0; + virtual std::uint64_t getMinOutputValue() const = 0; + virtual void setMinOutputValue(std::uint64_t min_output_value) = 0; + virtual bool getMergeDestinations() const = 0; + virtual void setMergeDestinations(bool do_merge_destinations) = 0; + virtual bool getConfirmBacklog() const = 0; + virtual void setConfirmBacklog(bool do_confirm_backlog) = 0; + virtual std::uint32_t getConfirmBacklogThreshold() const = 0; + virtual void setConfirmBacklogThreshold(std::uint32_t confirm_backlog_threshold) = 0; + virtual bool getConfirmExportOverwrite() const = 0; + virtual void setConfirmExportOverwrite(bool do_confirm_export_overwrite) = 0; + virtual std::uint64_t getRefreshFromBlockHeight() const = 0; + // note: sets status error on failure + virtual void setRefreshFromBlockHeight(std::uint64_t refresh_from_block_height) = 0; + virtual bool getAutoLowPriority() const = 0; + virtual void setAutoLowPriority(bool use_auto_low_priority) = 0; + virtual bool getSegregatePreForkOutputs() const = 0; + virtual void setSegregatePreForkOutputs(bool do_segregate_pre_fork_outputs) = 0; + virtual bool getKeyReuseMitigation2() const = 0; + virtual void setKeyReuseMitigation2(bool do_key_reuse_mitigation) = 0; + virtual std::pair getSubaddressLookahead() const = 0; + virtual void setSubaddressLookahead(uint32_t major, uint32_t minor) = 0; + virtual std::uint64_t getSegregationHeight() const = 0; + virtual void setSegregationHeight(std::uint64_t segregation_height) = 0; + virtual bool getIgnoreFractionalOutputs() const = 0; + virtual void setIgnoreFractionalOutputs(bool do_ignore_fractional_outputs) = 0; + virtual std::uint64_t getIgnoreOutputsAbove() const = 0; + virtual void setIgnoreOutputsAbove(std::uint64_t ignore_outputs_above) = 0; + virtual std::uint64_t getIgnoreOutputsBelow() const = 0; + virtual void setIgnoreOutputsBelow(std::uint64_t ignore_outputs_below) = 0; + virtual bool getTrackUses() const = 0; + virtual void setTrackUses(bool do_track_uses) = 0; + virtual BackgroundMiningSetupType getSetupBackgroundMining() const = 0; + virtual void setSetupBackgroundMining(BackgroundMiningSetupType background_mining_setup) = 0; + virtual std::string getDeviceName() const = 0; + virtual void setDeviceName(const std::string &device_name) = 0; + virtual ExportFormat getExportFormat() const = 0; + virtual void setExportFormat(ExportFormat export_format) = 0; + virtual bool getShowWalletNameWhenLocked() const = 0; + virtual void setShowWalletNameWhenLocked(bool do_show_wallet_name) = 0; + virtual std::uint32_t getInactivityLockTimeout() const = 0; + virtual void setInactivityLockTimeout(std::uint32_t seconds) = 0; + virtual bool getEnableMultisig() const = 0; + virtual void setEnableMultisig(bool do_enable_multisig) = 0; }; /** @@ -1151,9 +1939,14 @@ struct WalletManager * \param language Language to be used to generate electrum seed mnemonic * \param nettype Network type * \param kdf_rounds Number of rounds for key derivation function + * \param create_address_file - Create .address.txt file (Default: false) + * NOTE: gets irgnored for stagenet and testnet and will create .address.txt file even if set to false + * \param non_determinisitc - Whether to create a non-deterministic wallet, where the secret-spend-key and secret-view-key are both random and not in relation (Default: false) + * NOTE: this old way to create keys should not be used, except you have a good reason and you know what you do + * \param unattended Whether the wallet is unattended (GUI is unattended, CLI is not unattended) (Default: true) * \return Wallet instance (Wallet::status() needs to be called to check if created successfully) */ - virtual Wallet * createWallet(const std::string &path, const std::string &password, const std::string &language, NetworkType nettype, uint64_t kdf_rounds = 1) = 0; + virtual Wallet * createWallet(const std::string &path, const std::string &password, const std::string &language, NetworkType nettype, uint64_t kdf_rounds = 1, const bool create_address_file = false, const bool non_determinisitc = false, const bool unattended = true) = 0; Wallet * createWallet(const std::string &path, const std::string &password, const std::string &language, bool testnet = false) // deprecated { return createWallet(path, password, language, testnet ? TESTNET : MAINNET); @@ -1166,9 +1959,10 @@ struct WalletManager * \param nettype Network type * \param kdf_rounds Number of rounds for key derivation function * \param listener Wallet listener to set to the wallet after creation + * \param unattended Whether the wallet is unattended (GUI is unattended, CLI is not unattended) (Default: true) * \return Wallet instance (Wallet::status() needs to be called to check if opened successfully) */ - virtual Wallet * openWallet(const std::string &path, const std::string &password, NetworkType nettype, uint64_t kdf_rounds = 1, WalletListener * listener = nullptr) = 0; + virtual Wallet * openWallet(const std::string &path, const std::string &password, NetworkType nettype, uint64_t kdf_rounds = 1, WalletListener * listener = nullptr, const bool unattended = true) = 0; Wallet * openWallet(const std::string &path, const std::string &password, bool testnet = false) // deprecated { return openWallet(path, password, testnet ? TESTNET : MAINNET); @@ -1183,11 +1977,12 @@ struct WalletManager * \param restoreHeight restore from start height * \param kdf_rounds Number of rounds for key derivation function * \param seed_offset Seed offset passphrase (optional) + * \param unattended Whether the wallet is unattended (GUI is unattended, CLI is not unattended) (Default: true) * \return Wallet instance (Wallet::status() needs to be called to check if recovered successfully) */ virtual Wallet * recoveryWallet(const std::string &path, const std::string &password, const std::string &mnemonic, NetworkType nettype = MAINNET, uint64_t restoreHeight = 0, uint64_t kdf_rounds = 1, - const std::string &seed_offset = {}) = 0; + const std::string &seed_offset = {}, const bool unattended = true) = 0; Wallet * recoveryWallet(const std::string &path, const std::string &password, const std::string &mnemonic, bool testnet = false, uint64_t restoreHeight = 0) // deprecated { @@ -1220,6 +2015,8 @@ struct WalletManager * \param viewKeyString view key * \param spendKeyString spend key (optional) * \param kdf_rounds Number of rounds for key derivation function + * \param create_address_file Whether to create an address file + * \param unattended Whether the wallet is unattended (GUI is unattended, CLI is not unattended) (Default: true) * \return Wallet instance (Wallet::status() needs to be called to check if recovered successfully) */ virtual Wallet * createWalletFromKeys(const std::string &path, @@ -1230,7 +2027,9 @@ struct WalletManager const std::string &addressString, const std::string &viewKeyString, const std::string &spendKeyString = "", - uint64_t kdf_rounds = 1) = 0; + uint64_t kdf_rounds = 1, + const bool create_address_file = false, + const bool unattended = true) = 0; Wallet * createWalletFromKeys(const std::string &path, const std::string &password, const std::string &language, @@ -1272,6 +2071,61 @@ struct WalletManager { return createWalletFromKeys(path, language, testnet ? TESTNET : MAINNET, restoreHeight, addressString, viewKeyString, spendKeyString); } + /** + * brief: createWalletFromJson - create a deterministic, non-deterministic or watch-only wallet from a json file + * json file fields: + * "version" : , // [mandatory] (1 (=current version at time of writing)) + * "filename" : "", // [mandatory] + * "scan_from_height" : , // [optional] + * "password" : "", // [optional] + * "viewkey" : "", // [optional] + * "spendkey" : "", // [optional] + * "seed" : "", // [optional] + * "seed_passphrase" : "", // [optional] + * "address" : "", // [optional] + * "create_address_file" : , // [optional] (0 (=false) | 1 (=true)) + * + * depending on what type of wallet you want to create you need to specify different combinations of fields: + * "normal" (determ.) : either `spendkey` or `seed` + * non-deterministic : `spendkey` and `viewkey` + * watch-only : `viewkey` and `address` + * + * param: json_file_path - path to json file + * param: nettype - network type + * outparam: pw_out - password given by json file, this method sets an empty wallet password if no password is given + * param: kdf_rounds - Number of rounds for key derivation function (Default: 1) + * param: unattended - Whether the wallet is unattended (GUI is unattended, CLI is not unattended) (Default: true) + * return: Wallet instance (Wallet::status() needs to be called to check if recovered successfully) + */ + virtual Wallet * createWalletFromJson(const std::string &json_file_path, + NetworkType nettype, + std::string &pw_out, + uint64_t kdf_rounds = 1, + const bool unattended = true) = 0; + /** + * \brief: createWalletFromMultisigSeed - recovers existing multisig wallet from a multisig seed and optional seed offset passphrase + * \param: path - name of wallet file to be created + * \param: password - password of wallet file + * \param: language - mnemonic seed language + * \param: nettype - network type + * \param: restore_height - restore from start height + * \param: multisig_seed - multisig seed + * \param: seed_pass - seed offset passphrase (Default: "") + * \param: kdf_rounds - Number of rounds for key derivation function (Default: 1) + * \param: create_address_file - Whether to create an address file (Default: false) + * \param: unattended - Whether the wallet is unattended (GUI is unattended, CLI is not unattended) (Default: true) + * \return: Wallet instance (Wallet::status() needs to be called to check if recovered successfully) + */ + virtual Wallet * createWalletFromMultisigSeed(const std::string &path, + const std::string &password, + const std::string &language, + NetworkType nettype, + uint64_t restore_height, + const std::string &multisig_seed, + const std::string seed_pass = "", + uint64_t kdf_rounds = 1, + const bool create_address_file = false, + const bool unattended = true) = 0; /*! * \brief creates wallet using hardware device. @@ -1283,6 +2137,9 @@ struct WalletManager * \param subaddressLookahead Size of subaddress lookahead (empty sets to some default low value) * \param kdf_rounds Number of rounds for key derivation function * \param listener Wallet listener to set to the wallet after creation + * \param device_derivation_path + * \param create_address_file Whether to create an address file + * \param unattended Whether the wallet is unattended (GUI is unattended, CLI is not unattended) (Default: true) * \return Wallet instance (Wallet::status() needs to be called to check if recovered successfully) */ virtual Wallet * createWalletFromDevice(const std::string &path, @@ -1292,14 +2149,17 @@ struct WalletManager uint64_t restoreHeight = 0, const std::string &subaddressLookahead = "", uint64_t kdf_rounds = 1, - WalletListener * listener = nullptr) = 0; + WalletListener * listener = nullptr, + const std::string device_derivation_path = "", + const bool create_address_file = false, + const bool unattended = true) = 0; /*! * \brief Closes wallet. In case operation succeeded, wallet object deleted. in case operation failed, wallet object not deleted * \param wallet previously opened / created wallet instance * \return None */ - virtual bool closeWallet(Wallet *wallet, bool store = true) = 0; + virtual bool closeWallet(Wallet *wallet, bool store = true, bool do_delete_pointer = true) = 0; /* * ! checks if wallet with the given name already exists @@ -1369,6 +2229,12 @@ struct WalletManager //! returns current block target virtual uint64_t blockTarget() = 0; + //! returns true if bootstrap mode was used by daemon or if bootstrap daemon address is set + virtual bool wasBootstrapEverUsed() = 0; + + //! returns true iff backgound mining is enabled + virtual bool isBackgroundMiningEnabled() = 0; + //! returns true iff mining virtual bool isMining() = 0; @@ -1378,6 +2244,12 @@ struct WalletManager //! stops mining virtual bool stopMining() = 0; + //! saves the blockchain + virtual bool saveBlockchain() = 0; + + //! daemon RPC /get_outs + virtual bool getOutsBin(const std::vector> &output_amount_index, const bool do_get_txid, std::vector &enote_public_key_out, std::vector &rct_key_mask_out, std::vector &unlocked_out, std::vector &height_out, std::vector &tx_id_out) = 0; + //! resolves an OpenAlias address to a monero address virtual std::string resolveOpenAlias(const std::string &address, bool &dnssec_valid) const = 0; diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp index b18333e8f91..3116c985fec 100644 --- a/src/wallet/api/wallet_manager.cpp +++ b/src/wallet/api/wallet_manager.cpp @@ -30,6 +30,8 @@ #include "wallet_manager.h" +#include "misc_language.h" +#include "rpc/core_rpc_server_commands_defs.h" #include "wallet.h" #include "common_defines.h" #include "common/dns_utils.h" @@ -43,6 +45,23 @@ #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "WalletAPI" +namespace { +inline std::string interpret_rpc_response(bool ok, const std::string& status) +{ + std::string err; + if (ok) + { + if (status == CORE_RPC_STATUS_BUSY) + err = tr("daemon is busy. Please try again later."); + else if (status != CORE_RPC_STATUS_OK) + err = status; + } + else + err = tr("possibly lost connection to daemon"); + return err; +} +} // namespace + namespace Monero { WalletManagerImpl::WalletManagerImpl() @@ -51,16 +70,19 @@ WalletManagerImpl::WalletManagerImpl() } Wallet *WalletManagerImpl::createWallet(const std::string &path, const std::string &password, - const std::string &language, NetworkType nettype, uint64_t kdf_rounds) + const std::string &language, NetworkType nettype, uint64_t kdf_rounds, + const bool create_address_file /* = false */, + const bool non_deterministic /* = false */, + const bool unattended /* = true */) { - WalletImpl * wallet = new WalletImpl(nettype, kdf_rounds); - wallet->create(path, password, language); + WalletImpl * wallet = new WalletImpl(nettype, kdf_rounds, unattended); + wallet->create(path, password, language, create_address_file, non_deterministic); return wallet; } -Wallet *WalletManagerImpl::openWallet(const std::string &path, const std::string &password, NetworkType nettype, uint64_t kdf_rounds, WalletListener * listener) +Wallet *WalletManagerImpl::openWallet(const std::string &path, const std::string &password, NetworkType nettype, uint64_t kdf_rounds, WalletListener * listener, const bool unattended /* = true */) { - WalletImpl * wallet = new WalletImpl(nettype, kdf_rounds); + WalletImpl * wallet = new WalletImpl(nettype, kdf_rounds, unattended); wallet->setListener(listener); if (listener){ listener->onSetWallet(wallet); @@ -95,9 +117,10 @@ Wallet *WalletManagerImpl::recoveryWallet(const std::string &path, NetworkType nettype, uint64_t restoreHeight, uint64_t kdf_rounds, - const std::string &seed_offset/* = {}*/) + const std::string &seed_offset/* = {}*/, + const bool unattended /* = true */) { - WalletImpl * wallet = new WalletImpl(nettype, kdf_rounds); + WalletImpl * wallet = new WalletImpl(nettype, kdf_rounds, unattended); if(restoreHeight > 0){ wallet->setRefreshFromBlockHeight(restoreHeight); } @@ -113,9 +136,11 @@ Wallet *WalletManagerImpl::createWalletFromKeys(const std::string &path, const std::string &addressString, const std::string &viewKeyString, const std::string &spendKeyString, - uint64_t kdf_rounds) + uint64_t kdf_rounds, + const bool create_address_file /* = false */, + const bool unattended /* = true */) { - WalletImpl * wallet = new WalletImpl(nettype, kdf_rounds); + WalletImpl * wallet = new WalletImpl(nettype, kdf_rounds, unattended); if(restoreHeight > 0){ wallet->setRefreshFromBlockHeight(restoreHeight); } @@ -123,6 +148,35 @@ Wallet *WalletManagerImpl::createWalletFromKeys(const std::string &path, return wallet; } +Wallet *WalletManagerImpl::createWalletFromJson(const std::string &json_file_path, + NetworkType nettype, + std::string &pw_out, + uint64_t kdf_rounds /* = 1 */, + const bool unattended /* = true */) +{ + WalletImpl * wallet = new WalletImpl(nettype, kdf_rounds, unattended); + wallet->createFromJson(json_file_path, pw_out); + return wallet; +} +Wallet *WalletManagerImpl::createWalletFromMultisigSeed(const std::string &path, + const std::string &password, + const std::string &language, + NetworkType nettype, + uint64_t restoreHeight, + const std::string &multisig_seed, + const std::string seed_pass /* = "" */, + uint64_t kdf_rounds /* = 1 */, + const bool create_address_file /* = false */, + const bool unattended /* = true */) +{ + WalletImpl * wallet = new WalletImpl(nettype, kdf_rounds, unattended); + if(restoreHeight > 0){ + wallet->setRefreshFromBlockHeight(restoreHeight); + } + wallet->recoverFromMultisigSeed(path, password, language, multisig_seed, seed_pass, create_address_file); + return wallet; +} + Wallet *WalletManagerImpl::createWalletFromDevice(const std::string &path, const std::string &password, NetworkType nettype, @@ -130,14 +184,20 @@ Wallet *WalletManagerImpl::createWalletFromDevice(const std::string &path, uint64_t restoreHeight, const std::string &subaddressLookahead, uint64_t kdf_rounds, - WalletListener * listener) + WalletListener * listener, + const std::string device_derivation_path /* = "" */, + const bool create_address_file /* = false */, + const bool unattended /* = true */) { - WalletImpl * wallet = new WalletImpl(nettype, kdf_rounds); + WalletImpl * wallet = new WalletImpl(nettype, kdf_rounds, unattended); wallet->setListener(listener); if (listener){ listener->onSetWallet(wallet); } + if (!device_derivation_path.empty()) + wallet->setDeviceDerivationPath(device_derivation_path); + if(restoreHeight > 0){ wallet->setRefreshFromBlockHeight(restoreHeight); } else { @@ -152,7 +212,7 @@ Wallet *WalletManagerImpl::createWalletFromDevice(const std::string &path, return wallet; } -bool WalletManagerImpl::closeWallet(Wallet *wallet, bool store) +bool WalletManagerImpl::closeWallet(Wallet *wallet, bool store /* = true */, bool do_delete_pointer /* = true */) { WalletImpl * wallet_ = dynamic_cast(wallet); if (!wallet_) @@ -160,7 +220,7 @@ bool WalletManagerImpl::closeWallet(Wallet *wallet, bool store) bool result = wallet_->close(store); if (!result) { m_errorString = wallet_->errorString(); - } else { + } else if (do_delete_pointer) { delete wallet_; } return result; @@ -252,7 +312,9 @@ uint64_t WalletManagerImpl::blockchainHeight() cryptonote::COMMAND_RPC_GET_INFO::request ireq; cryptonote::COMMAND_RPC_GET_INFO::response ires; - if (!epee::net_utils::invoke_http_json("/getinfo", ireq, ires, m_http_client)) + bool r = epee::net_utils::invoke_http_json("/getinfo", ireq, ires, m_http_client); + m_errorString = interpret_rpc_response(r, ires.status); + if (!r) return 0; return ires.height; } @@ -262,7 +324,9 @@ uint64_t WalletManagerImpl::blockchainTargetHeight() cryptonote::COMMAND_RPC_GET_INFO::request ireq; cryptonote::COMMAND_RPC_GET_INFO::response ires; - if (!epee::net_utils::invoke_http_json("/getinfo", ireq, ires, m_http_client)) + bool r = epee::net_utils::invoke_http_json("/getinfo", ireq, ires, m_http_client); + m_errorString = interpret_rpc_response(r, ires.status); + if (!r) return 0; return ires.target_height >= ires.height ? ires.target_height : ires.height; } @@ -272,7 +336,9 @@ uint64_t WalletManagerImpl::networkDifficulty() cryptonote::COMMAND_RPC_GET_INFO::request ireq; cryptonote::COMMAND_RPC_GET_INFO::response ires; - if (!epee::net_utils::invoke_http_json("/getinfo", ireq, ires, m_http_client)) + bool r = epee::net_utils::invoke_http_json("/getinfo", ireq, ires, m_http_client); + m_errorString = interpret_rpc_response(r, ires.status); + if (!r) return 0; return ires.difficulty; } @@ -282,8 +348,9 @@ 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 (!epee::net_utils::invoke_http_json("/mining_status", mreq, mres, m_http_client)) + bool r = epee::net_utils::invoke_http_json("/mining_status", mreq, mres, m_http_client); + m_errorString = interpret_rpc_response(r, mres.status); + if (!r) return 0.0; if (!mres.active) return 0.0; @@ -295,17 +362,45 @@ uint64_t WalletManagerImpl::blockTarget() cryptonote::COMMAND_RPC_GET_INFO::request ireq; cryptonote::COMMAND_RPC_GET_INFO::response ires; - if (!epee::net_utils::invoke_http_json("/getinfo", ireq, ires, m_http_client)) + bool r = epee::net_utils::invoke_http_json("/getinfo", ireq, ires, m_http_client); + m_errorString = interpret_rpc_response(r, ires.status); + if (!r) return 0; return ires.target; } +bool WalletManagerImpl::wasBootstrapEverUsed() +{ + cryptonote::COMMAND_RPC_GET_INFO::request mreq; + cryptonote::COMMAND_RPC_GET_INFO::response mres; + + bool r = epee::net_utils::invoke_http_json("/getinfo", mreq, mres, m_http_client); + m_errorString = interpret_rpc_response(r, mres.status); + if (!r) + return false; + return (mres.was_bootstrap_ever_used || !mres.bootstrap_daemon_address.empty()); +} + +bool WalletManagerImpl::isBackgroundMiningEnabled() +{ + cryptonote::COMMAND_RPC_MINING_STATUS::request mreq; + cryptonote::COMMAND_RPC_MINING_STATUS::response mres; + + bool r = epee::net_utils::invoke_http_json("/mining_status", mreq, mres, m_http_client); + m_errorString = interpret_rpc_response(r, mres.status); + if (!r) + return false; + return mres.is_background_mining_enabled; +} + bool WalletManagerImpl::isMining() { cryptonote::COMMAND_RPC_MINING_STATUS::request mreq; cryptonote::COMMAND_RPC_MINING_STATUS::response mres; - if (!epee::net_utils::invoke_http_json("/mining_status", mreq, mres, m_http_client)) + bool r = epee::net_utils::invoke_http_json("/mining_status", mreq, mres, m_http_client); + m_errorString = interpret_rpc_response(r, mres.status); + if (!r) return false; return mres.active; } @@ -320,7 +415,9 @@ bool WalletManagerImpl::startMining(const std::string &address, uint32_t threads mreq.ignore_battery = ignore_battery; mreq.do_background_mining = background_mining; - if (!epee::net_utils::invoke_http_json("/start_mining", mreq, mres, m_http_client)) + bool r = epee::net_utils::invoke_http_json("/start_mining", mreq, mres, m_http_client); + m_errorString = interpret_rpc_response(r, mres.status); + if (!r) return false; return mres.status == CORE_RPC_STATUS_OK; } @@ -330,11 +427,58 @@ bool WalletManagerImpl::stopMining() cryptonote::COMMAND_RPC_STOP_MINING::request mreq; cryptonote::COMMAND_RPC_STOP_MINING::response mres; - if (!epee::net_utils::invoke_http_json("/stop_mining", mreq, mres, m_http_client)) + bool r = epee::net_utils::invoke_http_json("/stop_mining", mreq, mres, m_http_client); + m_errorString = interpret_rpc_response(r, mres.status); + if (!r) return false; return mres.status == CORE_RPC_STATUS_OK; } +bool WalletManagerImpl::saveBlockchain() +{ + cryptonote::COMMAND_RPC_SAVE_BC::request mreq; + cryptonote::COMMAND_RPC_SAVE_BC::response mres; + + bool r = epee::net_utils::invoke_http_json("/save_bc", mreq, mres, m_http_client); + m_errorString = interpret_rpc_response(r, mres.status); + if (!r) + return false; + return mres.status == CORE_RPC_STATUS_OK; +} + +bool WalletManagerImpl::getOutsBin(const std::vector> &output_amount_index, + const bool do_get_txid, + std::vector &enote_public_key_out, + std::vector &rct_key_mask_out, + std::vector &unlocked_out, + std::vector &height_out, + std::vector &tx_id_out) +{ + cryptonote::COMMAND_RPC_GET_OUTPUTS_BIN::request mreq = AUTO_VAL_INIT(mreq); + cryptonote::COMMAND_RPC_GET_OUTPUTS_BIN::response mres = AUTO_VAL_INIT(mres); + + mreq.outputs.resize(output_amount_index.size()); + for (size_t i = 0; i < output_amount_index.size(); ++i) + { + mreq.outputs[i].amount = output_amount_index[i].first; + mreq.outputs[i].index = output_amount_index[i].second; + } + mreq.get_txid = do_get_txid; + bool r = epee::net_utils::invoke_http_bin("/get_outs.bin", mreq, mres, m_http_client); + m_errorString = interpret_rpc_response(r, mres.status); + if (!r) + return false; + for (const auto &out : mres.outs) + { + enote_public_key_out.push_back(epee::string_tools::pod_to_hex(out.key)); + rct_key_mask_out.push_back(epee::string_tools::pod_to_hex(out.mask)); + unlocked_out.push_back(out.unlocked); + height_out.push_back(out.height); + tx_id_out.push_back(epee::string_tools::pod_to_hex(out.txid)); + } + return true; +} + std::string WalletManagerImpl::resolveOpenAlias(const std::string &address, bool &dnssec_valid) const { std::vector addresses = tools::dns_utils::addresses_from_url(address, dnssec_valid); diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h index 45a9f010f46..214c6328e7e 100644 --- a/src/wallet/api/wallet_manager.h +++ b/src/wallet/api/wallet_manager.h @@ -39,15 +39,16 @@ class WalletManagerImpl : public WalletManager { public: Wallet * createWallet(const std::string &path, const std::string &password, - const std::string &language, NetworkType nettype, uint64_t kdf_rounds = 1) override; - Wallet * openWallet(const std::string &path, const std::string &password, NetworkType nettype, uint64_t kdf_rounds = 1, WalletListener * listener = nullptr) override; + const std::string &language, NetworkType nettype, uint64_t kdf_rounds = 1, bool create_address_file = false, bool non_deterministic = false, bool unattended = true) override; + Wallet * openWallet(const std::string &path, const std::string &password, NetworkType nettype, uint64_t kdf_rounds = 1, WalletListener * listener = nullptr, bool unattended = true) override; virtual Wallet * recoveryWallet(const std::string &path, const std::string &password, const std::string &mnemonic, NetworkType nettype, uint64_t restoreHeight, uint64_t kdf_rounds = 1, - const std::string &seed_offset = {}) override; + const std::string &seed_offset = {}, + const bool unattended = true) override; virtual Wallet * createWalletFromKeys(const std::string &path, const std::string &password, const std::string &language, @@ -56,7 +57,24 @@ class WalletManagerImpl : public WalletManager const std::string &addressString, const std::string &viewKeyString, const std::string &spendKeyString = "", - uint64_t kdf_rounds = 1) override; + uint64_t kdf_rounds = 1, + const bool create_address_file = false, + const bool unattended = true) override; + Wallet * createWalletFromJson(const std::string &json_file_path, + NetworkType nettype, + std::string &pw_out, + uint64_t kdf_rounds = 1, + const bool unattended = true) override; + Wallet * createWalletFromMultisigSeed(const std::string &path, + const std::string &password, + const std::string &language, + NetworkType nettype, + uint64_t restoreHeight, + const std::string &multisig_seed, + const std::string seed_pass = "", + uint64_t kdf_rounds = 1, + const bool create_address_file = false, + const bool unattended = true) override; // next two methods are deprecated - use the above version which allow setting of a password virtual Wallet * recoveryWallet(const std::string &path, const std::string &mnemonic, NetworkType nettype, uint64_t restoreHeight) override; // deprecated: use createWalletFromKeys(..., password, ...) instead @@ -74,8 +92,11 @@ class WalletManagerImpl : public WalletManager uint64_t restoreHeight = 0, const std::string &subaddressLookahead = "", uint64_t kdf_rounds = 1, - WalletListener * listener = nullptr) override; - virtual bool closeWallet(Wallet *wallet, bool store = true) override; + WalletListener * listener = nullptr, + const std::string device_derivation_path = "", + const bool create_address_file = false, + const bool unattended = true) override; + virtual bool closeWallet(Wallet *wallet, bool store = true, bool do_delete_pointer = true) override; bool walletExists(const std::string &path) override; bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key, uint64_t kdf_rounds = 1) const override; bool queryWalletDevice(Wallet::Device& device_type, const std::string &keys_file_name, const std::string &password, uint64_t kdf_rounds = 1) const override; @@ -88,9 +109,13 @@ class WalletManagerImpl : public WalletManager uint64_t networkDifficulty() override; double miningHashRate() override; uint64_t blockTarget() override; + bool wasBootstrapEverUsed() override; + bool isBackgroundMiningEnabled() override; bool isMining() override; bool startMining(const std::string &address, uint32_t threads = 1, bool background_mining = false, bool ignore_battery = true) override; bool stopMining() override; + bool saveBlockchain() override; + bool getOutsBin(const std::vector> &output_amount_index, const bool do_get_txid, std::vector &enote_public_key_out, std::vector &rct_key_mask_out, std::vector &unlocked_out, std::vector &height_out, std::vector &tx_id_out) override; std::string resolveOpenAlias(const std::string &address, bool &dnssec_valid) const override; bool setProxy(const std::string &address) override; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index b7ec6d28c02..c7294da4f8f 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -1144,6 +1144,7 @@ wallet_keys_unlocker::~wallet_keys_unlocker() if (--lockers_per_wallet[w_ptr] > 0) return; // there are other unlock-ers for this wallet, do nothing for now lockers_per_wallet.erase(w_ptr); + w.encrypt_keys(key); } catch (...) @@ -2110,6 +2111,13 @@ size_t wallet2::get_transfer_details(const crypto::key_image &ki) const CHECK_AND_ASSERT_THROW_MES(false, "Key image not found"); } //---------------------------------------------------------------------------------------------------- +size_t wallet2::get_output_index(const crypto::public_key &pk) const +{ + auto search = m_pub_keys.find(pk); + CHECK_AND_ASSERT_THROW_MES(search != m_pub_keys.end(), "Public key not found in owned outputs"); + return search->second; +} +//---------------------------------------------------------------------------------------------------- bool wallet2::frozen(const transfer_details &td) const { return td.m_frozen; @@ -2570,7 +2578,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote } LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid); if (!ignore_callbacks && 0 != m_callback) - m_callback->on_money_received(height, txid, tx, td.m_amount, 0, td.m_subaddr_index, spends_one_of_ours(tx), td.m_tx.unlock_time); + m_callback->on_money_received(height, txid, tx, td.m_amount, 0, td.m_subaddr_index, spends_one_of_ours(tx), td.m_tx.unlock_time, td.get_public_key()); } total_received_1 += amount; notify = true; @@ -2648,7 +2656,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote LOG_PRINT_L0("Received money: " << print_money(td.amount()) << ", with tx: " << txid); if (!ignore_callbacks && 0 != m_callback) - m_callback->on_money_received(height, txid, tx, td.m_amount, burnt, td.m_subaddr_index, spends_one_of_ours(tx), td.m_tx.unlock_time); + m_callback->on_money_received(height, txid, tx, td.m_amount, burnt, td.m_subaddr_index, spends_one_of_ours(tx), td.m_tx.unlock_time, td.get_public_key()); } total_received_1 += extra_amount; notify = true; @@ -2702,7 +2710,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote if (!pool) { if (!ignore_callbacks && 0 != m_callback) - m_callback->on_money_spent(height, txid, tx, amount, tx, td.m_subaddr_index); + m_callback->on_money_spent(height, txid, tx, amount, tx, td.m_subaddr_index, td.get_public_key()); if (m_background_syncing && m_background_sync_data.txs.find(txid) == m_background_sync_data.txs.end()) { @@ -6127,7 +6135,7 @@ std::string wallet2::get_multisig_key_exchange_booster(const epee::wipeable_stri CHECK_AND_ASSERT_THROW_MES(kex_messages.size() > 0, "No key exchange messages passed in."); // decrypt account keys - wallet_keys_unlocker unlocker(*this, &password); + std::optional unlocker(std::in_place, *this, &password); // prepare multisig account multisig::multisig_account multisig_account; @@ -7994,7 +8002,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; @@ -8051,19 +8059,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"); @@ -8190,7 +8210,7 @@ bool wallet2::parse_multisig_tx_from_str(std::string multisig_tx_st, multisig_tx return true; } //---------------------------------------------------------------------------------------------------- -bool wallet2::load_multisig_tx(cryptonote::blobdata s, multisig_tx_set &exported_txs, std::function accept_func) +bool wallet2::load_multisig_tx(cryptonote::blobdata s, multisig_tx_set &exported_txs, std::function accept_func, bool skip_callback /* = false */) { if(!parse_multisig_tx_from_str(s, exported_txs)) { @@ -8201,30 +8221,18 @@ bool wallet2::load_multisig_tx(cryptonote::blobdata s, multisig_tx_set &exported LOG_PRINT_L1("Loaded multisig tx unsigned data from binary: " << exported_txs.m_ptx.size() << " transactions"); for (auto &ptx: exported_txs.m_ptx) LOG_PRINT_L0(cryptonote::obj_to_json_str(ptx.tx)); + if (skip_callback) return true; + if (accept_func && !accept_func(exported_txs)) { LOG_PRINT_L1("Transactions rejected by callback"); return false; } - - const bool is_signed = exported_txs.m_signers.size() >= m_multisig_threshold; - if (is_signed) - { - for (const auto &ptx: exported_txs.m_ptx) - { - const crypto::hash txid = get_transaction_hash(ptx.tx); - if (store_tx_info()) - { - m_tx_keys[txid] = ptx.tx_key; - m_additional_tx_keys[txid] = ptx.additional_tx_keys; - } - } - } - + finish_loading_accepted_multisig_tx(exported_txs); return true; } //---------------------------------------------------------------------------------------------------- -bool wallet2::load_multisig_tx_from_file(const std::string &filename, multisig_tx_set &exported_txs, std::function accept_func) +bool wallet2::load_multisig_tx_from_file(const std::string &filename, multisig_tx_set &exported_txs, std::function accept_func, bool skip_callback /* = false */) { std::string s; boost::system::error_code errcode; @@ -8240,7 +8248,7 @@ bool wallet2::load_multisig_tx_from_file(const std::string &filename, multisig_t return false; } - if (!load_multisig_tx(s, exported_txs, accept_func)) + if (!load_multisig_tx(s, exported_txs, accept_func, skip_callback)) { LOG_PRINT_L0("Failed to parse multisig tx data from " << filename); return false; @@ -8248,6 +8256,23 @@ bool wallet2::load_multisig_tx_from_file(const std::string &filename, multisig_t return true; } //---------------------------------------------------------------------------------------------------- +void wallet2::finish_loading_accepted_multisig_tx(multisig_tx_set &exported_txs) +{ + const bool is_signed = exported_txs.m_signers.size() >= m_multisig_threshold; + if (is_signed) + { + for (const auto &ptx: exported_txs.m_ptx) + { + const crypto::hash txid = get_transaction_hash(ptx.tx); + if (store_tx_info()) + { + m_tx_keys[txid] = ptx.tx_key; + m_additional_tx_keys[txid] = ptx.additional_tx_keys; + } + } + } +} +//---------------------------------------------------------------------------------------------------- bool wallet2::sign_multisig_tx(multisig_tx_set &exported_txs, std::vector &txids) { THROW_WALLET_EXCEPTION_IF(exported_txs.m_ptx.empty(), error::wallet_internal_error, "No tx found"); @@ -13190,6 +13215,11 @@ crypto::public_key wallet2::get_tx_pub_key_from_received_outs(const tools::walle bool wallet2::export_key_images(const std::string &filename, bool all) const { PERF_TIMER(export_key_images); + return save_to_file(filename, export_key_images_to_str(all)); +} + +std::string wallet2::export_key_images_to_str(bool all /* = false */) const +{ std::pair>> ski = export_key_images(all); std::string magic(KEY_IMAGE_EXPORT_FILE_MAGIC, strlen(KEY_IMAGE_EXPORT_FILE_MAGIC)); const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address; @@ -13213,7 +13243,7 @@ bool wallet2::export_key_images(const std::string &filename, bool all) const // encrypt data, keep magic plaintext PERF_TIMER(export_key_images_encrypt); std::string ciphertext = encrypt_with_view_secret_key(data); - return save_to_file(filename, magic + ciphertext); + return magic + ciphertext; } //---------------------------------------------------------------------------------------------------- @@ -13278,10 +13308,16 @@ uint64_t wallet2::import_key_images(const std::string &filename, uint64_t &spent THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, std::string(tr("failed to read file ")) + filename); + return import_key_images_from_str(data, spent, unspent); +} + +std::uint64_t wallet2::import_key_images_from_str(const std::string &key_images_str, std::uint64_t &spent, std::uint64_t &unspent) +{ + std::string data = key_images_str; const size_t magiclen = strlen(KEY_IMAGE_EXPORT_FILE_MAGIC); if (data.size() < magiclen || memcmp(data.data(), KEY_IMAGE_EXPORT_FILE_MAGIC, magiclen)) { - THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Bad key image export file magic in ") + filename); + THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Bad key image export file magic")); } try @@ -13291,24 +13327,24 @@ uint64_t wallet2::import_key_images(const std::string &filename, uint64_t &spent } catch (const std::exception &e) { - THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to decrypt ") + filename + ": " + e.what()); + THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Failed to decrypt: ") + e.what()); } const size_t headerlen = 4 + 2 * sizeof(crypto::public_key); - THROW_WALLET_EXCEPTION_IF(data.size() < headerlen, error::wallet_internal_error, std::string("Bad data size from file ") + filename); + THROW_WALLET_EXCEPTION_IF(data.size() < headerlen, error::wallet_internal_error, std::string("Bad data size from file ")); const uint32_t offset = (uint8_t)data[0] | (((uint8_t)data[1]) << 8) | (((uint8_t)data[2]) << 16) | (((uint8_t)data[3]) << 24); const crypto::public_key &public_spend_key = *(const crypto::public_key*)&data[4]; const crypto::public_key &public_view_key = *(const crypto::public_key*)&data[4 + sizeof(crypto::public_key)]; const cryptonote::account_public_address &keys = get_account().get_keys().m_account_address; if (public_spend_key != keys.m_spend_public_key || public_view_key != keys.m_view_public_key) { - THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string( "Key images from ") + filename + " are for a different account"); + THROW_WALLET_EXCEPTION(error::wallet_internal_error, std::string("Key images are for a different account")); } THROW_WALLET_EXCEPTION_IF(offset > m_transfers.size(), error::wallet_internal_error, "Offset larger than known outputs"); const size_t record_size = sizeof(crypto::key_image) + sizeof(crypto::signature); THROW_WALLET_EXCEPTION_IF((data.size() - headerlen) % record_size, - error::wallet_internal_error, std::string("Bad data size from file ") + filename); + error::wallet_internal_error, std::string("Bad data size ")); size_t nki = (data.size() - headerlen) / record_size; std::vector> ski; @@ -13557,7 +13593,7 @@ uint64_t wallet2::import_key_images(const std::vectorsecond, e.block_height); if (m_callback) - m_callback->on_money_spent(e.block_height, *spent_txid, spent_tx, amount, spent_tx, td.m_subaddr_index); + m_callback->on_money_spent(e.block_height, *spent_txid, spent_tx, amount, spent_tx, td.m_subaddr_index, td.get_public_key()); if (subaddr_account != (uint32_t)-1 && subaddr_account != td.m_subaddr_index.major) LOG_PRINT_L0("WARNING: This tx spends outputs received by different subaddress accounts, which isn't supposed to happen"); subaddr_account = td.m_subaddr_index.major; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index ff6111f9558..ac31d2da66d 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -118,7 +118,7 @@ namespace tools wallet_keys_unlocker(wallet2 &w, const epee::wipeable_string *password); ~wallet_keys_unlocker(); private: - wallet2 &w; + tools::wallet2 &w; bool should_relock; crypto::chacha_key key; static boost::mutex lockers_lock; @@ -131,9 +131,9 @@ namespace tools // Full wallet callbacks virtual void on_new_block(uint64_t height, const cryptonote::block& block) {} virtual void on_reorg(uint64_t height, uint64_t blocks_detached, size_t transfers_detached) {} - virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, uint64_t burnt, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time) {} + virtual void on_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, uint64_t burnt, const cryptonote::subaddress_index& subaddr_index, bool is_change, uint64_t unlock_time, const crypto::public_key& enote_pub_key) {} virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index) {} - virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx, const cryptonote::subaddress_index& subaddr_index) {} + virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx, const cryptonote::subaddress_index& subaddr_index, const crypto::public_key& enote_pub_key) {} virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx) {} virtual boost::optional on_get_password(const char *reason) { return boost::none; } // Device callbacks @@ -1193,7 +1193,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, tools::wallet2::signed_tx_set *signed_txs_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, fee_priority 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, fee_priority 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, fee_priority priority, const std::vector& extra); @@ -1204,8 +1205,11 @@ namespace tools uint64_t cold_key_image_sync(uint64_t &spent, uint64_t &unspent); void device_show_address(uint32_t account_index, uint32_t address_index, const boost::optional &payment_id); bool parse_multisig_tx_from_str(std::string multisig_tx_st, multisig_tx_set &exported_txs) const; - bool load_multisig_tx(cryptonote::blobdata blob, multisig_tx_set &exported_txs, std::function accept_func = NULL); - bool load_multisig_tx_from_file(const std::string &filename, multisig_tx_set &exported_txs, std::function accept_func = NULL); + // skip_callback = true allows the Wallet API to use it's own confirmation mechanism + bool load_multisig_tx(cryptonote::blobdata blob, multisig_tx_set &exported_txs, std::function accept_func = NULL, bool skip_callback = false); + bool load_multisig_tx_from_file(const std::string &filename, multisig_tx_set &exported_txs, std::function accept_func = NULL, bool skip_callback = false); + // call this after load_multisig_tx*(..., skip_callback = true), when loading the tx was accepted + void finish_loading_accepted_multisig_tx(multisig_tx_set &exported_txs); bool sign_multisig_tx_from_file(const std::string &filename, std::vector &txids, std::function accept_func); bool sign_multisig_tx(multisig_tx_set &exported_txs, std::vector &txids); bool sign_multisig_tx_to_file(multisig_tx_set &exported_txs, const std::string &filename, std::vector &txids); @@ -1537,6 +1541,8 @@ namespace tools uint64_t get_num_rct_outputs(); size_t get_num_transfer_details() const { return m_transfers.size(); } const transfer_details &get_transfer_details(size_t idx) const; + // similar to get_transfer_details(ki) below, but uses outputs pubkey instead of key image and this method is public + size_t get_output_index(const crypto::public_key &pk) const; uint8_t get_current_hard_fork(); void get_hard_fork_info(uint8_t version, uint64_t &earliest_height); @@ -1624,8 +1630,10 @@ namespace tools std::tuple> export_blockchain() const; void import_blockchain(const std::tuple> &bc); bool export_key_images(const std::string &filename, bool all = false) const; + std::string export_key_images_to_str(bool all = false) const; std::pair>> export_key_images(bool all = false) const; uint64_t import_key_images(const std::vector> &signed_key_images, size_t offset, uint64_t &spent, uint64_t &unspent, bool check_spent = true); + std::uint64_t import_key_images_from_str(const std::string &key_images_str, std::uint64_t &spent, std::uint64_t &unspent); uint64_t import_key_images(const std::string &filename, uint64_t &spent, uint64_t &unspent); bool import_key_images(std::vector key_images, size_t offset=0, boost::optional> selected_transfers=boost::none); bool import_key_images(signed_tx_set & signed_tx, size_t offset=0, bool only_selected_transfers=false);