summaryrefslogtreecommitdiff
path: root/patches/wownero/0010-coin-control.patch
diff options
context:
space:
mode:
Diffstat (limited to 'patches/wownero/0010-coin-control.patch')
-rw-r--r--patches/wownero/0010-coin-control.patch979
1 files changed, 979 insertions, 0 deletions
diff --git a/patches/wownero/0010-coin-control.patch b/patches/wownero/0010-coin-control.patch
new file mode 100644
index 0000000..2362fed
--- /dev/null
+++ b/patches/wownero/0010-coin-control.patch
@@ -0,0 +1,979 @@
+From 14bcfd4bb49e7697c034d3d38c988f90a1842145 Mon Sep 17 00:00:00 2001
+From: tobtoht <tob@featherwallet.org>
+Date: Tue, 12 Mar 2024 11:07:57 +0100
+Subject: [PATCH 10/15] 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 | 64 +++++++++-
+ 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, 593 insertions(+), 19 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 8c5122097..cfdb8935f 100644
+--- a/src/simplewallet/simplewallet.cpp
++++ b/src/simplewallet/simplewallet.cpp
+@@ -6981,7 +6981,7 @@ bool simple_wallet::transfer_main(const std::vector<std::string> &args_, bool ca
+ {
+ // figure out what tx will be necessary
+ auto ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, priority, extra,
+- m_current_subaddress_account, subaddr_indices, subtract_fee_from_outputs);
++ m_current_subaddress_account, subaddr_indices, {}, subtract_fee_from_outputs);
+
+ if (ptx_vector.empty())
+ {
+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,186 @@
++#include "coins.h"
++#include "coins_info.h"
++#include "wallet.h"
++#include "crypto/hash.h"
++#include "wallet/wallet2.h"
++#include "common_defines.h"
++
++#include <string>
++#include <vector>
++
++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<boost::shared_mutex> lock(m_rowsMutex);
++ int result = m_rows.size();
++ return result;
++}
++
++CoinsInfo *CoinsImpl::coin(int index) const
++{
++ boost::shared_lock<boost::shared_mutex> lock(m_rowsMutex);
++ // sanity check
++ if (index < 0)
++ return nullptr;
++ auto index_ = static_cast<unsigned>(index);
++ return index_ < m_rows.size() ? m_rows[index_] : nullptr;
++}
++
++std::vector<CoinsInfo *> CoinsImpl::getAll() const
++{
++ boost::shared_lock<boost::shared_mutex> lock(m_rowsMutex);
++ return m_rows;
++}
++
++
++void CoinsImpl::refresh()
++{
++ LOG_PRINT_L2("Refreshing coins");
++
++ boost::unique_lock<boost::shared_mutex> lock(m_rowsMutex);
++ boost::shared_lock<boost::shared_mutex> 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);
++
++ 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(td.m_subaddr_index); // 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);
++
++ 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<CoinsInfo*> 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<CoinsInfo*> 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,122 @@
++#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)
++{
++
++}
++
++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;
++}
++} // 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,71 @@
++#ifndef FEATHER_COINS_INFO_H
++#define FEATHER_COINS_INFO_H
++
++#include "wallet/api/wallet2_api.h"
++#include <string>
++#include <ctime>
++
++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;
++
++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;
++
++ 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 704e5e148..e69910e69 100644
+--- a/src/wallet/api/wallet.cpp
++++ b/src/wallet/api/wallet.cpp
+@@ -35,6 +35,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"
+@@ -473,6 +474,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));
+
+
+@@ -2046,7 +2048,7 @@ PendingTransaction* WalletImpl::restoreMultisigTransaction(const string& signDat
+ // - unconfirmed_transfer_details;
+ // - confirmed_transfer_details)
+
+-PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector<string> &dst_addr, const string &payment_id, optional<std::vector<uint64_t>> amount, uint32_t mixin_count, PendingTransaction::Priority priority, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices)
++PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector<string> &dst_addr, const string &payment_id, optional<std::vector<uint64_t>> amount, uint32_t mixin_count, PendingTransaction::Priority priority, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, const std::set<std::string> &preferred_inputs)
+
+ {
+ clearStatus();
+@@ -2084,6 +2086,7 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector<stri
+ }
+ }
+ 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
+@@ -2105,6 +2108,7 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector<stri
+ de.original = dst_addr[i];
+ de.addr = info.address;
+ de.amount = (*amount)[i];
++ amountSum += (*amount)[i];
+ de.is_subaddress = info.is_subaddress;
+ de.is_integrated = info.has_payment_id;
+ dsts.push_back(de);
+@@ -2115,6 +2119,51 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector<stri
+ }
+ }
+ }
++ // 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<crypto::key_image> preferred_input_list;
++ if (!preferred_inputs.empty()) {
++ LOG_ERROR("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;
++ }
++
++ preferred_input_list.push_back(keyImage);
++ }
++ } else {
++ LOG_ERROR("not empty");
++
++ boost::shared_lock<boost::shared_mutex> 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() << "; "<<td.m_spent << ";" << td.m_frozen << ";" << m_wallet->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;
+ }
+@@ -2129,11 +2178,11 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector<stri
+ if (amount) {
+ transaction->m_pending_tx = m_wallet->create_transactions_2(dsts, fake_outs_count,
+ adjusted_priority,
+- extra, subaddr_account, subaddr_indices);
++ extra, subaddr_account, subaddr_indices, preferred_input_list);
+ } else {
+ transaction->m_pending_tx = m_wallet->create_transactions_all(0, info.address, info.is_subaddress, 1, fake_outs_count,
+ adjusted_priority,
+- extra, subaddr_account, subaddr_indices);
++ extra, subaddr_account, subaddr_indices, preferred_input_list);
+ }
+ pendingTxPostProcess(transaction);
+
+@@ -2214,10 +2263,10 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector<stri
+ }
+
+ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const string &payment_id, optional<uint64_t> amount, uint32_t mixin_count,
+- PendingTransaction::Priority priority, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices)
++ PendingTransaction::Priority priority, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, const std::set<std::string> &preferred_inputs)
+
+ {
+- return createTransactionMultDest(std::vector<string> {dst_addr}, payment_id, amount ? (std::vector<uint64_t> {*amount}) : (optional<std::vector<uint64_t>>()), mixin_count, priority, subaddr_account, subaddr_indices);
++ return createTransactionMultDest(std::vector<string> {dst_addr}, payment_id, amount ? (std::vector<uint64_t> {*amount}) : (optional<std::vector<uint64_t>>()), mixin_count, priority, subaddr_account, subaddr_indices, preferred_inputs);
+ }
+
+ PendingTransaction *WalletImpl::createSweepUnmixableTransaction()
+@@ -2342,6 +2391,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,12 +168,14 @@ public:
+ optional<std::vector<uint64_t>> amount, uint32_t mixin_count,
+ PendingTransaction::Priority priority = PendingTransaction::Priority_Low,
+ uint32_t subaddr_account = 0,
+- std::set<uint32_t> subaddr_indices = {}) override;
++ std::set<uint32_t> subaddr_indices = {},
++ const std::set<std::string> &preferred_inputs = {}) override;
+ PendingTransaction * createTransaction(const std::string &dst_addr, const std::string &payment_id,
+ optional<uint64_t> amount, uint32_t mixin_count,
+ PendingTransaction::Priority priority = PendingTransaction::Priority_Low,
+ uint32_t subaddr_account = 0,
+- std::set<uint32_t> subaddr_indices = {}) override;
++ std::set<uint32_t> subaddr_indices = {},
++ const std::set<std::string> &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<Wallet2CallbackImpl> m_wallet2Callback;
+ std::unique_ptr<AddressBookImpl> m_addressBook;
+ std::unique_ptr<SubaddressImpl> m_subaddress;
++ std::unique_ptr<CoinsImpl> m_coins;
+ std::unique_ptr<SubaddressAccountImpl> 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,51 @@ 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;
++};
++
++struct Coins
++{
++ virtual ~Coins() = 0;
++ virtual int count() const = 0;
++ virtual CoinsInfo * coin(int index) const = 0;
++ virtual std::vector<CoinsInfo*> 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):
+@@ -856,7 +901,8 @@ struct Wallet
+ optional<std::vector<uint64_t>> amount, uint32_t mixin_count,
+ PendingTransaction::Priority = PendingTransaction::Priority_Low,
+ uint32_t subaddr_account = 0,
+- std::set<uint32_t> subaddr_indices = {}) = 0;
++ std::set<uint32_t> subaddr_indices = {},
++ const std::set<std::string> &preferred_inputs = {}) = 0;
+
+ /*!
+ * \brief createTransaction creates transaction. if dst_addr is an integrated address, payment_id is ignored
+@@ -875,7 +921,8 @@ struct Wallet
+ optional<uint64_t> amount, uint32_t mixin_count,
+ PendingTransaction::Priority = PendingTransaction::Priority_Low,
+ uint32_t subaddr_account = 0,
+- std::set<uint32_t> subaddr_indices = {}) = 0;
++ std::set<uint32_t> subaddr_indices = {},
++ const std::set<std::string> &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 3e49c21f8..4d756567f 100644
+--- a/src/wallet/wallet2.cpp
++++ b/src/wallet/wallet2.cpp
+@@ -2094,12 +2094,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));
+@@ -2110,6 +2119,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)
+@@ -2523,6 +2544,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
+ uint64_t amount = tx.vout[o].amount ? tx.vout[o].amount : tx_scan_info[o].amount;
+ if (!pool)
+ {
++ boost::unique_lock<boost::shared_mutex> lock(m_transfers_mutex);
+ m_transfers.push_back(transfer_details{});
+ transfer_details& td = m_transfers.back();
+ td.m_block_height = height;
+@@ -2626,6 +2648,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
+ uint64_t extra_amount = amount - burnt;
+ if (!pool)
+ {
++ boost::unique_lock<boost::shared_mutex> lock(m_transfers_mutex);
+ transfer_details &td = m_transfers[kit->second];
+ td.m_block_height = height;
+ td.m_internal_output_index = o;
+@@ -10497,7 +10520,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
+ LOG_PRINT_L2("transfer_selected_rct done");
+ }
+
+-std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set<uint32_t> &subaddr_indices)
++std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set<uint32_t> &subaddr_indices, const std::vector<crypto::key_image>& preferred_input_list)
+ {
+ std::vector<size_t> picks;
+ float current_output_relatdness = 1.0f;
+@@ -10508,6 +10531,9 @@ std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui
+ for (size_t i = 0; i < m_transfers.size(); ++i)
+ {
+ 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)
+@@ -10528,6 +10554,9 @@ std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui
+ for (size_t i = 0; i < m_transfers.size(); ++i)
+ {
+ 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.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)
+@@ -10539,6 +10568,9 @@ std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui
+ for (size_t j = i + 1; j < m_transfers.size(); ++j)
+ {
+ const transfer_details& td2 = m_transfers[j];
++ 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 " << j << " of amount " << print_money(td2.amount()) << " which is outside prescribed range [" << print_money(m_ignore_outputs_below) << ", " << print_money(m_ignore_outputs_above) << "]");
+@@ -11111,7 +11143,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::pending_tx> wallet2::create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, const unique_index_container& subtract_fee_from_outputs)
++std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, const std::vector<crypto::key_image>& 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();
+@@ -11319,6 +11351,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
+ for (size_t i = 0; i < m_transfers.size(); ++i)
+ {
+ 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 fractional threshold " << print_money(fractional_threshold));
+@@ -11410,7 +11445,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
+ // will get us a known fee.
+ uint64_t estimated_fee = estimate_fee(use_per_byte_fee, use_rct, 2, fake_outs_count, 2, extra.size(), bulletproof, clsag, bulletproof_plus, use_view_tags, base_fee, fee_quantization_mask);
+ total_needed_money = needed_money + (subtract_fee_from_outputs.size() ? 0 : estimated_fee);
+- preferred_inputs = pick_preferred_rct_inputs(total_needed_money, subaddr_account, subaddr_indices);
++ preferred_inputs = pick_preferred_rct_inputs(total_needed_money, subaddr_account, subaddr_indices, preferred_input_list);
+ if (!preferred_inputs.empty())
+ {
+ string s;
+@@ -11889,7 +11924,7 @@ bool wallet2::sanity_check(const std::vector<wallet2::pending_tx> &ptx_vector, c
+ return true;
+ }
+
+-std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices)
++std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, const std::vector<crypto::key_image>& preferred_input_list)
+ {
+ std::vector<size_t> unused_transfers_indices;
+ std::vector<size_t> unused_dust_indices;
+@@ -11918,6 +11953,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below
+ for (size_t i = 0; i < m_transfers.size(); ++i)
+ {
+ 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 c26349ce3..a48ba33b6 100644
+--- a/src/wallet/wallet2.h
++++ b/src/wallet/wallet2.h
+@@ -1209,8 +1209,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<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set&)> accept_func = NULL);
+ bool parse_tx_from_str(const std::string &signed_tx_st, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set &)> accept_func);
+- std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, const unique_index_container& subtract_fee_from_outputs = {}); // pass subaddr_indices by value on purpose
+- std::vector<wallet2::pending_tx> create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices);
++ std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, const std::vector<crypto::key_image>& preferred_input_list = {}, const unique_index_container& subtract_fee_from_outputs = {}); // pass subaddr_indices by value on purpose
++ std::vector<wallet2::pending_tx> create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, const std::vector<crypto::key_image>& preferred_input_list = {});
+ std::vector<wallet2::pending_tx> create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, uint32_t priority, const std::vector<uint8_t>& extra);
+ std::vector<wallet2::pending_tx> create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, uint32_t priority, const std::vector<uint8_t>& extra);
+ bool sanity_check(const std::vector<wallet2::pending_tx> &ptx_vector, const std::vector<cryptonote::tx_destination_entry>& dsts, const unique_index_container& subtract_fee_from_outputs = {}) const;
+@@ -1562,6 +1562,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 get_current_hard_fork();
+ void get_hard_fork_info(uint8_t version, uint64_t &earliest_height);
+@@ -1793,7 +1794,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;
+@@ -1834,6 +1837,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;
++
+ private:
+ /*!
+ * \brief Stores wallet information to wallet file.
+@@ -1905,7 +1910,7 @@ private:
+ std::vector<uint64_t> 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<size_t> pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set<uint32_t> &subaddr_indices);
++ std::vector<size_t> pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set<uint32_t> &subaddr_indices, const std::vector<crypto::key_image>& preferred_input_list);
+ void set_spent(size_t idx, uint64_t height);
+ void set_unspent(size_t idx);
+ bool is_spent(const transfer_details &td, bool strict = true) const;
+--
+2.39.5 (Apple Git-154)
+