diff options
Diffstat (limited to 'src/simplewallet/simplewallet.cpp')
-rw-r--r-- | src/simplewallet/simplewallet.cpp | 892 |
1 files changed, 731 insertions, 161 deletions
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 04170df62..7d28de9c0 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -58,6 +58,7 @@ #include "crypto/crypto.h" // for crypto::secret_key definition #include "mnemonics/electrum-words.h" #include "rapidjson/document.h" +#include "common/json_util.h" #include <stdexcept> #if defined(WIN32) @@ -76,6 +77,23 @@ typedef cryptonote::simple_wallet sw; #define DEFAULT_MIX 4 +#define LOCK_REFRESH_THREAD_SCOPE() \ + bool auto_refresh_run = m_auto_refresh_run.load(std::memory_order_relaxed); \ + m_auto_refresh_run.store(false, std::memory_order_relaxed); \ + /* stop any background refresh, and take over */ \ + m_wallet->stop(); \ + m_auto_refresh_mutex.lock(); \ + m_auto_refresh_cond.notify_one(); \ + m_auto_refresh_mutex.unlock(); \ + if (auto_refresh_run) \ + m_auto_refresh_thread.join(); \ + boost::unique_lock<boost::mutex> lock(m_auto_refresh_mutex); \ + epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ \ + m_auto_refresh_run.store(auto_refresh_run, std::memory_order_relaxed); \ + if (auto_refresh_run) \ + m_auto_refresh_thread = boost::thread([&]{wallet_refresh_thread();}); \ + }) + namespace { const command_line::arg_descriptor<std::string> arg_wallet_file = {"wallet-file", sw::tr("Use wallet <arg>"), ""}; @@ -92,10 +110,12 @@ namespace const command_line::arg_descriptor<bool> arg_non_deterministic = {"non-deterministic", sw::tr("Create non-deterministic view and spend keys"), false}; const command_line::arg_descriptor<int> arg_daemon_port = {"daemon-port", sw::tr("Use daemon instance at port <arg> instead of 18081"), 0}; const command_line::arg_descriptor<uint32_t> arg_log_level = {"log-level", "", LOG_LEVEL_0}; + const command_line::arg_descriptor<uint32_t> arg_max_concurrency = {"max-concurrency", "Max number of threads to use for a parallel job", 0}; const command_line::arg_descriptor<std::string> arg_log_file = {"log-file", sw::tr("Specify log file"), ""}; const command_line::arg_descriptor<bool> arg_testnet = {"testnet", sw::tr("For testnet. Daemon must also be launched with --testnet flag"), false}; const command_line::arg_descriptor<bool> arg_restricted = {"restricted-rpc", sw::tr("Restricts RPC to view-only commands"), false}; const command_line::arg_descriptor<bool> arg_trusted_daemon = {"trusted-daemon", sw::tr("Enable commands which rely on a trusted daemon"), false}; + const command_line::arg_descriptor<uint64_t> arg_restore_height = {"restore-height", sw::tr("Restore from specific blockchain height"), 0}; const command_line::arg_descriptor< std::vector<std::string> > arg_command = {"command", ""}; @@ -212,6 +232,44 @@ namespace return true; return false; } + + const struct + { + const char *name; + tools::wallet2::RefreshType refresh_type; + } refresh_type_names[] = + { + { "full", tools::wallet2::RefreshFull }, + { "optimize-coinbase", tools::wallet2::RefreshOptimizeCoinbase }, + { "optimized-coinbase", tools::wallet2::RefreshOptimizeCoinbase }, + { "no-coinbase", tools::wallet2::RefreshNoCoinbase }, + { "default", tools::wallet2::RefreshDefault }, + }; + + bool parse_refresh_type(const std::string &s, tools::wallet2::RefreshType &refresh_type) + { + for (size_t n = 0; n < sizeof(refresh_type_names) / sizeof(refresh_type_names[0]); ++n) + { + if (s == refresh_type_names[n].name) + { + refresh_type = refresh_type_names[n].refresh_type; + return true; + } + } + fail_msg_writer() << tr("failed to parse refresh type"); + return false; + } + + std::string get_refresh_type_name(tools::wallet2::RefreshType type) + { + for (size_t n = 0; n < sizeof(refresh_type_names) / sizeof(refresh_type_names[0]); ++n) + { + if (type == refresh_type_names[n].refresh_type) + return refresh_type_names[n].name; + } + return "invalid"; + } + } @@ -257,6 +315,8 @@ bool simple_wallet::seed(const std::vector<std::string> &args/* = std::vector<st if (m_wallet->get_seed_language().empty()) { std::string mnemonic_language = get_mnemonic_language(); + if (mnemonic_language.empty()) + return true; m_wallet->set_seed_language(mnemonic_language); } @@ -304,6 +364,8 @@ bool simple_wallet::seed_set_language(const std::vector<std::string> &args/* = s } std::string mnemonic_language = get_mnemonic_language(); + if (mnemonic_language.empty()) + return true; m_wallet->set_seed_language(mnemonic_language); m_wallet->rewrite(m_wallet_file, pwd_container.password()); return true; @@ -460,32 +522,6 @@ bool simple_wallet::set_auto_refresh(const std::vector<std::string> &args/* = st return true; } -static bool parse_refresh_type(const std::string &s, tools::wallet2::RefreshType &refresh_type) -{ - static const struct - { - const char *name; - tools::wallet2::RefreshType refresh_type; - } names[] = - { - { "full", tools::wallet2::RefreshFull }, - { "optimize-coinbase", tools::wallet2::RefreshOptimizeCoinbase }, - { "optimized-coinbase", tools::wallet2::RefreshOptimizeCoinbase }, - { "no-coinbase", tools::wallet2::RefreshNoCoinbase }, - { "default", tools::wallet2::RefreshDefault }, - }; - for (size_t n = 0; n < sizeof(names) / sizeof(names[0]); ++n) - { - if (s == names[n].name) - { - refresh_type = names[n].refresh_type; - return true; - } - } - fail_msg_writer() << tr("failed to parse refresh type"); - return false; -} - bool simple_wallet::set_refresh_type(const std::vector<std::string> &args/* = std::vector<std::string>()*/) { bool success = false; @@ -542,6 +578,7 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer, this, _1), tr("transfer [<mixin_count>] <addr_1> <amount_1> [<addr_2> <amount_2> ... <addr_N> <amount_N>] [payment_id] - Transfer <amount_1>,... <amount_N> to <address_1>,... <address_N>, respectively. <mixin_count> is the number of extra inputs to include for untraceability (from 0 to maximum available)")); m_cmd_binder.set_handler("transfer_new", boost::bind(&simple_wallet::transfer_new, this, _1), tr("Same as transfer, but using a new transaction building algorithm")); m_cmd_binder.set_handler("sweep_unmixable", boost::bind(&simple_wallet::sweep_unmixable, this, _1), tr("Send all unmixable outputs to yourself with mixin 0")); + m_cmd_binder.set_handler("sweep_all", boost::bind(&simple_wallet::sweep_all, this, _1), tr("Send all unlocked balance an address")); m_cmd_binder.set_handler("set_log", boost::bind(&simple_wallet::set_log, this, _1), tr("set_log <level> - Change current log detail level, <0-4>")); m_cmd_binder.set_handler("address", boost::bind(&simple_wallet::print_address, this, _1), tr("Show current wallet public address")); m_cmd_binder.set_handler("integrated_address", boost::bind(&simple_wallet::print_integrated_address, this, _1), tr("integrated_address [PID] - Encode a payment ID into an integrated address for the current wallet public address (no argument uses a random payment ID), or decode an integrated address to standard address and payment ID")); @@ -556,6 +593,8 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("check_tx_key", boost::bind(&simple_wallet::check_tx_key, this, _1), tr("Check amount going to <address> in <txid>")); m_cmd_binder.set_handler("show_transfers", boost::bind(&simple_wallet::show_transfers, this, _1), tr("show_transfers [in|out] [<min_height> [<max_height>]] - Show incoming/outgoing transfers within an optional height range")); m_cmd_binder.set_handler("rescan_bc", boost::bind(&simple_wallet::rescan_blockchain, this, _1), tr("Rescan blockchain from scratch")); + m_cmd_binder.set_handler("set_tx_note", boost::bind(&simple_wallet::set_tx_note, this, _1), tr("Set an arbitrary string note for a txid")); + m_cmd_binder.set_handler("get_tx_note", boost::bind(&simple_wallet::get_tx_note, this, _1), tr("Get a string note for a txid")); m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::help, this, _1), tr("Show this help")); } //---------------------------------------------------------------------------------------------------- @@ -563,7 +602,12 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) { if (args.empty()) { - fail_msg_writer() << tr("set: needs an argument. available options: seed, always-confirm-transfers, default-mixin, auto-refresh, refresh-type"); + success_msg_writer() << "seed = " << m_wallet->get_seed_language(); + success_msg_writer() << "always-confirm-transfers = " << m_wallet->always_confirm_transfers(); + success_msg_writer() << "store-tx-info = " << m_wallet->store_tx_info(); + success_msg_writer() << "default-mixin = " << m_wallet->default_mixin(); + success_msg_writer() << "auto-refresh = " << m_wallet->auto_refresh(); + success_msg_writer() << "refresh-type = " << get_refresh_type_name(m_wallet->get_refresh_type()); return true; } else @@ -698,6 +742,10 @@ bool simple_wallet::ask_wallet_create_if_needed() tr("Specify wallet file name (e.g., MyWallet). If the wallet doesn't exist, it will be created.\n" "Wallet file name: ") ); + if (std::cin.eof()) + { + return false; + } valid_path = tools::wallet2::wallet_valid_path_format(wallet_path); if (!valid_path) { @@ -820,6 +868,8 @@ static bool get_password(const boost::program_options::variables_map& vm, bool a //---------------------------------------------------------------------------------------------------- bool simple_wallet::generate_from_json(const boost::program_options::variables_map& vm, std::string &wallet_file, std::string &password) { + bool testnet = command_line::get_arg(vm, arg_testnet); + std::string buf; bool r = epee::file_io_utils::load_file_to_string(m_generate_from_json, buf); if (!r) { @@ -833,84 +883,123 @@ bool simple_wallet::generate_from_json(const boost::program_options::variables_m return false; } - if (!json.HasMember("version")) { - fail_msg_writer() << tr("Version not found in JSON"); - return false; - } - unsigned int version = json["version"].GetUint(); + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, version, unsigned, Uint, true); const int current_version = 1; - if (version > current_version) { - fail_msg_writer() << boost::format(tr("Version %u too new, we can only grok up to %u")) % version % current_version; - return false; - } - if (!json.HasMember("filename")) { - fail_msg_writer() << tr("Filename not found in JSON"); + if (field_version > current_version) { + fail_msg_writer() << boost::format(tr("Version %u too new, we can only grok up to %u")) % field_version % current_version; return false; } - std::string filename = json["filename"].GetString(); - bool recover = false; - uint64_t scan_from_height = 0; - if (json.HasMember("scan_from_height")) { - scan_from_height = json["scan_from_height"].GetUint64(); - recover = true; - } + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, filename, std::string, String, true); - password = ""; - if (json.HasMember("password")) { - password = json["password"].GetString(); - } + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, scan_from_height, uint64_t, Uint64, false); + bool recover = field_scan_from_height_found; - std::string viewkey_string(""); + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, password, std::string, String, false); + password = field_password; + + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, viewkey, std::string, String, false); crypto::secret_key viewkey; - if (json.HasMember("viewkey")) { - viewkey_string = json["viewkey"].GetString(); + if (field_viewkey_found) + { cryptonote::blobdata viewkey_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(viewkey_string, viewkey_data)) + if(!epee::string_tools::parse_hexstr_to_binbuff(field_viewkey, viewkey_data)) { fail_msg_writer() << tr("failed to parse view key secret key"); return false; } viewkey = *reinterpret_cast<const crypto::secret_key*>(viewkey_data.data()); + crypto::public_key pkey; + if (!crypto::secret_key_to_public_key(viewkey, pkey)) { + fail_msg_writer() << tr("failed to verify view key secret key"); + return false; + } } - std::string spendkey_string(""); + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, spendkey, std::string, String, false); crypto::secret_key spendkey; - if (json.HasMember("spendkey")) { - spendkey_string = json["spendkey"].GetString(); + if (field_spendkey_found) + { cryptonote::blobdata spendkey_data; - if(!epee::string_tools::parse_hexstr_to_binbuff(spendkey_string, spendkey_data)) + if(!epee::string_tools::parse_hexstr_to_binbuff(field_spendkey, spendkey_data)) { fail_msg_writer() << tr("failed to parse spend key secret key"); return false; } spendkey = *reinterpret_cast<const crypto::secret_key*>(spendkey_data.data()); + crypto::public_key pkey; + if (!crypto::secret_key_to_public_key(spendkey, pkey)) { + fail_msg_writer() << tr("failed to verify spend key secret key"); + return false; + } } - std::string seed(""); + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, seed, std::string, String, false); std::string old_language; - if (json.HasMember("seed")) { - seed = json["seed"].GetString(); - if (!crypto::ElectrumWords::words_to_bytes(seed, m_recovery_key, old_language)) + if (field_seed_found) + { + if (!crypto::ElectrumWords::words_to_bytes(field_seed, m_recovery_key, old_language)) { fail_msg_writer() << tr("Electrum-style word list failed verification"); return false; } - m_electrum_seed = seed; + m_electrum_seed = field_seed; m_restore_deterministic_wallet = true; } + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, address, std::string, String, false); + // compatibility checks - if (seed.empty() && viewkey_string.empty()) { + if (!field_seed_found && !field_viewkey_found) + { fail_msg_writer() << tr("At least one of Electrum-style word list and private view key must be specified"); return false; } - if (!seed.empty() && (!viewkey_string.empty() || !spendkey_string.empty())) { + if (field_seed_found && (field_viewkey_found || field_spendkey_found)) + { fail_msg_writer() << tr("Both Electrum-style word list and private key(s) specified"); return false; } - m_wallet_file = filename; + // if an address was given, we check keys against it, and deduce the spend + // public key if it was not given + if (field_address_found) + { + cryptonote::account_public_address address; + bool has_payment_id; + crypto::hash8 new_payment_id; + if(!get_account_integrated_address_from_str(address, has_payment_id, new_payment_id, testnet, field_address)) + { + fail_msg_writer() << tr("invalid address"); + return false; + } + if (field_viewkey_found) + { + crypto::public_key pkey; + if (!crypto::secret_key_to_public_key(viewkey, pkey)) { + fail_msg_writer() << tr("failed to verify view key secret key"); + return false; + } + if (address.m_view_public_key != pkey) { + fail_msg_writer() << tr("view key does not match standard address"); + return false; + } + } + if (field_spendkey_found) + { + crypto::public_key pkey; + if (!crypto::secret_key_to_public_key(spendkey, pkey)) { + fail_msg_writer() << tr("failed to verify spend key secret key"); + return false; + } + if (address.m_spend_public_key != pkey) { + fail_msg_writer() << tr("spend key does not match standard address"); + return false; + } + } + } + + m_wallet_file = field_filename; bool was_deprecated_wallet = m_restore_deterministic_wallet && ((old_language == crypto::ElectrumWords::old_language_name) || crypto::ElectrumWords::get_is_old_style_seed(m_electrum_seed)); @@ -919,13 +1008,13 @@ bool simple_wallet::generate_from_json(const boost::program_options::variables_m return false; } - bool testnet = command_line::get_arg(vm, arg_testnet); m_wallet.reset(new tools::wallet2(testnet)); m_wallet->callback(this); + m_wallet->set_refresh_from_block_height(field_scan_from_height); try { - if (!seed.empty()) + if (!field_seed.empty()) { m_wallet->generate(m_wallet_file, password, m_recovery_key, recover, false); } @@ -936,17 +1025,27 @@ bool simple_wallet::generate_from_json(const boost::program_options::variables_m fail_msg_writer() << tr("failed to verify view key secret key"); return false; } - if (!crypto::secret_key_to_public_key(spendkey, address.m_spend_public_key)) { - fail_msg_writer() << tr("failed to verify spend key secret key"); - return false; - } - if (spendkey_string.empty()) + if (field_spendkey.empty()) { + // if we have an addres but no spend key, we can deduce the spend public key + // from the address + if (field_address_found) + { + cryptonote::account_public_address address2; + bool has_payment_id; + crypto::hash8 new_payment_id; + get_account_integrated_address_from_str(address2, has_payment_id, new_payment_id, testnet, field_address); + address.m_spend_public_key = address2.m_spend_public_key; + } m_wallet->generate(m_wallet_file, password, address, viewkey); } else { + if (!crypto::secret_key_to_public_key(spendkey, address.m_spend_public_key)) { + fail_msg_writer() << tr("failed to verify spend key secret key"); + return false; + } m_wallet->generate(m_wallet_file, password, address, spendkey, viewkey); } } @@ -957,13 +1056,42 @@ bool simple_wallet::generate_from_json(const boost::program_options::variables_m return false; } - m_wallet->set_refresh_from_block_height(scan_from_height); - wallet_file = m_wallet_file; return r; } +static bool is_local_daemon(const std::string &address) +{ + // extract host + epee::net_utils::http::url_content u_c; + if (!epee::net_utils::parse_url(address, u_c)) + { + LOG_PRINT_L1("Failed to determine whether daemon is local, assuming not"); + return false; + } + if (u_c.host.empty()) + { + LOG_PRINT_L1("Failed to determine whether daemon is local, assuming not"); + return false; + } + + // resolve to IP + boost::asio::io_service io_service; + boost::asio::ip::tcp::resolver resolver(io_service); + boost::asio::ip::tcp::resolver::query query(u_c.host, ""); + boost::asio::ip::tcp::resolver::iterator i = resolver.resolve(query); + while (i != boost::asio::ip::tcp::resolver::iterator()) + { + const boost::asio::ip::tcp::endpoint &ep = *i; + if (ep.address().is_loopback()) + return true; + ++i; + } + + return false; +} + //---------------------------------------------------------------------------------------------------- bool simple_wallet::init(const boost::program_options::variables_map& vm) { @@ -999,6 +1127,17 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) if (m_daemon_address.empty()) m_daemon_address = std::string("http://") + m_daemon_host + ":" + std::to_string(m_daemon_port); + // set --trusted-daemon if local + try + { + if (is_local_daemon(m_daemon_address)) + { + LOG_PRINT_L1(tr("Daemon is local, assuming trusted")); + m_trusted_daemon = true; + } + } + catch (const std::exception &e) { } + tools::password_container pwd_container; if (!get_password(vm, true, pwd_container)) return false; @@ -1020,6 +1159,8 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) if (m_electrum_seed.empty()) { m_electrum_seed = command_line::input_line("Specify Electrum seed: "); + if (std::cin.eof()) + return false; if (m_electrum_seed.empty()) { fail_msg_writer() << tr("specify a recovery parameter with the --electrum-seed=\"words list here\""); @@ -1033,10 +1174,28 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) return false; } } + if (!m_restore_height && m_generate_new.empty()) + { + std::string heightstr = command_line::input_line("Restore from specific blockchain height (optional, default 0): "); + if (std::cin.eof()) + return false; + if (heightstr.size()) + { + try { + m_restore_height = boost::lexical_cast<uint64_t>(heightstr); + } + catch (boost::bad_lexical_cast &) { + fail_msg_writer() << tr("bad m_restore_height parameter:") << " " << heightstr; + return false; + } + } + } if (!m_generate_from_view_key.empty()) { // parse address std::string address_string = command_line::input_line("Standard address: "); + if (std::cin.eof()) + return false; if (address_string.empty()) { fail_msg_writer() << tr("No data supplied, cancelled"); return false; @@ -1052,6 +1211,8 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) // parse view secret key std::string viewkey_string = command_line::input_line("View key: "); + if (std::cin.eof()) + return false; if (viewkey_string.empty()) { fail_msg_writer() << tr("No data supplied, cancelled"); return false; @@ -1084,6 +1245,8 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) { // parse address std::string address_string = command_line::input_line("Standard address: "); + if (std::cin.eof()) + return false; if (address_string.empty()) { fail_msg_writer() << tr("No data supplied, cancelled"); return false; @@ -1099,6 +1262,8 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) // parse spend secret key std::string spendkey_string = command_line::input_line("Spend key: "); + if (std::cin.eof()) + return false; if (spendkey_string.empty()) { fail_msg_writer() << tr("No data supplied, cancelled"); return false; @@ -1113,6 +1278,8 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) // parse view secret key std::string viewkey_string = command_line::input_line("View key: "); + if (std::cin.eof()) + return false; if (viewkey_string.empty()) { fail_msg_writer() << tr("No data supplied, cancelled"); return false; @@ -1193,6 +1360,7 @@ bool simple_wallet::handle_command_line(const boost::program_options::variables_ m_restore_deterministic_wallet = command_line::get_arg(vm, arg_restore_deterministic_wallet); m_non_deterministic = command_line::get_arg(vm, arg_non_deterministic); m_trusted_daemon = command_line::get_arg(vm, arg_trusted_daemon); + m_restore_height = command_line::get_arg(vm, arg_restore_height); return true; } @@ -1232,6 +1400,8 @@ std::string simple_wallet::get_mnemonic_language() while (language_number < 0) { language_choice = command_line::input_line(tr("Enter the number corresponding to the language of your choice: ")); + if (std::cin.eof()) + return std::string(); try { language_number = std::stoi(language_choice); @@ -1270,6 +1440,8 @@ bool simple_wallet::new_wallet(const std::string &wallet_file, const std::string "a deprecated version of the wallet. Please use the new seed that we provide.\n"); } mnemonic_language = get_mnemonic_language(); + if (mnemonic_language.empty()) + return false; } m_wallet_file = wallet_file; @@ -1278,6 +1450,16 @@ bool simple_wallet::new_wallet(const std::string &wallet_file, const std::string m_wallet->callback(this); m_wallet->set_seed_language(mnemonic_language); + // for a totally new account, we don't care about older blocks. + if (!m_generate_new.empty()) + { + std::string err; + m_wallet->set_refresh_from_block_height(get_daemon_blockchain_height(err)); + } else if (m_restore_height) + { + m_wallet->set_refresh_from_block_height(m_restore_height); + } + crypto::secret_key recovery_val; try { @@ -1325,6 +1507,8 @@ bool simple_wallet::new_wallet(const std::string &wallet_file, const std::string m_wallet.reset(new tools::wallet2(testnet)); m_wallet->callback(this); + if (m_restore_height) + m_wallet->set_refresh_from_block_height(m_restore_height); try { @@ -1351,6 +1535,8 @@ bool simple_wallet::new_wallet(const std::string &wallet_file, const std::string m_wallet.reset(new tools::wallet2(testnet)); m_wallet->callback(this); + if (m_restore_height) + m_wallet->set_refresh_from_block_height(m_restore_height); try { @@ -1397,6 +1583,8 @@ bool simple_wallet::open_wallet(const string &wallet_file, const std::string& pa message_writer(epee::log_space::console_color_green, false) << "\n" << tr("You had been using " "a deprecated version of the wallet. Please proceed to upgrade your wallet.\n"); std::string mnemonic_language = get_mnemonic_language(); + if (mnemonic_language.empty()) + return false; m_wallet->set_seed_language(mnemonic_language); m_wallet->rewrite(m_wallet_file, password); @@ -1465,6 +1653,7 @@ bool simple_wallet::save(const std::vector<std::string> &args) { try { + LOCK_REFRESH_THREAD_SCOPE(); m_wallet->store(); success_msg_writer() << tr("Wallet data saved"); } @@ -1520,7 +1709,7 @@ bool simple_wallet::start_mining(const std::vector<std::string>& args) req.miner_address = m_wallet->get_account().get_public_address_str(m_wallet->testnet()); bool ok = true; - size_t max_mining_threads_count = (std::max)(boost::thread::hardware_concurrency(), static_cast<unsigned>(2)); + size_t max_mining_threads_count = (std::max)(tools::get_max_concurrency(), static_cast<unsigned>(2)); if (0 == args.size()) { req.threads_count = 1; @@ -1633,12 +1822,7 @@ bool simple_wallet::refresh_main(uint64_t start_height, bool reset) if (!try_connect_to_daemon()) return true; - bool auto_refresh_run = m_auto_refresh_run.load(std::memory_order_relaxed); - m_auto_refresh_run.store(false, std::memory_order_relaxed); - // stop any background refresh, and take over - m_wallet->stop(); - boost::unique_lock<boost::mutex> lock(m_auto_refresh_mutex); - m_auto_refresh_cond.notify_one(); + LOCK_REFRESH_THREAD_SCOPE(); if (reset) m_wallet->rescan_blockchain(false); @@ -1651,13 +1835,13 @@ bool simple_wallet::refresh_main(uint64_t start_height, bool reset) try { m_in_manual_refresh.store(true, std::memory_order_relaxed); + epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){m_in_manual_refresh.store(false, std::memory_order_relaxed);}); m_wallet->refresh(start_height, fetched_blocks); - m_in_manual_refresh.store(false, std::memory_order_relaxed); ok = true; // Clear line "Height xxx of xxx" std::cout << "\r \r"; success_msg_writer(true) << tr("Refresh done, blocks received: ") << fetched_blocks; - show_balance(); + show_balance_unlocked(); } catch (const tools::error::daemon_busy&) { @@ -1698,8 +1882,6 @@ bool simple_wallet::refresh_main(uint64_t start_height, bool reset) fail_msg_writer() << tr("refresh failed: ") << ss.str() << ". " << tr("Blocks received: ") << fetched_blocks; } - m_in_manual_refresh.store(false, std::memory_order_relaxed); - m_auto_refresh_run.store(auto_refresh_run, std::memory_order_relaxed); return true; } //---------------------------------------------------------------------------------------------------- @@ -1719,15 +1901,24 @@ bool simple_wallet::refresh(const std::vector<std::string>& args) return refresh_main(start_height, false); } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::show_balance(const std::vector<std::string>& args/* = std::vector<std::string>()*/) +bool simple_wallet::show_balance_unlocked() { success_msg_writer() << tr("Balance: ") << print_money(m_wallet->balance()) << ", " << tr("unlocked balance: ") << print_money(m_wallet->unlocked_balance()); return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::show_balance(const std::vector<std::string>& args/* = std::vector<std::string>()*/) +{ + LOCK_REFRESH_THREAD_SCOPE(); + show_balance_unlocked(); + return true; +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args) { + LOCK_REFRESH_THREAD_SCOPE(); + bool filter = false; bool available = false; if (!args.empty()) @@ -1793,6 +1984,8 @@ bool simple_wallet::show_payments(const std::vector<std::string> &args) return true; } + LOCK_REFRESH_THREAD_SCOPE(); + message_writer() << boost::format("%68s%68s%12s%21s%16s") % tr("payment") % tr("transaction") % tr("height") % tr("amount") % tr("unlock time"); @@ -1870,6 +2063,7 @@ bool simple_wallet::rescan_spent(const std::vector<std::string> &args) try { + LOCK_REFRESH_THREAD_SCOPE(); m_wallet->rescan_spent(); } catch (const tools::error::daemon_busy&) @@ -1903,11 +2097,83 @@ bool simple_wallet::rescan_spent(const std::vector<std::string> &args) return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::get_address_from_str(const std::string &str, cryptonote::account_public_address &address, bool &has_payment_id, crypto::hash8 &payment_id) +{ + if(!get_account_integrated_address_from_str(address, has_payment_id, payment_id, m_wallet->testnet(), str)) + { + // if treating as an address fails, try as url + bool dnssec_ok = false; + std::string url = str; + + // attempt to get address from dns query + auto addresses_from_dns = tools::wallet2::addresses_from_url(url, dnssec_ok); + + // for now, move on only if one address found + if (addresses_from_dns.size() == 1) + { + if (get_account_integrated_address_from_str(address, has_payment_id, payment_id, m_wallet->testnet(), addresses_from_dns[0])) + { + // if it was an address, prompt user for confirmation. + // inform user of DNSSEC validation status as well. + + std::string dnssec_str; + if (dnssec_ok) + { + dnssec_str = tr("DNSSEC validation passed"); + } + else + { + dnssec_str = tr("WARNING: DNSSEC validation was unsuccessful, this address may not be correct!"); + } + std::stringstream prompt; + prompt << tr("For URL: ") << url + << ", " << dnssec_str << std::endl + << tr(" Monero Address = ") << addresses_from_dns[0] + << std::endl + << tr("Is this OK? (Y/n) ") + ; + + // prompt the user for confirmation given the dns query and dnssec status + std::string confirm_dns_ok = command_line::input_line(prompt.str()); + if (std::cin.eof()) + { + return false; + } + if (confirm_dns_ok != "Y" && confirm_dns_ok != "y" && confirm_dns_ok != "Yes" && confirm_dns_ok != "yes" + && confirm_dns_ok != tr("yes") && confirm_dns_ok != tr("no")) + { + fail_msg_writer() << tr("you have cancelled the transfer request"); + return false; + } + } + else + { + fail_msg_writer() << tr("failed to get a Monero address from: ") << url; + return false; + } + } + else if (addresses_from_dns.size() > 1) + { + fail_msg_writer() << tr("not yet supported: Multiple Monero addresses found for given URL: ") << url; + return false; + } + else + { + fail_msg_writer() << tr("wrong address: ") << url; + return false; + } + } + + return true; +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::transfer_main(bool new_algorithm, const std::vector<std::string> &args_) { if (!try_connect_to_daemon()) return true; + LOCK_REFRESH_THREAD_SCOPE(); + std::vector<std::string> local_args = args_; size_t fake_outs_count; @@ -1977,65 +2243,8 @@ bool simple_wallet::transfer_main(bool new_algorithm, const std::vector<std::str cryptonote::tx_destination_entry de; bool has_payment_id; crypto::hash8 new_payment_id; - if(!get_account_integrated_address_from_str(de.addr, has_payment_id, new_payment_id, m_wallet->testnet(), local_args[i])) - { - // if treating as an address fails, try as url - bool dnssec_ok = false; - std::string url = local_args[i]; - - // attempt to get address from dns query - auto addresses_from_dns = tools::wallet2::addresses_from_url(url, dnssec_ok); - - // for now, move on only if one address found - if (addresses_from_dns.size() == 1) - { - if (get_account_integrated_address_from_str(de.addr, has_payment_id, new_payment_id, m_wallet->testnet(), addresses_from_dns[0])) - { - // if it was an address, prompt user for confirmation. - // inform user of DNSSEC validation status as well. - - std::string dnssec_str; - if (dnssec_ok) - { - dnssec_str = tr("DNSSEC validation passed"); - } - else - { - dnssec_str = tr("WARNING: DNSSEC validation was unsuccessful, this address may not be correct!"); - } - std::stringstream prompt; - prompt << tr("For URL: ") << url - << ", " << dnssec_str << std::endl - << tr(" Monero Address = ") << addresses_from_dns[0] - << std::endl - << tr("Is this OK? (Y/n) ") - ; - - // prompt the user for confirmation given the dns query and dnssec status - std::string confirm_dns_ok = command_line::input_line(prompt.str()); - if (confirm_dns_ok != "Y" && confirm_dns_ok != "y" && confirm_dns_ok != "Yes" && confirm_dns_ok != "yes" - && confirm_dns_ok != tr("yes") && confirm_dns_ok != tr("no")) - { - fail_msg_writer() << tr("you have cancelled the transfer request"); - return true; - } - } - else - { - fail_msg_writer() << tr("failed to get a Monero address from: ") << local_args[i]; - return true; - } - } - else if (addresses_from_dns.size() > 1) - { - fail_msg_writer() << tr("not yet supported: Multiple Monero addresses found for given URL: ") << url; - } - else - { - fail_msg_writer() << tr("wrong address: ") << local_args[i]; - return true; - } - } + if (!get_address_from_str(local_args[i], de.addr, has_payment_id, new_payment_id)) + return true; if (has_payment_id) { @@ -2071,32 +2280,46 @@ bool simple_wallet::transfer_main(bool new_algorithm, const std::vector<std::str // figure out what tx will be necessary std::vector<tools::wallet2::pending_tx> ptx_vector; if (new_algorithm) - ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, 0 /* unused fee arg*/, extra); + ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, 0 /* unused fee arg*/, extra, m_trusted_daemon); else - ptx_vector = m_wallet->create_transactions(dsts, fake_outs_count, 0 /* unlock_time */, 0 /* unused fee arg*/, extra); + ptx_vector = m_wallet->create_transactions(dsts, fake_outs_count, 0 /* unlock_time */, 0 /* unused fee arg*/, extra, m_trusted_daemon); // if more than one tx necessary, prompt user to confirm if (m_wallet->always_confirm_transfers() || ptx_vector.size() > 1) { uint64_t total_fee = 0; + uint64_t dust_not_in_fee = 0; + uint64_t dust_in_fee = 0; for (size_t n = 0; n < ptx_vector.size(); ++n) { total_fee += ptx_vector[n].fee; + + if (ptx_vector[n].dust_added_to_fee) + dust_in_fee += ptx_vector[n].dust; + else + dust_not_in_fee += ptx_vector[n].dust; } - std::string prompt_str; + std::stringstream prompt; if (ptx_vector.size() > 1) { - prompt_str = (boost::format(tr("Your transaction needs to be split into %llu transactions. " - "This will result in a transaction fee being applied to each transaction, for a total fee of %s. Is this okay? (Y/Yes/N/No)")) % - ((unsigned long long)ptx_vector.size()) % print_money(total_fee)).str(); + prompt << boost::format(tr("Your transaction needs to be split into %llu transactions. " + "This will result in a transaction fee being applied to each transaction, for a total fee of %s")) % + ((unsigned long long)ptx_vector.size()) % print_money(total_fee); } else { - prompt_str = (boost::format(tr("The transaction fee is %s. Is this okay? (Y/Yes/N/No)")) % - print_money(total_fee)).str(); + prompt << boost::format(tr("The transaction fee is %s")) % + print_money(total_fee); } - std::string accepted = command_line::input_line(prompt_str); + if (dust_in_fee != 0) prompt << boost::format(tr(", of which %s is dust from change")) % print_money(dust_in_fee); + if (dust_not_in_fee != 0) prompt << tr(".") << ENDL << boost::format(tr("A total of %s from dust change will be sent to dust address")) + % print_money(dust_not_in_fee); + prompt << tr(".") << ENDL << tr("Is this okay? (Y/Yes/N/No)"); + + std::string accepted = command_line::input_line(prompt.str()); + if (std::cin.eof()) + return true; if (accepted != "Y" && accepted != "y" && accepted != "Yes" && accepted != "yes") { fail_msg_writer() << tr("transaction cancelled."); @@ -2159,6 +2382,9 @@ bool simple_wallet::transfer_main(bool new_algorithm, const std::vector<std::str catch (const tools::error::tx_rejected& e) { fail_msg_writer() << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); + std::string reason = e.reason(); + if (!reason.empty()) + fail_msg_writer() << tr("Reason: ") << reason; } catch (const tools::error::tx_sum_overflow& e) { @@ -2218,6 +2444,7 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_) return true; } + LOCK_REFRESH_THREAD_SCOPE(); try { // figure out what tx will be necessary @@ -2254,6 +2481,8 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_) print_money(total_fee)).str(); } std::string accepted = command_line::input_line(prompt_str); + if (std::cin.eof()) + return true; if (accepted != "Y" && accepted != "y" && accepted != "Yes" && accepted != "yes") { fail_msg_writer() << tr("transaction cancelled."); @@ -2316,6 +2545,241 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_) catch (const tools::error::tx_rejected& e) { fail_msg_writer() << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); + std::string reason = e.reason(); + if (!reason.empty()) + fail_msg_writer() << tr("Reason: ") << reason; + } + catch (const tools::error::tx_sum_overflow& e) + { + fail_msg_writer() << e.what(); + } + catch (const tools::error::zero_destination&) + { + fail_msg_writer() << tr("one of destinations is zero"); + } + catch (const tools::error::tx_too_big& e) + { + fail_msg_writer() << tr("failed to find a suitable way to split transactions"); + } + catch (const tools::error::transfer_error& e) + { + LOG_ERROR("unknown transfer error: " << e.to_string()); + fail_msg_writer() << tr("unknown transfer error: ") << e.what(); + } + catch (const tools::error::wallet_internal_error& e) + { + LOG_ERROR("internal error: " << e.to_string()); + fail_msg_writer() << tr("internal error: ") << e.what(); + } + catch (const std::exception& e) + { + LOG_ERROR("unexpected error: " << e.what()); + fail_msg_writer() << tr("unexpected error: ") << e.what(); + } + catch (...) + { + LOG_ERROR("unknown error"); + fail_msg_writer() << tr("unknown error"); + } + + return true; +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::sweep_all(const std::vector<std::string> &args_) +{ + if (!try_connect_to_daemon()) + return true; + + if(m_wallet->watch_only()) + { + fail_msg_writer() << tr("this is a watch only wallet"); + return true; + } + + std::vector<std::string> local_args = args_; + + size_t fake_outs_count; + if(local_args.size() > 0) { + if(!epee::string_tools::get_xtype_from_string(fake_outs_count, local_args[0])) + { + fake_outs_count = m_wallet->default_mixin(); + if (fake_outs_count == 0) + fake_outs_count = DEFAULT_MIX; + } + else + { + local_args.erase(local_args.begin()); + } + } + + std::vector<uint8_t> extra; + bool payment_id_seen = false; + if (2 == local_args.size()) + { + std::string payment_id_str = local_args.back(); + local_args.pop_back(); + + crypto::hash payment_id; + bool r = tools::wallet2::parse_long_payment_id(payment_id_str, payment_id); + if(r) + { + std::string extra_nonce; + set_payment_id_to_tx_extra_nonce(extra_nonce, payment_id); + r = add_extra_nonce_to_tx_extra(extra, extra_nonce); + } + else + { + crypto::hash8 payment_id8; + r = tools::wallet2::parse_short_payment_id(payment_id_str, payment_id8); + if(r) + { + std::string extra_nonce; + set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id8); + r = add_extra_nonce_to_tx_extra(extra, extra_nonce); + } + } + + if(!r) + { + fail_msg_writer() << tr("payment id has invalid format, expected 16 or 64 character hex string: ") << payment_id_str; + return true; + } + payment_id_seen = true; + } + + if (local_args.size() == 0) + { + fail_msg_writer() << tr("No address given"); + return true; + } + + bool has_payment_id; + crypto::hash8 new_payment_id; + cryptonote::account_public_address address; + if (!get_address_from_str(local_args[0], address, has_payment_id, new_payment_id)) + return true; + + if (has_payment_id) + { + if (payment_id_seen) + { + fail_msg_writer() << tr("a single transaction cannot use more than one payment id: ") << local_args[0]; + return true; + } + + std::string extra_nonce; + set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, new_payment_id); + bool r = add_extra_nonce_to_tx_extra(extra, extra_nonce); + if(!r) + { + fail_msg_writer() << tr("failed to set up payment id, though it was decoded correctly"); + return true; + } + } + + try + { + // figure out what tx will be necessary + auto ptx_vector = m_wallet->create_transactions_all(address, fake_outs_count, 0 /* unlock_time */, 0 /* unused fee arg*/, extra, m_trusted_daemon); + + if (ptx_vector.empty()) + { + fail_msg_writer() << tr("No outputs found"); + return true; + } + + // give user total and fee, and prompt to confirm + uint64_t total_fee = 0, total_sent = 0; + for (size_t n = 0; n < ptx_vector.size(); ++n) + { + total_fee += ptx_vector[n].fee; + for (const auto &vin: ptx_vector[n].tx.vin) + { + if (vin.type() == typeid(txin_to_key)) + total_sent += boost::get<txin_to_key>(vin).amount; + } + } + + std::string prompt_str; + if (ptx_vector.size() > 1) { + prompt_str = (boost::format(tr("Sweeping %s in %llu transactions for a total fee of %s. Is this okay? (Y/Yes/N/No)")) % + print_money(total_sent) % + ((unsigned long long)ptx_vector.size()) % + print_money(total_fee)).str(); + } + else { + prompt_str = (boost::format(tr("Sweeping %s for a total fee of %s. Is this okay? (Y/Yes/N/No)")) % + print_money(total_sent) % + print_money(total_fee)).str(); + } + std::string accepted = command_line::input_line(prompt_str); + if (std::cin.eof()) + return true; + if (accepted != "Y" && accepted != "y" && accepted != "Yes" && accepted != "yes") + { + fail_msg_writer() << tr("transaction cancelled."); + + // would like to return false, because no tx made, but everything else returns true + // and I don't know what returning false might adversely affect. *sigh* + return true; + } + + // actually commit the transactions + while (!ptx_vector.empty()) + { + auto & ptx = ptx_vector.back(); + m_wallet->commit_tx(ptx); + success_msg_writer(true) << tr("Money successfully sent, transaction: ") << get_transaction_hash(ptx.tx); + + // if no exception, remove element from vector + ptx_vector.pop_back(); + } + } + catch (const tools::error::daemon_busy&) + { + fail_msg_writer() << tr("daemon is busy. Please try again later."); + } + catch (const tools::error::no_connection_to_daemon&) + { + fail_msg_writer() << tr("no connection to daemon. Please make sure daemon is running."); + } + catch (const tools::error::wallet_rpc_error& e) + { + LOG_ERROR("RPC error: " << e.to_string()); + fail_msg_writer() << tr("RPC error: ") << e.what(); + } + catch (const tools::error::get_random_outs_error&) + { + fail_msg_writer() << tr("failed to get random outputs to mix"); + } + catch (const tools::error::not_enough_money& e) + { + fail_msg_writer() << boost::format(tr("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee).\n%s")) % + print_money(e.available()) % + print_money(e.tx_amount() + e.fee()) % + print_money(e.tx_amount()) % + print_money(e.fee()) % + tr("This is usually due to dust which is so small it cannot pay for itself in fees"); + } + catch (const tools::error::not_enough_outs_to_mix& e) + { + auto writer = fail_msg_writer(); + writer << tr("not enough outputs for specified mixin_count") << " = " << e.mixin_count() << ":"; + for (const cryptonote::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& outs_for_amount : e.scanty_outs()) + { + writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.amount) << ", " << tr("found outputs to mix") << " = " << outs_for_amount.outs.size(); + } + } + catch (const tools::error::tx_not_constructed&) + { + fail_msg_writer() << tr("transaction was not constructed"); + } + catch (const tools::error::tx_rejected& e) + { + fail_msg_writer() << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); + std::string reason = e.reason(); + if (!reason.empty()) + fail_msg_writer() << tr("Reason: ") << reason; } catch (const tools::error::tx_sum_overflow& e) { @@ -2370,6 +2834,8 @@ bool simple_wallet::get_tx_key(const std::vector<std::string> &args_) } crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); + LOCK_REFRESH_THREAD_SCOPE(); + crypto::secret_key tx_key; bool r = m_wallet->get_tx_key(txid, tx_key); if (r) @@ -2404,6 +2870,8 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) } crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); + LOCK_REFRESH_THREAD_SCOPE(); + cryptonote::blobdata tx_key_data; if(!epee::string_tools::parse_hexstr_to_binbuff(local_args[1], tx_key_data)) { @@ -2425,13 +2893,18 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) COMMAND_RPC_GET_TRANSACTIONS::response res; req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txid)); if (!net_utils::invoke_http_json_remote_command2(m_daemon_address + "/gettransactions", req, res, m_http_client) || - res.txs_as_hex.empty()) + (res.txs.empty() && res.txs_as_hex.empty())) { fail_msg_writer() << tr("failed to get transaction from daemon"); return true; } cryptonote::blobdata tx_data; - if (!string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data)) + bool ok; + if (!res.txs.empty()) + ok = string_tools::parse_hexstr_to_binbuff(res.txs.front().as_hex, tx_data); + else + ok = string_tools::parse_hexstr_to_binbuff(res.txs_as_hex.front(), tx_data); + if (!ok) { fail_msg_writer() << tr("failed to parse transaction from daemon"); return true; @@ -2488,6 +2961,23 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_) return true; } //---------------------------------------------------------------------------------------------------- +static std::string get_human_readable_timestamp(uint64_t ts) +{ + char buffer[64]; + if (ts < 1234567890) + return "<unknown>"; + time_t tt = ts; + struct tm tm; + gmtime_r(&tt, &tm); + uint64_t now = time(NULL); + uint64_t diff = ts > now ? ts - now : now - ts; + if (diff > 24*3600) + strftime(buffer, sizeof(buffer), "%F", &tm); + else + strftime(buffer, sizeof(buffer), "%r", &tm); + return std::string(buffer); +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::show_transfers(const std::vector<std::string> &args_) { std::vector<std::string> local_args = args_; @@ -2503,6 +2993,8 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) return true; } + LOCK_REFRESH_THREAD_SCOPE(); + // optional in/out selector if (local_args.size() > 0) { if (local_args[0] == "in" || local_args[0] == "incoming") { @@ -2560,7 +3052,8 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) std::string payment_id = string_tools::pod_to_hex(i->first); if (payment_id.substr(16).find_first_not_of('0') == std::string::npos) payment_id = payment_id.substr(0,16); - output.insert(std::make_pair(pd.m_block_height, std::make_pair(true, (boost::format("%20.20s %s %s %s") % print_money(pd.m_amount) % string_tools::pod_to_hex(pd.m_tx_hash) % payment_id % "-").str()))); + std::string note = m_wallet->get_tx_note(pd.m_tx_hash); + output.insert(std::make_pair(pd.m_block_height, std::make_pair(true, (boost::format("%16.16s %20.20s %s %s %s %s") % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount) % string_tools::pod_to_hex(pd.m_tx_hash) % payment_id % "-" % note).str()))); } } @@ -2580,7 +3073,8 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) std::string payment_id = string_tools::pod_to_hex(i->second.m_payment_id); if (payment_id.substr(16).find_first_not_of('0') == std::string::npos) payment_id = payment_id.substr(0,16); - output.insert(std::make_pair(pd.m_block_height, std::make_pair(false, (boost::format("%20.20s %s %s %14.14s %s") % print_money(pd.m_amount_in - change - fee) % string_tools::pod_to_hex(i->first) % payment_id % print_money(fee) % dests).str()))); + std::string note = m_wallet->get_tx_note(i->first); + output.insert(std::make_pair(pd.m_block_height, std::make_pair(false, (boost::format("%16.16s %20.20s %s %s %14.14s %s %s") % get_human_readable_timestamp(pd.m_timestamp) % print_money(pd.m_amount_in - change - fee) % string_tools::pod_to_hex(i->first) % payment_id % print_money(fee) % dests % note).str()))); } } @@ -2603,9 +3097,10 @@ bool simple_wallet::show_transfers(const std::vector<std::string> &args_) std::string payment_id = string_tools::pod_to_hex(i->second.m_payment_id); if (payment_id.substr(16).find_first_not_of('0') == std::string::npos) payment_id = payment_id.substr(0,16); + std::string note = m_wallet->get_tx_note(i->first); bool is_failed = pd.m_state == tools::wallet2::unconfirmed_transfer_details::failed; if ((failed && is_failed) || (!is_failed && pending)) { - message_writer() << (boost::format("%8.8s %6.6s %20.20s %s %s %14.14s") % (is_failed ? tr("failed") : tr("pending")) % tr("out") % print_money(amount - pd.m_change) % string_tools::pod_to_hex(i->first) % payment_id % print_money(fee)).str(); + message_writer() << (boost::format("%8.8s %6.6s %16.16s %20.20s %s %s %14.14s %s") % (is_failed ? tr("failed") : tr("pending")) % tr("out") % get_human_readable_timestamp(pd.m_timestamp) % print_money(amount - pd.m_change - fee) % string_tools::pod_to_hex(i->first) % payment_id % print_money(fee) % note).str(); } } } @@ -2657,7 +3152,12 @@ bool simple_wallet::run() void simple_wallet::stop() { m_cmd_binder.stop_handling(); + + m_auto_refresh_run.store(false, std::memory_order_relaxed); m_wallet->stop(); + // make the background refresh thread quit + boost::unique_lock<boost::mutex> lock(m_auto_refresh_mutex); + m_auto_refresh_cond.notify_one(); } //---------------------------------------------------------------------------------------------------- bool simple_wallet::print_address(const std::vector<std::string> &args/* = std::vector<std::string>()*/) @@ -2708,6 +3208,59 @@ bool simple_wallet::print_integrated_address(const std::vector<std::string> &arg return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::set_tx_note(const std::vector<std::string> &args) +{ + if (args.size() == 0) + { + fail_msg_writer() << tr("usage: set_tx_note [txid] free text note"); + return true; + } + + cryptonote::blobdata txid_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(args.front(), txid_data)) + { + fail_msg_writer() << tr("failed to parse txid"); + return false; + } + crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); + + std::string note = ""; + for (size_t n = 1; n < args.size(); ++n) + { + if (n > 1) + note += " "; + note += args[n]; + } + m_wallet->set_tx_note(txid, note); + + return true; +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::get_tx_note(const std::vector<std::string> &args) +{ + if (args.size() != 1) + { + fail_msg_writer() << tr("usage: get_tx_note [txid]"); + return true; + } + + cryptonote::blobdata txid_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(args.front(), txid_data)) + { + fail_msg_writer() << tr("failed to parse txid"); + return false; + } + crypto::hash txid = *reinterpret_cast<const crypto::hash*>(txid_data.data()); + + std::string note = m_wallet->get_tx_note(txid); + if (note.empty()) + success_msg_writer() << "no note found"; + else + success_msg_writer() << "note found: " << note; + + return true; +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::process_command(const std::vector<std::string> &args) { return m_cmd_binder.process_command_vec(args); @@ -2735,6 +3288,7 @@ int main(int argc, char* argv[]) std::string lang = i18n_get_language(); tools::sanitize_locale(); + tools::set_strict_default_file_permissions(true); string_tools::set_module_name_and_folder(argv[0]); @@ -2755,6 +3309,7 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, arg_daemon_port); command_line::add_arg(desc_params, arg_command); command_line::add_arg(desc_params, arg_log_level); + command_line::add_arg(desc_params, arg_max_concurrency); bf::path default_log {log_space::log_singletone::get_default_log_folder()}; std::string log_file_name = log_space::log_singletone::get_default_log_file(); @@ -2782,6 +3337,7 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, arg_testnet); command_line::add_arg(desc_params, arg_restricted); command_line::add_arg(desc_params, arg_trusted_daemon); + command_line::add_arg(desc_params, arg_restore_height); tools::wallet_rpc_server::init_options(desc_params); po::positional_options_description positional_options; @@ -2839,6 +3395,9 @@ int main(int argc, char* argv[]) LOG_LEVEL_4 ); + if(command_line::has_arg(vm, arg_max_concurrency)) + tools::set_max_concurrency(command_line::get_arg(vm, arg_max_concurrency)); + message_writer(epee::log_space::console_color_white, true) << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"; if(command_line::has_arg(vm, arg_log_level)) @@ -2891,12 +3450,25 @@ int main(int argc, char* argv[]) } tools::wallet2 wal(testnet,restricted); + bool quit = false; + tools::signal_handler::install([&wal, &quit](int) { + quit = true; + wal.stop(); + }); try { LOG_PRINT_L0(sw::tr("Loading wallet...")); wal.load(wallet_file, password); wal.init(daemon_address); wal.refresh(); + // if we ^C during potentially length load/refresh, there's no server loop yet + if (quit) + { + LOG_PRINT_L0(sw::tr("Storing wallet...")); + wal.store(); + LOG_PRINT_GREEN(sw::tr("Stored ok"), LOG_LEVEL_0); + return 1; + } LOG_PRINT_GREEN(sw::tr("Loaded ok"), LOG_LEVEL_0); } catch (const std::exception& e) @@ -2907,10 +3479,8 @@ int main(int argc, char* argv[]) tools::wallet_rpc_server wrpc(wal); bool r = wrpc.init(vm); CHECK_AND_ASSERT_MES(r, 1, sw::tr("Failed to initialize wallet rpc server")); - tools::signal_handler::install([&wrpc, &wal](int) { wrpc.send_stop_signal(); - wal.store(); }); LOG_PRINT_L0(sw::tr("Starting wallet rpc server")); wrpc.run(); |