diff options
Diffstat (limited to 'src/wallet/wallet2.cpp')
-rw-r--r-- | src/wallet/wallet2.cpp | 96 |
1 files changed, 80 insertions, 16 deletions
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 841c5c2ab..7871fe99c 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -66,6 +66,7 @@ using namespace epee; #include "memwipe.h" #include "common/base58.h" #include "common/dns_utils.h" +#include "common/notify.h" #include "ringct/rctSigs.h" #include "ringdb.h" @@ -113,9 +114,9 @@ using namespace cryptonote; #define OUTPUT_EXPORT_FILE_MAGIC "Monero output export\003" -#define SEGREGATION_FORK_HEIGHT 1546000 -#define TESTNET_SEGREGATION_FORK_HEIGHT 1000000 -#define STAGENET_SEGREGATION_FORK_HEIGHT 1000000 +#define SEGREGATION_FORK_HEIGHT 99999999 +#define TESTNET_SEGREGATION_FORK_HEIGHT 99999999 +#define STAGENET_SEGREGATION_FORK_HEIGHT 99999999 #define SEGREGATION_FORK_VICINITY 1500 /* blocks */ #define FIRST_REFRESH_GRANULARITY 1024 @@ -199,6 +200,7 @@ struct options { }; const command_line::arg_descriptor<uint64_t> kdf_rounds = {"kdf-rounds", tools::wallet2::tr("Number of rounds for the key derivation function"), 1}; const command_line::arg_descriptor<std::string> hw_device = {"hw-device", tools::wallet2::tr("HW device to use"), ""}; + const command_line::arg_descriptor<std::string> tx_notify = { "tx-notify" , "Run a program for each new incoming transaction, '%s' will be replaced by the transaction hash" , "" }; }; void do_prepare_file_names(const std::string& file_path, std::string& keys_file, std::string& wallet_file) @@ -293,7 +295,7 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl trusted_daemon = false; if (tools::is_local_address(daemon_address)) { - MINFO(tr("Daemon is local, assuming trusted")); + MINFO(tools::wallet2::tr("Daemon is local, assuming trusted")); trusted_daemon = true; } } @@ -305,6 +307,17 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl boost::filesystem::path ringdb_path = command_line::get_arg(vm, opts.shared_ringdb_dir); wallet->set_ring_database(ringdb_path.string()); wallet->device_name(device_name); + + try + { + if (!command_line::is_arg_defaulted(vm, opts.tx_notify)) + wallet->set_tx_notify(std::shared_ptr<tools::Notify>(new tools::Notify(command_line::get_arg(vm, opts.tx_notify).c_str()))); + } + catch (const std::exception &e) + { + MERROR("Failed to parse tx notify spec"); + } + return wallet; } @@ -334,10 +347,10 @@ boost::optional<tools::password_container> get_password(const boost::program_opt THROW_WALLET_EXCEPTION_IF(!password_prompter, tools::error::wallet_internal_error, tools::wallet2::tr("no password specified; use --prompt-for-password to prompt for a password")); - return password_prompter(verify ? tr("Enter a new password for the wallet") : tr("Wallet password"), verify); + return password_prompter(verify ? tools::wallet2::tr("Enter a new password for the wallet") : tools::wallet2::tr("Wallet password"), verify); } -std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, const boost::program_options::variables_map& vm, bool unattended, const options& opts, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) +std::pair<std::unique_ptr<tools::wallet2>, tools::password_container> generate_from_json(const std::string& json_file, const boost::program_options::variables_map& vm, bool unattended, const options& opts, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) { const bool testnet = command_line::get_arg(vm, opts.testnet); const bool stagenet = command_line::get_arg(vm, opts.stagenet); @@ -347,6 +360,7 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, false. Gcc will coerce this into unique_ptr(nullptr), but clang correctly fails. This large wrapper is for the use of that macro */ std::unique_ptr<tools::wallet2> wallet; + epee::wipeable_string password; const auto do_generate = [&]() -> bool { std::string buf; if (!epee::file_io_utils::load_file_to_string(json_file, buf)) { @@ -484,10 +498,12 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, if (!field_seed.empty()) { wallet->generate(field_filename, field_password, recovery_key, recover, false, create_address_file); + password = field_password; } else if (field_viewkey.empty() && !field_spendkey.empty()) { wallet->generate(field_filename, field_password, spendkey, recover, false, create_address_file); + password = field_password; } else { @@ -514,6 +530,7 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("Address must be specified in order to create watch-only wallet")); } wallet->generate(field_filename, field_password, address, viewkey, create_address_file); + password = field_password; } else { @@ -521,6 +538,7 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, THROW_WALLET_EXCEPTION(tools::error::wallet_internal_error, tools::wallet2::tr("failed to verify spend key secret key")); } wallet->generate(field_filename, field_password, address, spendkey, viewkey, create_address_file); + password = field_password; } } } @@ -533,9 +551,9 @@ std::unique_ptr<tools::wallet2> generate_from_json(const std::string& json_file, if (do_generate()) { - return wallet; + return {std::move(wallet), tools::password_container(password)}; } - return nullptr; + return {nullptr, tools::password_container{}}; } static void throw_on_rpc_response_error(const boost::optional<std::string> &status, const char *method) @@ -876,9 +894,10 @@ void wallet2::init_options(boost::program_options::options_description& desc_par command_line::add_arg(desc_params, opts.shared_ringdb_dir); command_line::add_arg(desc_params, opts.kdf_rounds); command_line::add_arg(desc_params, opts.hw_device); + command_line::add_arg(desc_params, opts.tx_notify); } -std::unique_ptr<wallet2> wallet2::make_from_json(const boost::program_options::variables_map& vm, bool unattended, const std::string& json_file, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) +std::pair<std::unique_ptr<wallet2>, tools::password_container> wallet2::make_from_json(const boost::program_options::variables_map& vm, bool unattended, const std::string& json_file, const std::function<boost::optional<tools::password_container>(const char *, bool)> &password_prompter) { const options opts{}; return generate_from_json(json_file, vm, unattended, opts, password_prompter); @@ -1363,9 +1382,9 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote // (that is, the prunable stuff may or may not be included) if (!miner_tx && !pool) process_unconfirmed(txid, tx, height); - std::vector<size_t> outs; std::unordered_map<cryptonote::subaddress_index, uint64_t> tx_money_got_in_outs; // per receiving subaddress index crypto::public_key tx_pub_key = null_pkey; + bool notify = false; std::vector<tx_extra_field> local_tx_extra_fields; if (tx_cache_data.tx_extra_fields.empty()) @@ -1385,6 +1404,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote uint64_t total_received_1 = 0; while (!tx.vout.empty()) { + std::vector<size_t> outs; // if tx.vout is not empty, we loop through all tx pubkeys tx_extra_pub_key pub_key_field; @@ -1605,6 +1625,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote m_callback->on_money_received(height, txid, tx, td.m_amount, td.m_subaddr_index); } total_received_1 += amount; + notify = true; } else if (m_transfers[kit->second].m_spent || m_transfers[kit->second].amount() >= tx_scan_info[o].amount) { @@ -1672,6 +1693,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote m_callback->on_money_received(height, txid, tx, td.m_amount, td.m_subaddr_index); } total_received_1 += extra_amount; + notify = true; } } } @@ -1745,10 +1767,14 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote } // remove change sent to the spending subaddress account from the list of received funds + uint64_t sub_change = 0; for (auto i = tx_money_got_in_outs.begin(); i != tx_money_got_in_outs.end();) { if (subaddr_account && i->first.major == *subaddr_account) + { + sub_change += i->second; i = tx_money_got_in_outs.erase(i); + } else ++i; } @@ -1793,7 +1819,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote } } - uint64_t total_received_2 = 0; + uint64_t total_received_2 = sub_change; for (const auto& i : tx_money_got_in_outs) total_received_2 += i.second; if (total_received_1 != total_received_2) @@ -1828,6 +1854,13 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote LOG_PRINT_L2("Payment found in " << (pool ? "pool" : "block") << ": " << payment_id << " / " << payment.m_tx_hash << " / " << payment.m_amount); } } + + if (notify) + { + std::shared_ptr<tools::Notify> tx_notify = m_tx_notify; + if (tx_notify) + tx_notify->notify(epee::string_tools::pod_to_hex(txid).c_str()); + } } //---------------------------------------------------------------------------------------------------- void wallet2::process_unconfirmed(const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t height) @@ -3352,9 +3385,16 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_ encrypted_secret_keys = field_encrypted_secret_keys; GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, device_name, std::string, String, false, std::string()); - if (m_device_name.empty() && field_device_name_found) + if (m_device_name.empty()) { - m_device_name = field_device_name; + if (field_device_name_found) + { + m_device_name = field_device_name; + } + else + { + m_device_name = m_key_device_type == hw::device::device_type::LEDGER ? "Ledger" : "default"; + } } } else @@ -6929,11 +6969,23 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> } // while we still need more mixins + uint64_t num_usable_outs = num_outs; + bool allow_blackballed = false; while (num_found < requested_outputs_count) { // if we've gone through every possible output, we've gotten all we can - if (seen_indices.size() == num_outs) - break; + if (seen_indices.size() == num_usable_outs) + { + // there is a first pass which rejects blackballed outputs, then a second pass + // which allows them if we don't have enough non blackballed outputs to reach + // the required amount of outputs (since consensus does not care about blackballed + // outputs, we still need to reach the minimum ring size) + if (allow_blackballed) + break; + MINFO("Not enough non blackballed outputs, we'll allow blackballed ones"); + allow_blackballed = true; + num_usable_outs = num_outs; + } // get a random output index from the DB. If we've already seen it, // return to the top of the loop and try again, otherwise add it to the @@ -7007,14 +7059,26 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>> if (seen_indices.count(i)) continue; - if (is_output_blackballed(std::make_pair(amount, i))) // don't add blackballed outputs + if (!allow_blackballed && is_output_blackballed(std::make_pair(amount, i))) // don't add blackballed outputs + { + --num_usable_outs; continue; + } seen_indices.emplace(i); LOG_PRINT_L2("picking " << i << " as " << type); req.outputs.push_back({amount, i}); ++num_found; } + + // if we had enough unusable outputs, we might fall off here and still + // have too few outputs, so we stuff with one to keep counts good, and + // we'll error out later + while (num_found < requested_outputs_count) + { + req.outputs.push_back({amount, 0}); + ++num_found; + } } // sort the subsection, to ensure the daemon doesn't know which output is ours |