diff options
Diffstat (limited to 'src/wallet')
-rw-r--r-- | src/wallet/api/address_book.cpp | 54 | ||||
-rw-r--r-- | src/wallet/api/wallet.cpp | 79 | ||||
-rw-r--r-- | src/wallet/api/wallet.h | 5 | ||||
-rw-r--r-- | src/wallet/wallet2.cpp | 65 | ||||
-rw-r--r-- | src/wallet/wallet2.h | 6 | ||||
-rw-r--r-- | src/wallet/wallet2_api.h | 20 |
6 files changed, 189 insertions, 40 deletions
diff --git a/src/wallet/api/address_book.cpp b/src/wallet/api/address_book.cpp index b878491ce..e397dac04 100644 --- a/src/wallet/api/address_book.cpp +++ b/src/wallet/api/address_book.cpp @@ -33,6 +33,7 @@ #include "wallet.h" #include "crypto/hash.h" #include "wallet/wallet2.h" +#include "common_defines.h" #include <vector> @@ -43,30 +44,50 @@ AddressBook::~AddressBook() {} AddressBookImpl::AddressBookImpl(WalletImpl *wallet) : m_wallet(wallet) {} -bool AddressBookImpl::addRow(const std::string &dst_addr , const std::string &payment_id, const std::string &description) +bool AddressBookImpl::addRow(const std::string &dst_addr , const std::string &payment_id_str, const std::string &description) { - LOG_PRINT_L2("Adding row"); - clearStatus(); cryptonote::account_public_address addr; - bool has_payment_id; + bool has_short_pid; crypto::hash8 payment_id_short; - if(!cryptonote::get_account_integrated_address_from_str(addr, has_payment_id, payment_id_short, m_wallet->m_wallet->testnet(), dst_addr)) { - m_errorString = "Invalid destination address"; + if(!cryptonote::get_account_integrated_address_from_str(addr, has_short_pid, payment_id_short, m_wallet->m_wallet->testnet(), dst_addr)) { + m_errorString = tr("Invalid destination address"); m_errorCode = Invalid_Address; return false; } - crypto::hash pid32 = cryptonote::null_hash; - bool long_pid = (payment_id.empty())? false : tools::wallet2::parse_long_payment_id(payment_id, pid32); - if(!payment_id.empty() && !long_pid) { - m_errorString = "Invalid payment ID"; + crypto::hash payment_id = cryptonote::null_hash; + bool has_long_pid = (payment_id_str.empty())? false : tools::wallet2::parse_long_payment_id(payment_id_str, payment_id); + + // Short payment id provided + if(payment_id_str.length() == 16) { + m_errorString = tr("Invalid payment ID. Short payment ID should only be used in an integrated address"); + m_errorCode = Invalid_Payment_Id; + return false; + } + + // long payment id provided but not valid + if(!payment_id_str.empty() && !has_long_pid) { + m_errorString = tr("Invalid payment ID"); + m_errorCode = Invalid_Payment_Id; + return false; + } + + // integrated + long payment id provided + if(has_long_pid && has_short_pid) { + m_errorString = tr("Integrated address and long payment id can't be used at the same time"); m_errorCode = Invalid_Payment_Id; return false; } - bool r = m_wallet->m_wallet->add_address_book_row(addr,pid32,description); + // Pad short pid with zeros + if (has_short_pid) + { + memcpy(payment_id.data, payment_id_short.data, 8); + } + + bool r = m_wallet->m_wallet->add_address_book_row(addr,payment_id,description); if (r) refresh(); else @@ -87,7 +108,16 @@ void AddressBookImpl::refresh() std::string payment_id = (row->m_payment_id == cryptonote::null_hash)? "" : epee::string_tools::pod_to_hex(row->m_payment_id); std::string address = cryptonote::get_account_address_as_str(m_wallet->m_wallet->testnet(),row->m_address); - + // convert the zero padded short payment id to integrated address + if (payment_id.length() > 16 && payment_id.substr(16).find_first_not_of('0') == std::string::npos) { + payment_id = payment_id.substr(0,16); + crypto::hash8 payment_id_short; + if(tools::wallet2::parse_short_payment_id(payment_id, payment_id_short)) { + address = cryptonote::get_account_integrated_address_as_str(m_wallet->m_wallet->testnet(), row->m_address, payment_id_short); + // Don't show payment id when integrated address is used + payment_id = ""; + } + } AddressBookRow * abr = new AddressBookRow(i, address, payment_id, row->m_description); m_rows.push_back(abr); } diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index e8ae7c642..5f7d8e522 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -54,6 +54,8 @@ namespace { static const int MAX_REFRESH_INTERVAL_MILLIS = 1000 * 60 * 1; // Default refresh interval when connected to remote node static const int DEFAULT_REMOTE_NODE_REFRESH_INTERVAL_MILLIS = 1000 * 10; + // Connection timeout 30 sec + static const int DEFAULT_CONNECTION_TIMEOUT_MILLIS = 1000 * 30; } struct Wallet2CallbackImpl : public tools::i_wallet2_callback @@ -83,7 +85,7 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback virtual void on_new_block(uint64_t height, const cryptonote::block& block) { - LOG_PRINT_L3(__FUNCTION__ << ": new block. height: " << height); + //LOG_PRINT_L3(__FUNCTION__ << ": new block. height: " << height); if (m_listener) { m_listener->newBlock(height); @@ -662,25 +664,68 @@ UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_file bool WalletImpl::submitTransaction(const string &fileName) { clearStatus(); - PendingTransactionImpl * transaction = new PendingTransactionImpl(*this); + std::unique_ptr<PendingTransactionImpl> transaction(new PendingTransactionImpl(*this)); -// bool r = m_wallet->load_tx(fileName, transaction->m_pending_tx, [&](const tools::wallet2::signed_tx_set &tx){ return accept_loaded_tx(tx); }); bool r = m_wallet->load_tx(fileName, transaction->m_pending_tx); if (!r) { m_errorString = tr("Failed to load transaction from file"); m_status = Status_Ok; - delete transaction; return false; } if(!transaction->commit()) { m_errorString = transaction->m_errorString; m_status = Status_Error; - delete transaction; return false; } - delete transaction; + return true; +} + +bool WalletImpl::exportKeyImages(const string &filename) +{ + if (m_wallet->watch_only()) + { + m_errorString = tr("Wallet is view only"); + m_status = Status_Error; + return false; + } + + try + { + if (!m_wallet->export_key_images(filename)) + { + m_errorString = tr("failed to save file ") + filename; + m_status = Status_Error; + return false; + } + } + catch (std::exception &e) + { + LOG_ERROR("Error exporting key images: " << e.what()); + m_errorString = e.what(); + m_status = Status_Error; + return false; + } + return true; +} + +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"); + } + catch (const std::exception &e) + { + LOG_ERROR("Error exporting key images: " << e.what()); + m_errorString = string(tr("Failed to import key images: ")) + e.what(); + m_status = Status_Error; + return false; + } return true; } @@ -1043,7 +1088,7 @@ bool WalletImpl::verifySignedMessage(const std::string &message, const std::stri bool WalletImpl::connectToDaemon() { - bool result = m_wallet->check_connection(); + bool result = m_wallet->check_connection(NULL, DEFAULT_CONNECTION_TIMEOUT_MILLIS); m_status = result ? Status_Ok : Status_Error; if (!result) { m_errorString = "Error connecting to daemon at " + m_wallet->get_daemon_address(); @@ -1056,7 +1101,7 @@ bool WalletImpl::connectToDaemon() Wallet::ConnectionStatus WalletImpl::connected() const { uint32_t version = 0; - m_is_connected = m_wallet->check_connection(&version); + m_is_connected = m_wallet->check_connection(&version, DEFAULT_CONNECTION_TIMEOUT_MILLIS); if (!m_is_connected) return Wallet::ConnectionStatus_Disconnected; if ((version >> 16) != CORE_RPC_VERSION_MAJOR) @@ -1217,6 +1262,24 @@ bool WalletImpl::parse_uri(const std::string &uri, std::string &address, std::st return m_wallet->parse_uri(uri, address, payment_id, amount, tx_description, recipient_name, unknown_parameters, error); } +bool WalletImpl::rescanSpent() +{ + clearStatus(); + if (!trustedDaemon()) { + m_status = Status_Error; + m_errorString = tr("Rescan spent can only be used with a trusted daemon"); + return false; + } + try { + m_wallet->rescan_spent(); + } catch (const std::exception &e) { + LOG_ERROR(__FUNCTION__ << " error: " << e.what()); + m_status = Status_Error; + m_errorString = e.what(); + return false; + } + return true; +} } // namespace namespace Bitmonero = Monero; diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 5b4064e8e..e26f30d70 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -93,7 +93,7 @@ public: void setRefreshFromBlockHeight(uint64_t refresh_from_block_height); void setRecoveringFromSeed(bool recoveringFromSeed); bool watchOnly() const; - + bool rescanSpent(); PendingTransaction * createTransaction(const std::string &dst_addr, const std::string &payment_id, @@ -102,6 +102,8 @@ public: virtual PendingTransaction * createSweepUnmixableTransaction(); bool submitTransaction(const std::string &fileName); virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename); + bool exportKeyImages(const std::string &filename); + bool importKeyImages(const std::string &filename); virtual void disposeTransaction(PendingTransaction * t); virtual TransactionHistory * history() const; @@ -127,6 +129,7 @@ private: bool isNewWallet() const; void doInit(const std::string &daemon_address, uint64_t upper_transaction_size_limit); + private: friend class PendingTransactionImpl; friend class UnsignedTransactionImpl; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 0ad74f52a..ccfe86ee0 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -2234,7 +2234,7 @@ bool wallet2::prepare_file_names(const std::string& file_path) return true; } //---------------------------------------------------------------------------------------------------- -bool wallet2::check_connection(uint32_t *version) +bool wallet2::check_connection(uint32_t *version, uint32_t timeout) { boost::lock_guard<boost::mutex> lock(m_daemon_rpc_mutex); @@ -2248,7 +2248,7 @@ bool wallet2::check_connection(uint32_t *version) u.port = m_testnet ? config::testnet::RPC_DEFAULT_PORT : config::RPC_DEFAULT_PORT; } - if (!m_http_client.connect(u.host, std::to_string(u.port), 10000)) + if (!m_http_client.connect(u.host, std::to_string(u.port), timeout)) return false; } @@ -2733,7 +2733,7 @@ float wallet2::get_output_relatedness(const transfer_details &td0, const transfe return 0.0f; } //---------------------------------------------------------------------------------------------------- -size_t wallet2::pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_indices, const std::list<size_t>& selected_transfers) const +size_t wallet2::pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_indices, const std::list<size_t>& selected_transfers, bool smallest) const { std::vector<size_t> candidates; float best_relatedness = 1.0f; @@ -2761,13 +2761,30 @@ size_t wallet2::pop_best_value_from(const transfer_container &transfers, std::ve if (relatedness == best_relatedness) candidates.push_back(n); } - size_t idx = crypto::rand<size_t>() % candidates.size(); + + // we have all the least related outputs in candidates, so we can pick either + // the smallest, or a random one, depending on request + size_t idx; + if (smallest) + { + idx = 0; + for (size_t n = 0; n < candidates.size(); ++n) + { + const transfer_details &td = transfers[unused_indices[candidates[n]]]; + if (td.amount() < transfers[unused_indices[candidates[idx]]].amount()) + idx = n; + } + } + else + { + idx = crypto::rand<size_t>() % candidates.size(); + } return pop_index (unused_indices, candidates[idx]); } //---------------------------------------------------------------------------------------------------- -size_t wallet2::pop_best_value(std::vector<size_t> &unused_indices, const std::list<size_t>& selected_transfers) const +size_t wallet2::pop_best_value(std::vector<size_t> &unused_indices, const std::list<size_t>& selected_transfers, bool smallest) const { - return pop_best_value_from(m_transfers, unused_indices, selected_transfers); + return pop_best_value_from(m_transfers, unused_indices, selected_transfers, smallest); } //---------------------------------------------------------------------------------------------------- // Select random input sources for transaction. @@ -4109,8 +4126,11 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp } } - // while we have something to send - while ((!dsts.empty() && dsts[0].amount > 0) || adding_fee) { + // while: + // - we have something to send + // - or we need to gather more fee + // - or we have just one input in that tx, which is rct (to try and make all/most rct txes 2/2) + while ((!dsts.empty() && dsts[0].amount > 0) || adding_fee || (use_rct && txes.back().selected_transfers.size() == 1)) { TX &tx = txes.back(); // if we need to spend money and don't have any left, we fail @@ -4121,7 +4141,14 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp // get a random unspent output and use it to pay part (or all) of the current destination (and maybe next one, etc) // This could be more clever, but maybe at the cost of making probabilistic inferences easier - size_t idx = !prefered_inputs.empty() ? pop_back(prefered_inputs) : !unused_transfers_indices.empty() ? pop_best_value(unused_transfers_indices, tx.selected_transfers) : pop_best_value(unused_dust_indices, tx.selected_transfers); + size_t idx; + if ((dsts.empty() || dsts[0].amount == 0) && !adding_fee) + // the "make rct txes 2/2" case - we pick a small value output to "clean up" the wallet too + idx = pop_best_value(unused_dust_indices.empty() ? unused_transfers_indices : unused_dust_indices, tx.selected_transfers, true); + else if (!prefered_inputs.empty()) + idx = pop_back(prefered_inputs); + else + idx = pop_best_value(unused_transfers_indices.empty() ? unused_dust_indices : unused_transfers_indices, tx.selected_transfers); const transfer_details &td = m_transfers[idx]; LOG_PRINT_L2("Picking output " << idx << ", amount " << print_money(td.amount()) << ", ki " << td.m_key_image); @@ -4228,13 +4255,19 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp else { LOG_PRINT_L2("We made a tx, adjusting fee and saving it"); - if (use_rct) - transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, - test_tx, test_ptx); - else - transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, - detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); - txBlob = t_serializable_object_to_blob(test_ptx.tx); + do { + if (use_rct) + transfer_selected_rct(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, + test_tx, test_ptx); + else + transfer_selected(tx.dsts, tx.selected_transfers, fake_outs_count, unlock_time, needed_fee, extra, + detail::digit_split_strategy, tx_dust_policy(::config::DEFAULT_DUST_THRESHOLD), test_tx, test_ptx); + txBlob = t_serializable_object_to_blob(test_ptx.tx); + needed_fee = calculate_fee(fee_per_kb, txBlob, fee_multiplier); + LOG_PRINT_L2("Made an attempt at a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) << + " fee and " << print_money(test_ptx.change_dts.amount) << " change"); + } while (needed_fee > test_ptx.fee); + LOG_PRINT_L2("Made a final " << ((txBlob.size() + 1023)/1024) << " kB tx, with " << print_money(test_ptx.fee) << " fee and " << print_money(test_ptx.change_dts.amount) << " change"); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 7f25673d6..5a569950f 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -402,7 +402,7 @@ namespace tools std::vector<wallet2::pending_tx> create_transactions_all(const cryptonote::account_public_address &address, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon); std::vector<wallet2::pending_tx> create_transactions_from(const cryptonote::account_public_address &address, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t> extra, bool trusted_daemon); std::vector<pending_tx> create_unmixable_sweep_transactions(bool trusted_daemon); - bool check_connection(uint32_t *version = NULL); + bool check_connection(uint32_t *version = NULL, uint32_t timeout = 200000); void get_transfers(wallet2::transfer_container& incoming_transfers) const; void get_payments(const crypto::hash& payment_id, std::list<wallet2::payment_details>& payments, uint64_t min_height = 0) const; void get_payments(std::list<std::pair<crypto::hash,wallet2::payment_details>>& payments, uint64_t min_height, uint64_t max_height = (uint64_t)-1) const; @@ -527,8 +527,8 @@ namespace tools std::vector<size_t> select_available_unmixable_outputs(bool trusted_daemon); std::vector<size_t> select_available_mixable_outputs(bool trusted_daemon); - size_t pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_dust_indices, const std::list<size_t>& selected_transfers) const; - size_t pop_best_value(std::vector<size_t> &unused_dust_indices, const std::list<size_t>& selected_transfers) const; + size_t pop_best_value_from(const transfer_container &transfers, std::vector<size_t> &unused_dust_indices, const std::list<size_t>& selected_transfers, bool smallest = false) const; + size_t pop_best_value(std::vector<size_t> &unused_dust_indices, const std::list<size_t>& selected_transfers, bool smallest = false) const; void set_tx_note(const crypto::hash &txid, const std::string ¬e); std::string get_tx_note(const crypto::hash &txid) const; diff --git a/src/wallet/wallet2_api.h b/src/wallet/wallet2_api.h index daffb48bf..5a13205c5 100644 --- a/src/wallet/wallet2_api.h +++ b/src/wallet/wallet2_api.h @@ -513,6 +513,21 @@ struct Wallet */ virtual void disposeTransaction(PendingTransaction * t) = 0; + /*! + * \brief exportKeyImages - exports key images to file + * \param filename + * \return - true on success + */ + virtual bool exportKeyImages(const std::string &filename) = 0; + + /*! + * \brief importKeyImages - imports key images from file + * \param filename + * \return - true on success + */ + virtual bool importKeyImages(const std::string &filename) = 0; + + virtual TransactionHistory * history() const = 0; virtual AddressBook * addressBook() const = 0; virtual void setListener(WalletListener *) = 0; @@ -558,6 +573,11 @@ struct Wallet virtual bool verifySignedMessage(const std::string &message, const std::string &addres, const std::string &signature) const = 0; virtual bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &amount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error) = 0; + /* + * \brief rescanSpent - Rescan spent outputs - Can only be used with trusted daemon + * \return true on success + */ + virtual bool rescanSpent() = 0; }; /** |