From 753d7eee48d8e9ab5950e48dc984cc1038c11dd1 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Tue, 12 Mar 2024 11:07:57 +0100 Subject: [PATCH 08/14] coin control --- src/simplewallet/simplewallet.cpp | 2 +- src/wallet/api/CMakeLists.txt | 8 +- src/wallet/api/coins.cpp | 186 ++++++++++++++++++++++++++++++ src/wallet/api/coins.h | 40 +++++++ src/wallet/api/coins_info.cpp | 122 ++++++++++++++++++++ src/wallet/api/coins_info.h | 71 ++++++++++++ src/wallet/api/wallet.cpp | 170 +++++++++++++++++++++------ src/wallet/api/wallet.h | 10 +- src/wallet/api/wallet2_api.h | 52 ++++++++- src/wallet/wallet2.cpp | 46 +++++++- src/wallet/wallet2.h | 11 +- 11 files changed, 667 insertions(+), 51 deletions(-) create mode 100644 src/wallet/api/coins.cpp create mode 100644 src/wallet/api/coins.h create mode 100644 src/wallet/api/coins_info.cpp create mode 100644 src/wallet/api/coins_info.h diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 89691e9f7..b26817e52 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -7183,25 +7183,25 @@ bool simple_wallet::transfer_main( switch (transfer_type) { case Burn: unlock_block = 0; - ptx_vector = m_wallet->create_transactions_2(dsts, source_asset, dest_asset, cryptonote::transaction_type::BURN, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices, subtract_fee_from_outputs); + ptx_vector = m_wallet->create_transactions_2(dsts, source_asset, dest_asset, cryptonote::transaction_type::BURN, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices, {}, subtract_fee_from_outputs); break; case Convert: unlock_block = CONVERT_LOCK_PERIOD; - ptx_vector = m_wallet->create_transactions_2(dsts, source_asset, dest_asset, cryptonote::transaction_type::CONVERT, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices, subtract_fee_from_outputs); + ptx_vector = m_wallet->create_transactions_2(dsts, source_asset, dest_asset, cryptonote::transaction_type::CONVERT, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices, {}, subtract_fee_from_outputs); break; case Stake: unlock_block = get_config(m_wallet->nettype()).STAKE_LOCK_PERIOD; - ptx_vector = m_wallet->create_transactions_2(dsts, source_asset, dest_asset, cryptonote::transaction_type::STAKE, fake_outs_count, unlock_block, priority, extra, m_current_subaddress_account, subaddr_indices, subtract_fee_from_outputs); + ptx_vector = m_wallet->create_transactions_2(dsts, source_asset, dest_asset, cryptonote::transaction_type::STAKE, fake_outs_count, unlock_block, priority, extra, m_current_subaddress_account, subaddr_indices, {}, subtract_fee_from_outputs); break; case TransferLocked: unlock_block = locked_blocks; - ptx_vector = m_wallet->create_transactions_2(dsts, source_asset, dest_asset, cryptonote::transaction_type::TRANSFER, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices, subtract_fee_from_outputs); + ptx_vector = m_wallet->create_transactions_2(dsts, source_asset, dest_asset, cryptonote::transaction_type::TRANSFER, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices, {}, subtract_fee_from_outputs); break; default: LOG_ERROR("Unknown transfer method, using default"); /* FALLTHRU */ case Transfer: - ptx_vector = m_wallet->create_transactions_2(dsts, source_asset, dest_asset, cryptonote::transaction_type::TRANSFER, fake_outs_count, 0 /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices, subtract_fee_from_outputs); + ptx_vector = m_wallet->create_transactions_2(dsts, source_asset, dest_asset, cryptonote::transaction_type::TRANSFER, fake_outs_count, 0 /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices, {}, subtract_fee_from_outputs); break; } } diff --git a/src/wallet/api/CMakeLists.txt b/src/wallet/api/CMakeLists.txt index af7948d8a..bb740e2ac 100644 --- a/src/wallet/api/CMakeLists.txt +++ b/src/wallet/api/CMakeLists.txt @@ -40,7 +40,9 @@ set(wallet_api_sources address_book.cpp subaddress.cpp subaddress_account.cpp - unsigned_transaction.cpp) + unsigned_transaction.cpp + coins.cpp + coins_info.cpp) set(wallet_api_headers wallet2_api.h) @@ -55,7 +57,9 @@ set(wallet_api_private_headers address_book.h subaddress.h subaddress_account.h - unsigned_transaction.h) + unsigned_transaction.h + coins.h + coins_info.h) monero_private_headers(wallet_api ${wallet_api_private_headers}) diff --git a/src/wallet/api/coins.cpp b/src/wallet/api/coins.cpp new file mode 100644 index 000000000..ef12141cf --- /dev/null +++ b/src/wallet/api/coins.cpp @@ -0,0 +1,194 @@ +#include "coins.h" +#include "coins_info.h" +#include "wallet.h" +#include "crypto/hash.h" +#include "wallet/wallet2.h" +#include "common_defines.h" + +#include +#include + +using namespace epee; + +namespace Monero { + +Coins::~Coins() = default; + +CoinsImpl::CoinsImpl(WalletImpl *wallet) + : m_wallet(wallet) {} + +CoinsImpl::~CoinsImpl() +{ + for (auto t : m_rows) + delete t; +} + +int CoinsImpl::count() const +{ + boost::shared_lock lock(m_rowsMutex); + int result = m_rows.size(); + return result; +} + +CoinsInfo *CoinsImpl::coin(int index) const +{ + boost::shared_lock lock(m_rowsMutex); + // sanity check + if (index < 0) + return nullptr; + auto index_ = static_cast(index); + return index_ < m_rows.size() ? m_rows[index_] : nullptr; +} + +std::vector CoinsImpl::getAll() const +{ + boost::shared_lock lock(m_rowsMutex); + return m_rows; +} + + +void CoinsImpl::refresh() +{ + LOG_PRINT_L2("Refreshing coins"); + + boost::unique_lock lock(m_rowsMutex); + boost::shared_lock transfers_lock(m_wallet->m_wallet->m_transfers_mutex); + + // delete old outputs; + for (auto t : m_rows) + delete t; + m_rows.clear(); + + for (size_t i = 0; i < m_wallet->m_wallet->get_num_transfer_details(); ++i) + { + const tools::wallet2::transfer_details &td = m_wallet->m_wallet->get_transfer_details(i); + + // Make a subaddress_index_extended from td.m_subaddr_index + carrot::subaddress_index_extended csub{ + {td.m_subaddr_index.major, td.m_subaddr_index.minor}, + td.is_carrot() ? carrot::AddressDeriveType::Carrot : carrot::AddressDeriveType::PreCarrot, + false}; + + auto ci = new CoinsInfoImpl(); + ci->m_blockHeight = td.m_block_height; + ci->m_hash = string_tools::pod_to_hex(td.m_txid); + ci->m_internalOutputIndex = td.m_internal_output_index; + ci->m_globalOutputIndex = td.m_global_output_index; + ci->m_spent = td.m_spent; + ci->m_frozen = td.m_frozen; + ci->m_spentHeight = td.m_spent_height; + ci->m_amount = td.m_amount; + ci->m_rct = td.m_rct; + ci->m_keyImageKnown = td.m_key_image_known; + ci->m_pkIndex = td.m_pk_index; + ci->m_subaddrIndex = td.m_subaddr_index.minor; + ci->m_subaddrAccount = td.m_subaddr_index.major; + ci->m_address = m_wallet->m_wallet->get_subaddress_as_str(csub); // todo: this is expensive, cache maybe? + ci->m_addressLabel = m_wallet->m_wallet->get_subaddress_label(td.m_subaddr_index); + ci->m_keyImage = string_tools::pod_to_hex(td.m_key_image); + ci->m_unlockTime = td.m_tx.unlock_time; + ci->m_unlocked = m_wallet->m_wallet->is_transfer_unlocked(td); + ci->m_pubKey = string_tools::pod_to_hex(td.get_public_key()); + ci->m_coinbase = td.m_tx.vin.size() == 1 && td.m_tx.vin[0].type() == typeid(cryptonote::txin_gen); + ci->m_description = m_wallet->m_wallet->get_tx_note(td.m_txid); + ci->m_asset = td.asset_type; + ci->m_type = td.m_tx.type; + + m_rows.push_back(ci); + } +} + +void CoinsImpl::setFrozen(std::string public_key) +{ + crypto::public_key pk; + if (!epee::string_tools::hex_to_pod(public_key, pk)) + { + LOG_ERROR("Invalid public key: " << public_key); + return; + } + + try + { + m_wallet->m_wallet->freeze(pk); + refresh(); + } + catch (const std::exception& e) + { + LOG_ERROR("setFrozen: " << e.what()); + } +} + +void CoinsImpl::setFrozen(int index) +{ + try + { + LOG_ERROR("Freezing coin: " << index); + m_wallet->m_wallet->freeze(index); + refresh(); + } + catch (const std::exception& e) + { + LOG_ERROR("setLabel: " << e.what()); + } +} + +void CoinsImpl::thaw(std::string public_key) +{ + crypto::public_key pk; + if (!epee::string_tools::hex_to_pod(public_key, pk)) + { + LOG_ERROR("Invalid public key: " << public_key); + return; + } + + try + { + m_wallet->m_wallet->thaw(pk); + refresh(); + } + catch (const std::exception& e) + { + LOG_ERROR("thaw: " << e.what()); + } +} + +void CoinsImpl::thaw(int index) +{ + try + { + m_wallet->m_wallet->thaw(index); + refresh(); + } + catch (const std::exception& e) + { + LOG_ERROR("thaw: " << e.what()); + } +} + +bool CoinsImpl::isTransferUnlocked(uint64_t unlockTime, uint64_t blockHeight) { + return m_wallet->m_wallet->is_transfer_unlocked(unlockTime, blockHeight); +} + +void CoinsImpl::setDescription(const std::string &public_key, const std::string &description) +{ + crypto::public_key pk; + if (!epee::string_tools::hex_to_pod(public_key, pk)) + { + LOG_ERROR("Invalid public key: " << public_key); + return; + } + + try + { + const size_t index = m_wallet->m_wallet->get_transfer_details(pk); + const tools::wallet2::transfer_details &td = m_wallet->m_wallet->get_transfer_details(index); + m_wallet->m_wallet->set_tx_note(td.m_txid, description); + refresh(); + } + catch (const std::exception& e) + { + LOG_ERROR("setDescription: " << e.what()); + } +} + +} // namespace diff --git a/src/wallet/api/coins.h b/src/wallet/api/coins.h new file mode 100644 index 000000000..b7a0a8642 --- /dev/null +++ b/src/wallet/api/coins.h @@ -0,0 +1,40 @@ +#ifndef FEATHER_COINS_H +#define FEATHER_COINS_H + +#include "wallet/api/wallet2_api.h" +#include "wallet/wallet2.h" + +namespace Monero { + +class WalletImpl; + +class CoinsImpl : public Coins +{ +public: + explicit CoinsImpl(WalletImpl * wallet); + ~CoinsImpl() override; + int count() const override; + CoinsInfo * coin(int index) const override; + std::vector getAll() const override; + void refresh() override; + + void setFrozen(std::string public_key) override; + void setFrozen(int index) override; + void thaw(std::string public_key) override; + void thaw(int index) override; + + bool isTransferUnlocked(uint64_t unlockTime, uint64_t blockHeight) override; + + void setDescription(const std::string &public_key, const std::string &description) override; + +private: + WalletImpl *m_wallet; + std::vector m_rows; + mutable boost::shared_mutex m_rowsMutex; +}; + +} + +namespace Bitmonero = Monero; + +#endif //FEATHER_COINS_H diff --git a/src/wallet/api/coins_info.cpp b/src/wallet/api/coins_info.cpp new file mode 100644 index 000000000..5f2c4e1e4 --- /dev/null +++ b/src/wallet/api/coins_info.cpp @@ -0,0 +1,132 @@ +#include "coins_info.h" + +using namespace std; + +namespace Monero { + +CoinsInfo::~CoinsInfo() = default; + +CoinsInfoImpl::CoinsInfoImpl() + : m_blockHeight(0) + , m_internalOutputIndex(0) + , m_globalOutputIndex(0) + , m_spent(false) + , m_frozen(false) + , m_spentHeight(0) + , m_amount(0) + , m_rct(false) + , m_keyImageKnown(false) + , m_pkIndex(0) + , m_subaddrAccount(0) + , m_subaddrIndex(0) + , m_unlockTime(0) + , m_unlocked(false) + , m_type(0) +{ + +} + +CoinsInfoImpl::~CoinsInfoImpl() = default; + +uint64_t CoinsInfoImpl::blockHeight() const +{ + return m_blockHeight; +} + +string CoinsInfoImpl::hash() const +{ + return m_hash; +} + +size_t CoinsInfoImpl::internalOutputIndex() const { + return m_internalOutputIndex; +} + +uint64_t CoinsInfoImpl::globalOutputIndex() const +{ + return m_globalOutputIndex; +} + +bool CoinsInfoImpl::spent() const +{ + return m_spent; +} + +bool CoinsInfoImpl::frozen() const +{ + return m_frozen; +} + +uint64_t CoinsInfoImpl::spentHeight() const +{ + return m_spentHeight; +} + +uint64_t CoinsInfoImpl::amount() const +{ + return m_amount; +} + +bool CoinsInfoImpl::rct() const { + return m_rct; +} + +bool CoinsInfoImpl::keyImageKnown() const { + return m_keyImageKnown; +} + +size_t CoinsInfoImpl::pkIndex() const { + return m_pkIndex; +} + +uint32_t CoinsInfoImpl::subaddrIndex() const { + return m_subaddrIndex; +} + +uint32_t CoinsInfoImpl::subaddrAccount() const { + return m_subaddrAccount; +} + +string CoinsInfoImpl::address() const { + return m_address; +} + +string CoinsInfoImpl::addressLabel() const { + return m_addressLabel; +} + +string CoinsInfoImpl::keyImage() const { + return m_keyImage; +} + +uint64_t CoinsInfoImpl::unlockTime() const { + return m_unlockTime; +} + +bool CoinsInfoImpl::unlocked() const { + return m_unlocked; +} + +string CoinsInfoImpl::pubKey() const { + return m_pubKey; +} + +bool CoinsInfoImpl::coinbase() const { + return m_coinbase; +} + +string CoinsInfoImpl::description() const { + return m_description; +} + +string CoinsInfoImpl::asset() const { + return m_asset; +} + +uint8_t CoinsInfoImpl::type() const { + return m_type; +} + +} // namespace + +namespace Bitmonero = Monero; diff --git a/src/wallet/api/coins_info.h b/src/wallet/api/coins_info.h new file mode 100644 index 000000000..c43e45abd --- /dev/null +++ b/src/wallet/api/coins_info.h @@ -0,0 +1,75 @@ +#ifndef FEATHER_COINS_INFO_H +#define FEATHER_COINS_INFO_H + +#include "wallet/api/wallet2_api.h" +#include +#include + +namespace Monero { + +class CoinsImpl; + +class CoinsInfoImpl : public CoinsInfo +{ +public: + CoinsInfoImpl(); + ~CoinsInfoImpl(); + + virtual uint64_t blockHeight() const override; + virtual std::string hash() const override; + virtual size_t internalOutputIndex() const override; + virtual uint64_t globalOutputIndex() const override; + virtual bool spent() const override; + virtual bool frozen() const override; + virtual uint64_t spentHeight() const override; + virtual uint64_t amount() const override; + virtual bool rct() const override; + virtual bool keyImageKnown() const override; + virtual size_t pkIndex() const override; + virtual uint32_t subaddrIndex() const override; + virtual uint32_t subaddrAccount() const override; + virtual std::string address() const override; + virtual std::string addressLabel() const override; + virtual std::string keyImage() const override; + virtual uint64_t unlockTime() const override; + virtual bool unlocked() const override; + virtual std::string pubKey() const override; + virtual bool coinbase() const override; + virtual std::string description() const override; + virtual std::string asset() const override; + virtual uint8_t type() const override; + +private: + uint64_t m_blockHeight; + std::string m_hash; + size_t m_internalOutputIndex; + uint64_t m_globalOutputIndex; + bool m_spent; + bool m_frozen; + uint64_t m_spentHeight; + uint64_t m_amount; + bool m_rct; + bool m_keyImageKnown; + size_t m_pkIndex; + uint32_t m_subaddrIndex; + uint32_t m_subaddrAccount; + std::string m_address; + std::string m_addressLabel; + std::string m_keyImage; + uint64_t m_unlockTime; + bool m_unlocked; + std::string m_pubKey; + bool m_coinbase; + std::string m_description; + std::string m_asset; + uint8_t m_type; + + friend class CoinsImpl; + +}; + +} // namespace + +namespace Bitmonero = Monero; + +#endif //FEATHER_COINS_INFO_H diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 4b0a26a6b..644cf503b 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -36,6 +36,7 @@ #include "transaction_history.h" #include "address_book.h" #include "subaddress.h" +#include "coins.h" #include "subaddress_account.h" #include "common_defines.h" #include "common/util.h" @@ -444,6 +445,7 @@ WalletImpl::WalletImpl(NetworkType nettype, uint64_t kdf_rounds) m_wallet->set_refresh_enabled(false); m_addressBook.reset(new AddressBookImpl(this)); m_subaddress.reset(new SubaddressImpl(this)); + m_coins.reset(new CoinsImpl(this)); m_subaddressAccount.reset(new SubaddressAccountImpl(this)); @@ -2018,7 +2020,7 @@ PendingTransaction *WalletImpl::createAuditTransaction( // - unconfirmed_transfer_details; // - confirmed_transfer_details) -PendingTransaction *WalletImpl::createTransactionMultDest(const Monero::transaction_type &tx_type, const std::vector &dst_addr, const string &payment_id, optional> amount, uint32_t mixin_count, const std::string &asset_type, const bool is_return, PendingTransaction::Priority priority, uint32_t subaddr_account, std::set subaddr_indices) +PendingTransaction *WalletImpl::createTransactionMultDest(const Monero::transaction_type &tx_type, const std::vector &dst_addr, const string &payment_id, optional> amount, uint32_t mixin_count, const std::string &asset_type, const bool is_return, PendingTransaction::Priority priority, uint32_t subaddr_account, std::set subaddr_indices, const std::set &preferred_inputs) { clearStatus(); @@ -2057,55 +2059,114 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const Monero::transact break; } } - bool error = false; - for (size_t i = 0; i < dst_addr.size() && !error; i++) { - if(!cryptonote::get_account_address_from_str(info, m_wallet->nettype(), dst_addr[i])) { - // TODO: copy-paste 'if treating as an address fails, try as url' from simplewallet.cpp:1982 - setStatusError(tr("Invalid destination address")); - error = true; - break; - } - if (info.has_payment_id) { - if (!extra_nonce.empty()) { - setStatusError(tr("a single transaction cannot use more than one payment id")); + uint64_t max_coin_control_input = 0; + uint64_t max_frozen_input = 0; + try { + bool error = false; + uint64_t amountSum = 0; + for (size_t i = 0; i < dst_addr.size() && !error; i++) { + if(!cryptonote::get_account_address_from_str(info, m_wallet->nettype(), dst_addr[i])) { + // TODO: copy-paste 'if treating as an address fails, try as url' from simplewallet.cpp:1982 + setStatusError(tr("Invalid destination address")); error = true; break; } - set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, info.payment_id); + if (info.has_payment_id) { + if (!extra_nonce.empty()) { + setStatusError(tr("a single transaction cannot use more than one payment id")); + error = true; + break; + } + set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, info.payment_id); + } + + if (amount) { + cryptonote::tx_destination_entry de; + de.original = dst_addr[i]; + de.addr = info.address; + de.amount = (*amount)[i]; + de.asset_type = asset_type; + amountSum += (*amount)[i]; + de.is_subaddress = info.is_subaddress; + de.is_integrated = info.has_payment_id; + de.is_return = is_return; + dsts.push_back(de); + } else { + if (subaddr_indices.empty()) { + for (uint32_t index = 0; index < m_wallet->get_num_subaddresses(subaddr_account); ++index) + subaddr_indices.insert(index); + } + } } - if (amount) { - cryptonote::tx_destination_entry de; - de.original = dst_addr[i]; - de.addr = info.address; - de.amount = (*amount)[i]; - de.asset_type = asset_type; - de.is_subaddress = info.is_subaddress; - de.is_integrated = info.has_payment_id; - de.is_return = is_return; - dsts.push_back(de); - } else { - if (subaddr_indices.empty()) { - for (uint32_t index = 0; index < m_wallet->get_num_subaddresses(subaddr_account); ++index) - subaddr_indices.insert(index); + // uint64_t maxAllowedSpend = m_wallet->unlocked_balance(subaddr_account, true); + // if (maxAllowedSpend < amountSum) { + // error = true; + // setStatusError(tr("Amount you are trying to spend is larger than unlocked amount")); + // break; + // } + std::vector preferred_input_list; + if (!preferred_inputs.empty()) { + LOG_ERROR("not empty"); + + for (const auto &public_key : preferred_inputs) { + crypto::key_image keyImage; + bool r = epee::string_tools::hex_to_pod(public_key, keyImage); + if (!r) { + error = true; + setStatusError(tr("failed to parse key image")); + break; + } + if (m_wallet->frozen(keyImage)) { + error = true; + setStatusError(tr("refusing to spend frozen coin")); + break; } + + for (size_t i = 0; i < m_wallet->get_num_transfer_details(); ++i) { + const tools::wallet2::transfer_details &td = m_wallet->get_transfer_details(i); + if (td.m_key_image == keyImage) { + max_coin_control_input += td.amount(); + } + if (td.m_frozen) { + max_frozen_input += td.amount(); + } + } + + preferred_input_list.push_back(keyImage); + } + LOG_ERROR("not empty"); + + boost::shared_lock transfers_lock(m_wallet->m_transfers_mutex); + for (size_t i = 0; i < m_wallet->get_num_transfer_details(); ++i) { + const tools::wallet2::transfer_details &td = m_wallet->get_transfer_details(i); + LOG_ERROR("COIN: " << i << ": " << td.amount() << "; "<frozen(td)); + if (td.m_spent) continue; + LOG_ERROR("is frozen"); + if (!td.m_frozen) { + LOG_ERROR("isn't:"); + LOG_ERROR("hash: " << td.m_key_image << "; " << td.amount()); + preferred_input_list.push_back(td.m_key_image); + } + } + } + for (const auto &de : preferred_input_list) { + LOG_ERROR("preferred input: " << de); + } + if (error) { + break; + } + if (!extra_nonce.empty() && !add_extra_nonce_to_tx_extra(extra, extra_nonce)) { + setStatusError(tr("failed to set up payment id, though it was decoded correctly")); + break; } - } - if (error) { - break; - } - if (!extra_nonce.empty() && !add_extra_nonce_to_tx_extra(extra, extra_nonce)) { - setStatusError(tr("failed to set up payment id, though it was decoded correctly")); - break; - } - try { size_t fake_outs_count = mixin_count > 0 ? mixin_count : m_wallet->default_mixin(); fake_outs_count = m_wallet->adjust_mixin(mixin_count); if (amount) { transaction->m_pending_tx = m_wallet->create_transactions_2(dsts, asset_type, asset_type, converted_tx_type, fake_outs_count, 0 /* unlock_time */, adjusted_priority, - extra, subaddr_account, subaddr_indices); + extra, subaddr_account, subaddr_indices, preferred_input_list); } else { std::vector m_pending_txs; for (const auto subaddr_index : subaddr_indices) { @@ -2127,7 +2188,8 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const Monero::transact adjusted_priority, extra, subaddr_account, - std::set {subaddr_index} + std::set {subaddr_index}, + preferred_input_list ); m_pending_txs.insert(m_pending_txs.end(), result.begin(), result.end()); @@ -2165,6 +2227,16 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const Monero::transact writer << boost::format(tr("not enough money to transfer, available only %s, sent amount %s")) % print_money(e.available()) % print_money(e.tx_amount()); + if (max_coin_control_input != 0 && + max_coin_control_input != e.available()) { + writer << std::endl << boost::format(tr("In addition, coin control was enabled for this transaction, limiting available balance to %s. Make sure that you have enough outputs selected in coin control")) % + print_money(max_coin_control_input); + } + if (max_frozen_input != 0 && + max_frozen_input != e.available()) { + writer << std::endl << boost::format(tr("In addition, some a total of %s is frozen. Make sure that you have enough outputs unforzen outputs in coin control")) % + print_money(max_frozen_input); + } setStatusError(writer.str()); } catch (const tools::error::not_enough_money& e) { std::ostringstream writer; @@ -2172,6 +2244,16 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const Monero::transact writer << boost::format(tr("not enough money to transfer, overall balance only %s, sent amount %s")) % print_money(e.available()) % print_money(e.tx_amount()); + if (max_coin_control_input != 0 && + max_coin_control_input != e.available()) { + writer << std::endl << boost::format(tr("In addition, coin control was enabled for this transaction, limiting available balance to %s. Make sure that you have enough outputs selected in coin control")) % + print_money(max_coin_control_input); + } + if (max_frozen_input != 0 && + max_frozen_input != e.available()) { + writer << std::endl << boost::format(tr("In addition, some a total of %s is frozen. Make sure that you have enough outputs unforzen outputs in coin control")) % + print_money(max_frozen_input); + } setStatusError(writer.str()); } catch (const tools::error::tx_not_possible& e) { std::ostringstream writer; @@ -2179,6 +2261,16 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const Monero::transact print_money(e.tx_amount() + e.fee()) % print_money(e.tx_amount()) % print_money(e.fee()); + if (max_coin_control_input != 0 && + max_coin_control_input != e.available()) { + writer << std::endl << boost::format(tr("In addition, coin control was enabled for this transaction, limiting available balance to %s. Make sure that you have enough outputs selected in coin control")) % + print_money(max_coin_control_input); + } + if (max_frozen_input != 0 && + max_frozen_input != e.available()) { + writer << std::endl << boost::format(tr("In addition, some a total of %s is frozen. Make sure that you have enough outputs unforzen outputs in coin control")) % + print_money(max_frozen_input); + } setStatusError(writer.str()); } catch (const tools::error::not_enough_outs_to_mix& e) { std::ostringstream writer; @@ -2222,10 +2304,10 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const Monero::transact } PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const string &payment_id, optional amount, uint32_t mixin_count, - const std::string &asset_type, const bool is_return, PendingTransaction::Priority priority, uint32_t subaddr_account, std::set subaddr_indices) + const std::string &asset_type, const bool is_return, PendingTransaction::Priority priority, uint32_t subaddr_account, std::set subaddr_indices, const std::set &preferred_inputs) { - return createTransactionMultDest(Monero::transaction_type::TRANSFER, std::vector {dst_addr}, payment_id, amount ? (std::vector {*amount}) : (optional>()), mixin_count, asset_type, is_return, priority, subaddr_account, subaddr_indices); + return createTransactionMultDest(Monero::transaction_type::TRANSFER, std::vector {dst_addr}, payment_id, amount ? (std::vector {*amount}) : (optional>()), mixin_count, asset_type, is_return, priority, subaddr_account, subaddr_indices, preferred_inputs); } PendingTransaction *WalletImpl::createSweepUnmixableTransaction() @@ -2350,6 +2442,11 @@ AddressBook *WalletImpl::addressBook() return m_addressBook.get(); } +Coins *WalletImpl::coins() +{ + return m_coins.get(); +} + Subaddress *WalletImpl::subaddress() { return m_subaddress.get(); diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 32e12284b..a82f270e4 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -46,6 +46,7 @@ class PendingTransactionImpl; class UnsignedTransactionImpl; class AddressBookImpl; class SubaddressImpl; +class CoinsImpl; class SubaddressAccountImpl; struct Wallet2CallbackImpl; @@ -167,13 +168,15 @@ public: const std::string &asset_type, const bool is_return, PendingTransaction::Priority priority = PendingTransaction::Priority_Low, uint32_t subaddr_account = 0, - std::set subaddr_indices = {}) override; + std::set subaddr_indices = {}, + const std::set &preferred_inputs = {}) override; PendingTransaction * createTransaction(const std::string &dst_addr, const std::string &payment_id, optional amount, uint32_t mixin_count, const std::string &asset_type, const bool is_return, PendingTransaction::Priority priority = PendingTransaction::Priority_Low, uint32_t subaddr_account = 0, - std::set subaddr_indices = {}) override; + std::set subaddr_indices = {}, + const std::set &preferred_inputs = {}) override; virtual PendingTransaction * createSweepUnmixableTransaction() override; bool submitTransaction(const std::string &fileName) override; bool submitTransactionUR(const std::string &input) override; @@ -201,6 +204,7 @@ public: PendingTransaction::Priority priority) const override; virtual TransactionHistory * history() override; virtual AddressBook * addressBook() override; + virtual Coins * coins() override; virtual Subaddress * subaddress() override; virtual SubaddressAccount * subaddressAccount() override; virtual void setListener(WalletListener * l) override; @@ -272,6 +276,7 @@ private: friend class TransactionHistoryImpl; friend struct Wallet2CallbackImpl; friend class AddressBookImpl; + friend class CoinsImpl; friend class SubaddressImpl; friend class SubaddressAccountImpl; @@ -288,6 +293,7 @@ private: std::unique_ptr m_wallet2Callback; std::unique_ptr m_addressBook; std::unique_ptr m_subaddress; + std::unique_ptr m_coins; std::unique_ptr m_subaddressAccount; // multi-threaded refresh stuff diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index be1c3704e..013b5bcba 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -263,6 +263,53 @@ struct AddressBook virtual int lookupPaymentID(const std::string &payment_id) const = 0; }; +/** + * @brief The CoinsInfo - interface for displaying coins information + */ +struct CoinsInfo +{ + virtual ~CoinsInfo() = 0; + + virtual uint64_t blockHeight() const = 0; + virtual std::string hash() const = 0; + virtual size_t internalOutputIndex() const = 0; + virtual uint64_t globalOutputIndex() const = 0; + virtual bool spent() const = 0; + virtual bool frozen() const = 0; + virtual uint64_t spentHeight() const = 0; + virtual uint64_t amount() const = 0; + virtual bool rct() const = 0; + virtual bool keyImageKnown() const = 0; + virtual size_t pkIndex() const = 0; + virtual uint32_t subaddrIndex() const = 0; + virtual uint32_t subaddrAccount() const = 0; + virtual std::string address() const = 0; + virtual std::string addressLabel() const = 0; + virtual std::string keyImage() const = 0; + virtual uint64_t unlockTime() const = 0; + virtual bool unlocked() const = 0; + virtual std::string pubKey() const = 0; + virtual bool coinbase() const = 0; + virtual std::string description() const = 0; + virtual std::string asset() const = 0; + virtual uint8_t type() const = 0; +}; + +struct Coins +{ + virtual ~Coins() = 0; + virtual int count() const = 0; + virtual CoinsInfo * coin(int index) const = 0; + virtual std::vector getAll() const = 0; + virtual void refresh() = 0; + virtual void setFrozen(std::string public_key) = 0; + virtual void setFrozen(int index) = 0; + virtual void thaw(std::string public_key) = 0; + virtual void thaw(int index) = 0; + virtual bool isTransferUnlocked(uint64_t unlockTime, uint64_t blockHeight) = 0; + virtual void setDescription(const std::string &public_key, const std::string &description) = 0; +}; + struct SubaddressRow { public: SubaddressRow(std::size_t _rowId, const std::string &_address, const std::string &_label): @@ -940,7 +985,8 @@ struct Wallet const std::string &asset_type, const bool is_return, PendingTransaction::Priority = PendingTransaction::Priority_Low, uint32_t subaddr_account = 0, - std::set subaddr_indices = {}) = 0; + std::set subaddr_indices = {}, + const std::set &preferred_inputs = {}) = 0; /*! * \brief createTransaction creates transaction. if dst_addr is an integrated address, payment_id is ignored @@ -875,7 +921,8 @@ struct Wallet const std::string &asset_type, const bool is_return, PendingTransaction::Priority = PendingTransaction::Priority_Low, uint32_t subaddr_account = 0, - std::set subaddr_indices = {}) = 0; + std::set subaddr_indices = {}, + const std::set &preferred_inputs = {}) = 0; /*! * \brief createSweepUnmixableTransaction creates transaction with unmixable outputs. @@ -994,6 +1041,7 @@ struct Wallet virtual TransactionHistory * history() = 0; virtual AddressBook * addressBook() = 0; + virtual Coins * coins() = 0; virtual Subaddress * subaddress() = 0; virtual SubaddressAccount * subaddressAccount() = 0; virtual void setListener(WalletListener *) = 0; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 806de969a..8720e18b1 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -2100,12 +2100,21 @@ bool wallet2::frozen(const multisig_tx_set& txs) const return false; } +void wallet2::freeze(const crypto::public_key &pk) +{ + freeze(get_transfer_details(pk)); +} //---------------------------------------------------------------------------------------------------- void wallet2::freeze(const crypto::key_image &ki) { freeze(get_transfer_details(ki)); } //---------------------------------------------------------------------------------------------------- +void wallet2::thaw(const crypto::public_key &pk) +{ + thaw(get_transfer_details(pk)); +} +//---------------------------------------------------------------------------------------------------- void wallet2::thaw(const crypto::key_image &ki) { thaw(get_transfer_details(ki)); @@ -2116,6 +2125,18 @@ bool wallet2::frozen(const crypto::key_image &ki) const return frozen(get_transfer_details(ki)); } //---------------------------------------------------------------------------------------------------- +size_t wallet2::get_transfer_details(const crypto::public_key &pk) const +{ + for (size_t idx = 0; idx < m_transfers.size(); ++idx) + { + const transfer_details &td = m_transfers[idx]; + if (td.get_public_key() == pk) { + return idx; + } + } + CHECK_AND_ASSERT_THROW_MES(false, "Public key not found"); +} +//---------------------------------------------------------------------------------------------------- size_t wallet2::get_transfer_details(const crypto::key_image &ki) const { for (size_t idx = 0; idx < m_transfers.size(); ++idx) @@ -2621,6 +2643,7 @@ void wallet2::process_new_scanned_transaction(const crypto::hash &txid, const cryp continue; // update m_transfers view-incoming scan info, and default values + boost::unique_lock lock(m_transfers_mutex); transfer_details& td = m_transfers.emplace_back(); td.m_block_height = height; td.m_internal_output_index = local_output_index; @@ -11031,7 +11054,7 @@ void wallet2::transfer_selected_rct(std::vector wallet2::pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set &subaddr_indices, const std::string& asset_type) +std::vector wallet2::pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set &subaddr_indices, const std::string& asset_type, const std::vector& preferred_input_list) { std::vector picks; float current_output_relatdness = 1.0f; @@ -11048,6 +11071,9 @@ std::vector wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui for (size_t i: m_transfers_indices[asset_type]) { const transfer_details& td = m_transfers[i]; + if (!is_preferred_input(preferred_input_list, td.m_key_image)) { + continue; + } if (!is_spent(td, false) && !td.m_frozen && td.is_rct() && td.amount() >= needed_money && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) { if (td.amount() > m_ignore_outputs_above || td.amount() < m_ignore_outputs_below) @@ -11069,6 +11095,9 @@ std::vector wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui { size_t idx = *i; const transfer_details& td = m_transfers[idx]; + if (!is_preferred_input(preferred_input_list, td.m_key_image)) { + continue; + } if (!is_spent(td, false) && !td.m_frozen && !td.m_key_image_partial && td.is_rct() && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) { if (td.amount() > m_ignore_outputs_above || td.amount() < m_ignore_outputs_below) @@ -11084,6 +11113,9 @@ std::vector wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui { size_t idx2 = *j; const transfer_details& td2 = m_transfers[idx2]; + if (!is_preferred_input(preferred_input_list, td2.m_key_image)) { + continue; + } if (td2.amount() > m_ignore_outputs_above || td2.amount() < m_ignore_outputs_below) { MDEBUG("Ignoring output " << idx2 << " of amount " << print_money(td2.amount()) << " which is outside prescribed range [" << print_money(m_ignore_outputs_below) << ", " << print_money(m_ignore_outputs_above) << "]"); @@ -11125,7 +11157,7 @@ bool wallet2::light_wallet_key_image_is_ours(const crypto::key_image& key_image, // This system allows for sending (almost) the entire balance, since it does // not generate spurious change in all txes, thus decreasing the instantaneous // usable balance. -std::vector wallet2::create_transactions_2(std::vector dsts, const std::string& source_asset, const std::string& dest_asset, const cryptonote::transaction_type tx_type, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra, uint32_t subaddr_account, std::set subaddr_indices, const unique_index_container& subtract_fee_from_outputs) +std::vector wallet2::create_transactions_2(std::vector dsts, const std::string& source_asset, const std::string& dest_asset, const cryptonote::transaction_type tx_type, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra, uint32_t subaddr_account, std::set subaddr_indices, const std::vector& preferred_input_list, const unique_index_container& subtract_fee_from_outputs) { //ensure device is let in NONE mode in any case hw::device &hwdev = m_account.get_device(); @@ -11333,6 +11365,9 @@ std::vector wallet2::create_transactions_2(std::vector wallet2::create_transactions_2(std::vector &ptx_vector, c return true; } -std::vector wallet2::create_transactions_all(uint64_t below, cryptonote::transaction_type tx_type, const std::string &asset_type, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra, uint32_t subaddr_account, std::set subaddr_indices) +std::vector wallet2::create_transactions_all(uint64_t below, cryptonote::transaction_type tx_type, const std::string &asset_type, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra, uint32_t subaddr_account, std::set subaddr_indices, const std::vector& preferred_input_list) { const bool do_carrot_tx_construction = use_fork_rules(HF_VERSION_CARROT); if (do_carrot_tx_construction) @@ -12056,7 +12091,10 @@ std::vector wallet2::create_transactions_all(uint64_t below for (const auto& i: m_transfers_indices[asset_type]) { //size_t i = m_transfers_indices[asset_type][idx]; const transfer_details& td = m_transfers[i]; + if (!is_preferred_input(preferred_input_list, td.m_key_image)) { + continue; + } if (m_ignore_fractional_outputs && td.amount() < fractional_threshold) { MDEBUG("Ignoring output " << i << " of amount " << print_money(td.amount()) << " which is below threshold " << print_money(fractional_threshold)); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index a619bdd15..4f324c238 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -1286,8 +1286,8 @@ private: 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); - std::vector create_transactions_2(std::vector dsts, const std::string& source_asset, const std::string& dest_asset, const cryptonote::transaction_type tx_type, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra, uint32_t subaddr_account, std::set subaddr_indices, const unique_index_container& subtract_fee_from_outputs = {}); // pass subaddr_indices by value on purpose - std::vector create_transactions_all(uint64_t below, cryptonote::transaction_type tx_type, const std::string& asset_type, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra, uint32_t subaddr_account, std::set subaddr_indices); + std::vector create_transactions_2(std::vector dsts, const std::string& source_asset, const std::string& dest_asset, const cryptonote::transaction_type tx_type, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra, uint32_t subaddr_account, std::set subaddr_indices, const std::vector& preferred_input_list = {}, const unique_index_container& subtract_fee_from_outputs = {}); // pass subaddr_indices by value on purpose + std::vector create_transactions_all(uint64_t below, cryptonote::transaction_type tx_type, const std::string& asset_type, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra, uint32_t subaddr_account, std::set subaddr_indices, const std::vector& preferred_input_list = {}); std::vector create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, const cryptonote::transaction_type tx_type, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra); std::vector create_transactions_return(std::vector transfers_indices); std::vector create_transactions_from(const cryptonote::account_public_address &address, const cryptonote::transaction_type tx_type, const std::string& asset_type, bool is_subaddress, const size_t outputs, std::vector unused_transfers_indices, std::vector unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector& extra); @@ -1694,6 +1694,7 @@ private: 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; + size_t get_transfer_details(const crypto::public_key &pk) const; uint8_t estimate_current_hard_fork(const uint64_t height = 0) const; uint8_t get_current_hard_fork(); @@ -1835,7 +1836,9 @@ private: void freeze(size_t idx); void thaw(size_t idx); bool frozen(size_t idx) const; + void freeze(const crypto::public_key &pk); void freeze(const crypto::key_image &ki); + void thaw(const crypto::public_key &pk); void thaw(const crypto::key_image &ki); bool frozen(const crypto::key_image &ki) const; bool frozen(const transfer_details &td) const; @@ -1876,6 +1879,8 @@ private: static std::string get_default_daemon_address() { CRITICAL_REGION_LOCAL(default_daemon_address_lock); return default_daemon_address; } + boost::shared_mutex m_transfers_mutex; + bool get_pricing_record(oracle::pricing_record& pr, const uint64_t height); bool get_circulating_supply(std::vector> &amounts); bool get_yield_info(std::vector& ybi_data); @@ -1962,7 +1967,7 @@ private: std::vector get_unspent_amounts_vector(bool strict); uint64_t get_dynamic_base_fee_estimate(); float get_output_relatedness(const transfer_details &td0, const transfer_details &td1) const; - std::vector pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set &subaddr_indices, const std::string& asset_type); + std::vector pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set &subaddr_indices, const std::string& asset_type, const std::vector& preferred_input_list); void set_spent(size_t idx, uint64_t height); void set_spent(const crypto::key_image &ki, const uint64_t height); void set_unspent(size_t idx); -- 2.48.1