diff --git a/contrib/epee/include/net/http_server_handlers_map2.h b/contrib/epee/include/net/http_server_handlers_map2.h index 429e3e1af..00a867d3e 100644 --- a/contrib/epee/include/net/http_server_handlers_map2.h +++ b/contrib/epee/include/net/http_server_handlers_map2.h @@ -122,6 +122,7 @@ if(!ps.load_from_json(query_info.m_body)) \ { \ boost::value_initialized rsp; \ + static_cast(rsp).jsonrpc = "2.0"; \ static_cast(rsp).error.code = -32700; \ static_cast(rsp).error.message = "Parse error"; \ epee::serialization::store_t_to_json(static_cast(rsp), response_info.m_body); \ diff --git a/contrib/epee/include/serialization/keyvalue_serialization_overloads.h b/contrib/epee/include/serialization/keyvalue_serialization_overloads.h index 7087136cc..db4b1af5e 100644 --- a/contrib/epee/include/serialization/keyvalue_serialization_overloads.h +++ b/contrib/epee/include/serialization/keyvalue_serialization_overloads.h @@ -35,6 +35,11 @@ namespace epee { + namespace + { + template void hint_resize(C &container, size_t size) {} + template void hint_resize(std::vector &container, size_t size) { container.reserve(size); } + } namespace serialization { @@ -158,6 +163,7 @@ namespace epee false, "size in blob " << loaded_size << " not have not zero modulo for sizeof(value_type) = " << sizeof(typename stl_container::value_type)); size_t count = (loaded_size/sizeof(typename stl_container::value_type)); + hint_resize(container, count); for(size_t i = 0; i < count; i++) container.insert(container.end(), *(pelem++)); } diff --git a/contrib/epee/include/storages/parserse_base_utils.h b/contrib/epee/include/storages/parserse_base_utils.h index c809392f4..8c6c1a64d 100644 --- a/contrib/epee/include/storages/parserse_base_utils.h +++ b/contrib/epee/include/storages/parserse_base_utils.h @@ -28,6 +28,8 @@ #pragma once +#include + namespace epee { namespace misc_utils @@ -36,8 +38,12 @@ namespace misc_utils { inline std::string transform_to_escape_sequence(const std::string& src) { - //std::stringstream res; + static const char escaped[] = "\b\f\n\r\t\v\"\\/"; + if (std::find_first_of(src.begin(), src.end(), escaped, escaped + sizeof(escaped)) == src.end()) + return src; + std::string res; + res.reserve(2 * src.size()); for(std::string::const_iterator it = src.begin(); it!=src.end(); ++it) { switch(*it) @@ -84,6 +90,7 @@ namespace misc_utils inline void match_string2(std::string::const_iterator& star_end_string, std::string::const_iterator buf_end, std::string& val) { val.clear(); + val.reserve(std::distance(star_end_string, buf_end)); bool escape_mode = false; std::string::const_iterator it = star_end_string; ++it; diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index ca7254bf5..fce729634 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -201,15 +201,25 @@ namespace cryptonote { crypto::key_derivation recv_derivation = AUTO_VAL_INIT(recv_derivation); bool r = hwdev.generate_key_derivation(tx_public_key, ack.m_view_secret_key, recv_derivation); - CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to generate_key_derivation(" << tx_public_key << ", " << ack.m_view_secret_key << ")"); + if (!r) + { + MWARNING("key image helper: failed to generate_key_derivation(" << tx_public_key << ", " << ack.m_view_secret_key << ")"); + memcpy(&recv_derivation, rct::identity().bytes, sizeof(recv_derivation)); + } std::vector additional_recv_derivations; for (size_t i = 0; i < additional_tx_public_keys.size(); ++i) { crypto::key_derivation additional_recv_derivation = AUTO_VAL_INIT(additional_recv_derivation); r = hwdev.generate_key_derivation(additional_tx_public_keys[i], ack.m_view_secret_key, additional_recv_derivation); - CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to generate_key_derivation(" << additional_tx_public_keys[i] << ", " << ack.m_view_secret_key << ")"); - additional_recv_derivations.push_back(additional_recv_derivation); + if (!r) + { + MWARNING("key image helper: failed to generate_key_derivation(" << additional_tx_public_keys[i] << ", " << ack.m_view_secret_key << ")"); + } + else + { + additional_recv_derivations.push_back(additional_recv_derivation); + } } boost::optional subaddr_recv_info = is_out_to_acc_precomp(subaddresses, out_key, recv_derivation, additional_recv_derivations, real_output_index,hwdev); diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 684a6b363..044a66953 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -746,7 +746,7 @@ namespace cryptonote m_blockchain.for_all_txpool_txes([&tx_infos, key_image_infos, include_sensitive_data](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd){ tx_info txi; txi.id_hash = epee::string_tools::pod_to_hex(txid); - txi.tx_blob = *bd; + txi.tx_blob = epee::string_tools::buff_to_hex_nodelimer(*bd); transaction tx; if (!parse_and_validate_tx_from_blob(*bd, tx)) { @@ -1095,7 +1095,7 @@ namespace cryptonote LockedTXN lock(m_blockchain); auto sorted_it = m_txs_by_fee_and_receive_time.begin(); - while (sorted_it != m_txs_by_fee_and_receive_time.end()) + for (; sorted_it != m_txs_by_fee_and_receive_time.end(); ++sorted_it) { txpool_tx_meta_t meta; if (!m_blockchain.get_txpool_tx_meta(sorted_it->second, meta)) @@ -1109,7 +1109,6 @@ namespace cryptonote if (max_total_size < total_size + meta.blob_size) { LOG_PRINT_L2(" would exceed maximum block size"); - sorted_it++; continue; } @@ -1122,14 +1121,12 @@ namespace cryptonote if(!get_block_reward(median_size, total_size + meta.blob_size, already_generated_coins, block_reward, version)) { LOG_PRINT_L2(" would exceed maximum block size"); - sorted_it++; continue; } coinbase = block_reward + fee + meta.fee; if (coinbase < template_accept_threshold(best_coinbase)) { LOG_PRINT_L2(" would decrease coinbase to " << print_money(coinbase)); - sorted_it++; continue; } } @@ -1173,13 +1170,11 @@ namespace cryptonote if (!ready) { LOG_PRINT_L2(" not ready to go"); - sorted_it++; continue; } if (have_key_images(k_images, tx)) { LOG_PRINT_L2(" key images already seen"); - sorted_it++; continue; } @@ -1188,7 +1183,6 @@ namespace cryptonote fee += meta.fee; best_coinbase = coinbase; append_key_images(k_images, tx); - sorted_it++; LOG_PRINT_L2(" added, new block size " << total_size << "/" << max_total_size << ", coinbase " << print_money(best_coinbase)); } @@ -1284,6 +1278,7 @@ namespace cryptonote { MWARNING("Failed to parse tx from txpool, removing"); remove.push_back(txid); + return true; } if (!insert_key_images(tx, meta.kept_by_block)) { diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index 6e7240b57..e970afcde 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -1167,8 +1167,20 @@ skip: + " blocks/sec), " + std::to_string(m_block_queue.get_data_size() / 1048576.f) + " MB queued"; if (ELPP->vRegistry()->allowed(el::Level::Debug, "sync-info")) timing_message += std::string(": ") + m_block_queue.get_overview(); - MGINFO_YELLOW(context << " Synced " << m_core.get_current_blockchain_height() << "/" << m_core.get_target_blockchain_height() + if(m_core.get_target_blockchain_height() == 0){ + MGINFO_YELLOW(context << " Synced " << m_core.get_current_blockchain_height() << "/" << m_core.get_target_blockchain_height() << timing_message); + } else { + const int completition_percent = (m_core.get_current_blockchain_height() * 100 / m_core.get_target_blockchain_height()); + if(completition_percent < 99) {//printing completion percent only if % is < of 99 cause for 99 >= this is useless + MGINFO_YELLOW(context << " Synced " << m_core.get_current_blockchain_height() << "/" << m_core.get_target_blockchain_height() + << " (" << completition_percent << "% " << (m_core.get_target_blockchain_height() - m_core.get_current_blockchain_height()) + << " blocks remaining)" << timing_message); + } else { + MGINFO_YELLOW(context << " Synced " << m_core.get_current_blockchain_height() << "/" << m_core.get_target_blockchain_height() + << timing_message); + } + } } } } @@ -1752,3 +1764,4 @@ skip: m_core.stop(); } } // namespace + diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index c73d98a84..8af512208 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -438,7 +438,8 @@ bool t_rpc_command_executor::show_status() { } } - tools::success_msg_writer() << boost::format("Height: %llu/%llu (%.1f%%) on %s%s, %s, net hash %s, v%u%s, %s, %u(out)+%u(in) connections, uptime %ud %uh %um %us") + std::stringstream str; + str << boost::format("Height: %llu/%llu (%.1f%%) on %s%s, %s, net hash %s, v%u%s, %s, %u(out)+%u(in) connections") % (unsigned long long)ires.height % (unsigned long long)net_height % get_sync_percentage(ires) @@ -451,12 +452,21 @@ bool t_rpc_command_executor::show_status() { % (hfres.state == cryptonote::HardFork::Ready ? "up to date" : hfres.state == cryptonote::HardFork::UpdateNeeded ? "update needed" : "out of date, likely forked") % (unsigned)ires.outgoing_connections_count % (unsigned)ires.incoming_connections_count - % (unsigned int)floor(uptime / 60.0 / 60.0 / 24.0) - % (unsigned int)floor(fmod((uptime / 60.0 / 60.0), 24.0)) - % (unsigned int)floor(fmod((uptime / 60.0), 60.0)) - % (unsigned int)fmod(uptime, 60.0) ; + // restricted RPC does not disclose start time + if (ires.start_time) + { + str << boost::format(", uptime %ud %uh %um %us") + % (unsigned int)floor(uptime / 60.0 / 60.0 / 24.0) + % (unsigned int)floor(fmod((uptime / 60.0 / 60.0), 24.0)) + % (unsigned int)floor(fmod((uptime / 60.0), 60.0)) + % (unsigned int)fmod(uptime, 60.0) + ; + } + + tools::success_msg_writer() << str.str(); + return true; } @@ -554,7 +564,7 @@ bool t_rpc_command_executor::print_blockchain_info(uint64_t start_block_index, u if (!first) std::cout << std::endl; std::cout - << "height: " << header.height << ", timestamp: " << header.timestamp << ", difficulty: " << header.difficulty + << "height: " << header.height << ", timestamp: " << header.timestamp << ", size: " << header.block_size << ", transactions: " << header.num_txes << std::endl << "major version: " << (unsigned)header.major_version << ", minor version: " << (unsigned)header.minor_version << std::endl << "block id: " << header.hash << ", previous block id: " << header.prev_hash << std::endl diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 3ec67e7e0..8617c7e28 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -863,7 +863,13 @@ namespace cryptonote boost::thread::attributes attrs; attrs.set_stack_size(THREAD_STACK_SIZE); - if(!m_core.get_miner().start(info.address, static_cast(req.threads_count), attrs, req.do_background_mining, req.ignore_battery)) + cryptonote::miner &miner= m_core.get_miner(); + if (miner.is_mining()) + { + res.status = "Already mining"; + return true; + } + if(!miner.start(info.address, static_cast(req.threads_count), attrs, req.do_background_mining, req.ignore_battery)) { res.status = "Failed, mining not started"; LOG_PRINT_L0(res.status); diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index f4ea54563..137d84b6e 100755 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -93,13 +93,8 @@ typedef cryptonote::simple_wallet sw; m_auto_refresh_enabled.store(false, std::memory_order_relaxed); \ /* stop any background refresh, and take over */ \ m_wallet->stop(); \ - m_idle_mutex.lock(); \ - while (m_auto_refresh_refreshing) \ - m_idle_cond.notify_one(); \ - m_idle_mutex.unlock(); \ -/* if (auto_refresh_run)*/ \ - /*m_auto_refresh_thread.join();*/ \ boost::unique_lock lock(m_idle_mutex); \ + m_idle_cond.notify_all(); \ epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ \ m_auto_refresh_enabled.store(auto_refresh_enabled, std::memory_order_relaxed); \ }) @@ -525,6 +520,18 @@ bool parse_priority(const std::string& arg, uint32_t& priority) return false; } +std::string join_priority_strings(const char *delimiter) +{ + std::string s; + for (size_t n = 0; n < allowed_priority_strings.size(); ++n) + { + if (!s.empty()) + s += delimiter; + s += allowed_priority_strings[n]; + } + return s; +} + std::string simple_wallet::get_commands_str() { std::stringstream ss; @@ -1611,12 +1618,12 @@ bool simple_wallet::set_store_tx_info(const std::vector &args/* = s bool simple_wallet::set_default_priority(const std::vector &args/* = std::vector()*/) { - int priority = 0; + uint32_t priority = 0; try { if (strchr(args[1].c_str(), '-')) { - fail_msg_writer() << tr("priority must be 0, 1, 2, 3, or 4 "); + fail_msg_writer() << tr("priority must be either 0, 1, 2, 3, or 4, or one of: ") << join_priority_strings(", "); return true; } if (args[1] == "0") @@ -1625,11 +1632,23 @@ bool simple_wallet::set_default_priority(const std::vector &args/* } else { - priority = boost::lexical_cast(args[1]); - if (priority < 1 || priority > 4) + bool found = false; + for (size_t n = 0; n < allowed_priority_strings.size(); ++n) { - fail_msg_writer() << tr("priority must be 0, 1, 2, 3, or 4"); - return true; + if (allowed_priority_strings[n] == args[1]) + { + found = true; + priority = n; + } + } + if (!found) + { + priority = boost::lexical_cast(args[1]); + if (priority < 1 || priority > 4) + { + fail_msg_writer() << tr("priority must be either 0, 1, 2, 3, or 4, or one of: ") << join_priority_strings(", "); + return true; + } } } @@ -1643,7 +1662,7 @@ bool simple_wallet::set_default_priority(const std::vector &args/* } catch(const boost::bad_lexical_cast &) { - fail_msg_writer() << tr("priority must be 0, 1, 2, 3, or 4"); + fail_msg_writer() << tr("priority must be either 0, 1, 2, 3, or 4, or one of: ") << join_priority_strings(", "); return true; } catch(...) @@ -2178,7 +2197,7 @@ simple_wallet::simple_wallet() tr("Show the unspent outputs of a specified address within an optional amount range.")); m_cmd_binder.set_handler("rescan_bc", boost::bind(&simple_wallet::rescan_blockchain, this, _1), - tr("Rescan the blockchain from scratch.")); + tr("Rescan the blockchain from scratch, losing any information which can not be recovered from the blockchain itself.")); m_cmd_binder.set_handler("set_tx_note", boost::bind(&simple_wallet::set_tx_note, this, _1), tr("set_tx_note [free text note]"), @@ -2308,6 +2327,10 @@ bool simple_wallet::set_variable(const std::vector &args) std::string seed_language = m_wallet->get_seed_language(); if (m_use_english_language_names) seed_language = crypto::ElectrumWords::get_english_name_for(seed_language); + std::string priority_string = "invalid"; + uint32_t priority = m_wallet->get_default_priority(); + if (priority < allowed_priority_strings.size()) + priority_string = allowed_priority_strings[priority]; success_msg_writer() << "seed = " << seed_language; success_msg_writer() << "always-confirm-transfers = " << m_wallet->always_confirm_transfers(); success_msg_writer() << "print-ring-members = " << m_wallet->print_ring_members(); @@ -2627,6 +2650,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) m_recovery_key = cryptonote::decrypt_key(m_recovery_key, seed_pass); } } + epee::wipeable_string password; if (!m_generate_from_view_key.empty()) { m_wallet_file = m_generate_from_view_key; @@ -2679,8 +2703,9 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) return false; } - bool r = new_wallet(vm, info.address, boost::none, viewkey); + auto r = new_wallet(vm, info.address, boost::none, viewkey); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); + password = *r; } else if (!m_generate_from_spend_key.empty()) { @@ -2698,8 +2723,9 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) fail_msg_writer() << tr("failed to parse spend key secret key"); return false; } - bool r = new_wallet(vm, m_recovery_key, true, false, ""); + auto r = new_wallet(vm, m_recovery_key, true, false, ""); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); + password = *r; } else if (!m_generate_from_keys.empty()) { @@ -2776,8 +2802,9 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) fail_msg_writer() << tr("view key does not match standard address"); return false; } - bool r = new_wallet(vm, info.address, spendkey, viewkey); + auto r = new_wallet(vm, info.address, spendkey, viewkey); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); + password = *r; } // Asks user for all the data required to merge secret keys from multisig wallets into one master wallet, which then gets full control of the multisig wallet. The resulting wallet will be the same as any other regular wallet. @@ -2910,8 +2937,9 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) } // create wallet - bool r = new_wallet(vm, info.address, spendkey, viewkey); + auto r = new_wallet(vm, info.address, spendkey, viewkey); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); + password = *r; } else if (!m_generate_from_json.empty()) @@ -2933,8 +2961,9 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) { m_wallet_file = m_generate_from_device; // create wallet - bool r = new_wallet(vm, "Ledger"); + auto r = new_wallet(vm, "Ledger"); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); + password = *r; // if no block_height is specified, assume its a new account and start it "now" if(m_wallet->get_refresh_from_block_height() == 0) { { @@ -2959,12 +2988,13 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) return false; } m_wallet_file = m_generate_new; - bool r; + boost::optional r; if (m_restore_multisig_wallet) r = new_wallet(vm, multisig_keys, old_language); else r = new_wallet(vm, m_recovery_key, m_restore_deterministic_wallet, m_non_deterministic, old_language); CHECK_AND_ASSERT_MES(r, false, tr("account creation failed")); + password = *r; } if (m_restoring && m_generate_from_json.empty() && m_generate_from_device.empty()) @@ -3046,6 +3076,7 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) } m_wallet->set_refresh_from_block_height(m_restore_height); } + m_wallet->rewrite(m_wallet_file, password); } else { @@ -3213,15 +3244,16 @@ boost::optional simple_wallet::get_and_verify_passwor return pwd_container; } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::new_wallet(const boost::program_options::variables_map& vm, +boost::optional simple_wallet::new_wallet(const boost::program_options::variables_map& vm, const crypto::secret_key& recovery_key, bool recover, bool two_random, const std::string &old_language) { auto rc = tools::wallet2::make_new(vm, password_prompter); m_wallet = std::move(rc.first); if (!m_wallet) { - return false; + return {}; } + epee::wipeable_string password = rc.second.password(); if (!m_subaddress_lookahead.empty()) { @@ -3257,7 +3289,7 @@ bool simple_wallet::new_wallet(const boost::program_options::variables_map& vm, } mnemonic_language = get_mnemonic_language(); if (mnemonic_language.empty()) - return false; + return {}; } m_wallet->set_seed_language(mnemonic_language); @@ -3275,7 +3307,7 @@ bool simple_wallet::new_wallet(const boost::program_options::variables_map& vm, catch (const std::exception& e) { fail_msg_writer() << tr("failed to generate new wallet: ") << e.what(); - return false; + return {}; } // convert rng value to electrum-style word list @@ -3301,10 +3333,10 @@ bool simple_wallet::new_wallet(const boost::program_options::variables_map& vm, } success_msg_writer() << "**********************************************************************"; - return true; + return std::move(password); } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::new_wallet(const boost::program_options::variables_map& vm, +boost::optional simple_wallet::new_wallet(const boost::program_options::variables_map& vm, const cryptonote::account_public_address& address, const boost::optional& spendkey, const crypto::secret_key& viewkey) { @@ -3312,8 +3344,9 @@ bool simple_wallet::new_wallet(const boost::program_options::variables_map& vm, m_wallet = std::move(rc.first); if (!m_wallet) { - return false; + return {}; } + epee::wipeable_string password = rc.second.password(); if (!m_subaddress_lookahead.empty()) { @@ -3343,22 +3376,23 @@ bool simple_wallet::new_wallet(const boost::program_options::variables_map& vm, catch (const std::exception& e) { fail_msg_writer() << tr("failed to generate new wallet: ") << e.what(); - return false; + return {}; } - return true; + return std::move(password); } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::new_wallet(const boost::program_options::variables_map& vm, +boost::optional simple_wallet::new_wallet(const boost::program_options::variables_map& vm, const std::string &device_name) { auto rc = tools::wallet2::make_new(vm, password_prompter); m_wallet = std::move(rc.first); if (!m_wallet) { - return false; + return {}; } + epee::wipeable_string password = rc.second.password(); if (!m_subaddress_lookahead.empty()) { @@ -3379,21 +3413,22 @@ bool simple_wallet::new_wallet(const boost::program_options::variables_map& vm, catch (const std::exception& e) { fail_msg_writer() << tr("failed to generate new wallet: ") << e.what(); - return false; + return {}; } - return true; + return std::move(password); } //---------------------------------------------------------------------------------------------------- -bool simple_wallet::new_wallet(const boost::program_options::variables_map& vm, +boost::optional simple_wallet::new_wallet(const boost::program_options::variables_map& vm, const std::string &multisig_keys, const std::string &old_language) { auto rc = tools::wallet2::make_new(vm, password_prompter); m_wallet = std::move(rc.first); if (!m_wallet) { - return false; + return {}; } + epee::wipeable_string password = rc.second.password(); if (!m_subaddress_lookahead.empty()) { @@ -3423,7 +3458,7 @@ bool simple_wallet::new_wallet(const boost::program_options::variables_map& vm, if (!m_wallet->multisig(&ready, &threshold, &total) || !ready) { fail_msg_writer() << tr("failed to generate new mutlisig wallet"); - return false; + return {}; } message_writer(console_color_white, true) << boost::format(tr("Generated new %u/%u multisig wallet: ")) % threshold % total << m_wallet->get_account().get_public_address_str(m_wallet->nettype()); @@ -3431,10 +3466,10 @@ bool simple_wallet::new_wallet(const boost::program_options::variables_map& vm, catch (const std::exception& e) { fail_msg_writer() << tr("failed to generate new wallet: ") << e.what(); - return false; + return {}; } - return true; + return std::move(password); } //---------------------------------------------------------------------------------------------------- bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) @@ -5972,7 +6007,7 @@ static std::string get_human_readable_timespan(std::chrono::seconds seconds) if (ts < 3600 * 24 * 30.5) return std::to_string((uint64_t)(ts / (3600 * 24))) + tr(" days"); if (ts < 3600 * 24 * 365.25) - return std::to_string((uint64_t)(ts / (3600 * 24 * 365.25))) + tr(" months"); + return std::to_string((uint64_t)(ts / (3600 * 24 * 30.5))) + tr(" months"); return tr("a long time"); } //---------------------------------------------------------------------------------------------------- @@ -6168,7 +6203,7 @@ bool simple_wallet::unspent_outputs(const std::vector &args_) auto local_args = args_; std::set subaddr_indices; - if (local_args.size() > 0 && local_args[0].substr(0, 6) != "index=") + if (local_args.size() > 0 && local_args[0].substr(0, 6) == "index=") { if (!parse_subaddress_indices(local_args[0], subaddr_indices)) return true; @@ -6298,6 +6333,14 @@ bool simple_wallet::unspent_outputs(const std::vector &args_) //---------------------------------------------------------------------------------------------------- bool simple_wallet::rescan_blockchain(const std::vector &args_) { + message_writer() << tr("Warning: this will lose any information which can not be recovered from the blockchain."); + message_writer() << tr("This includes destination addresses, tx secret keys, tx notes, etc"); + std::string confirm = input_line(tr("Rescan anyway ? (Y/Yes/N/No): ")); + if(!std::cin.eof()) + { + if (!command_line::is_yes(confirm)) + return true; + } return refresh_main(0, true); } //---------------------------------------------------------------------------------------------------- diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index c52c50909..8157ce9a2 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -91,13 +91,13 @@ namespace cryptonote //! \return Prompts user for password and verifies against local file. Logs on error and returns `none` boost::optional get_and_verify_password() const; - bool new_wallet(const boost::program_options::variables_map& vm, const crypto::secret_key& recovery_key, + boost::optional new_wallet(const boost::program_options::variables_map& vm, const crypto::secret_key& recovery_key, bool recover, bool two_random, const std::string &old_language); - bool new_wallet(const boost::program_options::variables_map& vm, const cryptonote::account_public_address& address, + boost::optional new_wallet(const boost::program_options::variables_map& vm, const cryptonote::account_public_address& address, const boost::optional& spendkey, const crypto::secret_key& viewkey); - bool new_wallet(const boost::program_options::variables_map& vm, + boost::optional new_wallet(const boost::program_options::variables_map& vm, const std::string &multisig_keys, const std::string &old_language); - bool new_wallet(const boost::program_options::variables_map& vm, const std::string& device_name); + boost::optional new_wallet(const boost::program_options::variables_map& vm, const std::string& device_name); bool open_wallet(const boost::program_options::variables_map& vm); bool close_wallet(); diff --git a/src/wallet/node_rpc_proxy.cpp b/src/wallet/node_rpc_proxy.cpp index c5d869354..3ac7d4406 100644 --- a/src/wallet/node_rpc_proxy.cpp +++ b/src/wallet/node_rpc_proxy.cpp @@ -146,7 +146,7 @@ boost::optional NodeRPCProxy::get_earliest_height(uint8_t version, CHECK_AND_ASSERT_MES(r, std::string(), "Failed to connect to daemon"); CHECK_AND_ASSERT_MES(resp_t.status != CORE_RPC_STATUS_BUSY, resp_t.status, "Failed to connect to daemon"); CHECK_AND_ASSERT_MES(resp_t.status == CORE_RPC_STATUS_OK, resp_t.status, "Failed to get hard fork status"); - m_earliest_height[version] = resp_t.enabled ? resp_t.earliest_height : std::numeric_limits::max(); + m_earliest_height[version] = resp_t.earliest_height; } earliest_height = m_earliest_height[version]; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 2cdb2e9d5..2c741c2e5 100755 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -887,6 +887,14 @@ cryptonote::account_public_address wallet2::get_subaddress(const cryptonote::sub return hwdev.get_subaddress(m_account.get_keys(), index); } //---------------------------------------------------------------------------------------------------- +boost::optional wallet2::get_subaddress_index(const cryptonote::account_public_address& address) const +{ + auto index = m_subaddresses.find(address.m_spend_public_key); + if (index == m_subaddresses.end()) + return boost::none; + return index->second; +} +//---------------------------------------------------------------------------------------------------- crypto::public_key wallet2::get_subaddress_spend_public_key(const cryptonote::subaddress_index& index) const { hw::device &hwdev = m_account.get_device(); @@ -1100,7 +1108,6 @@ 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 outs; std::unordered_map tx_money_got_in_outs; // per receiving subaddress index crypto::public_key tx_pub_key = null_pkey; @@ -1118,6 +1125,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote uint64_t total_received_1 = 0; while (!tx.vout.empty()) { + std::vector outs; // if tx.vout is not empty, we loop through all tx pubkeys tx_extra_pub_key pub_key_field; @@ -2385,7 +2393,7 @@ bool wallet2::refresh(uint64_t & blocks_fetched, bool& received_money, bool& ok) return ok; } //---------------------------------------------------------------------------------------------------- -bool wallet2::get_output_distribution(uint64_t &start_height, std::vector &distribution) +bool wallet2::get_rct_distribution(uint64_t &start_height, std::vector &distribution) { uint32_t rpc_version; boost::optional result = m_node_rpc_proxy.get_rpc_version(rpc_version); @@ -5909,22 +5917,42 @@ void wallet2::get_outs(std::vector> bool is_shortly_after_segregation_fork = height >= segregation_fork_height && height < segregation_fork_height + SEGREGATION_FORK_VICINITY; bool is_after_segregation_fork = height >= segregation_fork_height; + // if we have at least one rct out, get the distribution, or fall back to the previous system + uint64_t rct_start_height; + std::vector rct_offsets; + bool has_rct = false; + for (size_t idx: selected_transfers) + if (m_transfers[idx].is_rct()) + { has_rct = true; break; } + const bool has_rct_distribution = has_rct && get_rct_distribution(rct_start_height, rct_offsets); + if (has_rct_distribution) + { + // check we're clear enough of rct start, to avoid corner cases below + THROW_WALLET_EXCEPTION_IF(rct_offsets.size() <= CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE, + error::get_output_distribution, "Not enough rct outputs"); + } + // get histogram for the amounts we need cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request req_t = AUTO_VAL_INIT(req_t); cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response resp_t = AUTO_VAL_INIT(resp_t); - m_daemon_rpc_mutex.lock(); + // request histogram for all outputs, except 0 if we have the rct distribution for(size_t idx: selected_transfers) - req_t.amounts.push_back(m_transfers[idx].is_rct() ? 0 : m_transfers[idx].amount()); - std::sort(req_t.amounts.begin(), req_t.amounts.end()); - auto end = std::unique(req_t.amounts.begin(), req_t.amounts.end()); - req_t.amounts.resize(std::distance(req_t.amounts.begin(), end)); - req_t.unlocked = true; - req_t.recent_cutoff = time(NULL) - RECENT_OUTPUT_ZONE; - bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, m_http_client, rpc_timeout); - m_daemon_rpc_mutex.unlock(); - THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "transfer_selected"); - THROW_WALLET_EXCEPTION_IF(resp_t.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram"); - THROW_WALLET_EXCEPTION_IF(resp_t.status != CORE_RPC_STATUS_OK, error::get_histogram_error, resp_t.status); + if (!m_transfers[idx].is_rct() || !has_rct_distribution) + req_t.amounts.push_back(m_transfers[idx].is_rct() ? 0 : m_transfers[idx].amount()); + if (!req_t.amounts.empty()) + { + std::sort(req_t.amounts.begin(), req_t.amounts.end()); + auto end = std::unique(req_t.amounts.begin(), req_t.amounts.end()); + req_t.amounts.resize(std::distance(req_t.amounts.begin(), end)); + req_t.unlocked = true; + req_t.recent_cutoff = time(NULL) - RECENT_OUTPUT_ZONE; + m_daemon_rpc_mutex.lock(); + bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_histogram", req_t, resp_t, m_http_client, rpc_timeout); + m_daemon_rpc_mutex.unlock(); + THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "transfer_selected"); + THROW_WALLET_EXCEPTION_IF(resp_t.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram"); + THROW_WALLET_EXCEPTION_IF(resp_t.status != CORE_RPC_STATUS_OK, error::get_histogram_error, resp_t.status); + } // if we want to segregate fake outs pre or post fork, get distribution std::unordered_map> segregation_limit; @@ -5980,6 +6008,36 @@ void wallet2::get_outs(std::vector> COMMAND_RPC_GET_OUTPUTS_BIN::request req = AUTO_VAL_INIT(req); COMMAND_RPC_GET_OUTPUTS_BIN::response daemon_resp = AUTO_VAL_INIT(daemon_resp); + struct gamma_engine + { + typedef uint64_t result_type; + static constexpr result_type min() { return 0; } + static constexpr result_type max() { return std::numeric_limits::max(); } + result_type operator()() { return crypto::rand(); } + } engine; + static const double shape = 19.28/*16.94*/; + //static const double shape = m_testnet ? 17.02 : 17.28; + static const double scale = 1/1.61; + std::gamma_distribution gamma(shape, scale); + auto pick_gamma = [&]() + { + double x = gamma(engine); + x = exp(x); + uint64_t block_offset = x / DIFFICULTY_TARGET_V2; // this assumes constant target over the whole rct range + if (block_offset >= rct_offsets.size() - 1) + return std::numeric_limits::max(); // bad pick + block_offset = rct_offsets.size() - 2 - block_offset; + THROW_WALLET_EXCEPTION_IF(block_offset >= rct_offsets.size() - 1, error::wallet_internal_error, "Bad offset calculation"); + THROW_WALLET_EXCEPTION_IF(rct_offsets[block_offset + 1] < rct_offsets[block_offset], + error::get_output_distribution, "Decreasing offsets in rct distribution: " + + std::to_string(block_offset) + ": " + std::to_string(rct_offsets[block_offset]) + ", " + + std::to_string(block_offset + 1) + ": " + std::to_string(rct_offsets[block_offset + 1])); + uint64_t n_rct = rct_offsets[block_offset + 1] - rct_offsets[block_offset]; + if (n_rct == 0) + return rct_offsets[block_offset] ? rct_offsets[block_offset] - 1 : 0; + return rct_offsets[block_offset] + crypto::rand() % n_rct; + }; + size_t num_selected_transfers = 0; for(size_t idx: selected_transfers) { @@ -5990,6 +6048,7 @@ void wallet2::get_outs(std::vector> // request more for rct in base recent (locked) coinbases are picked, since they're locked for longer size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0); size_t start = req.outputs.size(); + bool use_histogram = amount != 0 || !has_rct_distribution; const bool output_is_pre_fork = td.m_block_height < segregation_fork_height; uint64_t num_outs = 0, num_recent_outs = 0; @@ -6045,26 +6104,41 @@ void wallet2::get_outs(std::vector> num_post_fork_outs = num_outs - segregation_limit[amount].first; } - LOG_PRINT_L1("" << num_outs << " unlocked outputs of size " << print_money(amount)); - THROW_WALLET_EXCEPTION_IF(num_outs == 0, error::wallet_internal_error, - "histogram reports no unlocked outputs for " + boost::lexical_cast(amount) + ", not even ours"); - THROW_WALLET_EXCEPTION_IF(num_recent_outs > num_outs, error::wallet_internal_error, - "histogram reports more recent outs than outs for " + boost::lexical_cast(amount)); + if (use_histogram) + { + LOG_PRINT_L1("" << num_outs << " unlocked outputs of size " << print_money(amount)); + THROW_WALLET_EXCEPTION_IF(num_outs == 0, error::wallet_internal_error, + "histogram reports no unlocked outputs for " + boost::lexical_cast(amount) + ", not even ours"); + THROW_WALLET_EXCEPTION_IF(num_recent_outs > num_outs, error::wallet_internal_error, + "histogram reports more recent outs than outs for " + boost::lexical_cast(amount)); + } + else + { + // the base offset of the first rct output in the first unlocked block (or the one to be if there's none) + num_outs = rct_offsets[rct_offsets.size() - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE]; + LOG_PRINT_L1("" << num_outs << " unlocked rct outputs"); + THROW_WALLET_EXCEPTION_IF(num_outs == 0, error::wallet_internal_error, + "histogram reports no unlocked rct outputs, not even ours"); + } - // how many fake outs to draw on a pre-fork triangular distribution + // how many fake outs to draw on a pre-fork distribution size_t pre_fork_outputs_count = requested_outputs_count * pre_fork_num_out_ratio; size_t post_fork_outputs_count = requested_outputs_count * post_fork_num_out_ratio; // how many fake outs to draw otherwise size_t normal_output_count = requested_outputs_count - pre_fork_outputs_count - post_fork_outputs_count; - // X% of those outs are to be taken from recent outputs - size_t recent_outputs_count = normal_output_count * RECENT_OUTPUT_RATIO; - if (recent_outputs_count == 0) - recent_outputs_count = 1; // ensure we have at least one, if possible - if (recent_outputs_count > num_recent_outs) - recent_outputs_count = num_recent_outs; - if (td.m_global_output_index >= num_outs - num_recent_outs && recent_outputs_count > 0) - --recent_outputs_count; // if the real out is recent, pick one less recent fake out + size_t recent_outputs_count = 0; + if (use_histogram) + { + // X% of those outs are to be taken from recent outputs + recent_outputs_count = normal_output_count * RECENT_OUTPUT_RATIO; + if (recent_outputs_count == 0) + recent_outputs_count = 1; // ensure we have at least one, if possible + if (recent_outputs_count > num_recent_outs) + recent_outputs_count = num_recent_outs; + if (td.m_global_output_index >= num_outs - num_recent_outs && recent_outputs_count > 0) + --recent_outputs_count; // if the real out is recent, pick one less recent fake out + } LOG_PRINT_L1("Fake output makeup: " << requested_outputs_count << " requested: " << recent_outputs_count << " recent, " << pre_fork_outputs_count << " pre-fork, " << post_fork_outputs_count << " post-fork, " << (requested_outputs_count - recent_outputs_count - pre_fork_outputs_count - post_fork_outputs_count) << " full-chain"); @@ -6144,7 +6218,26 @@ void wallet2::get_outs(std::vector> uint64_t i; const char *type = ""; - if (num_found - 1 < recent_outputs_count) // -1 to account for the real one we seeded with + if (amount == 0 && has_rct_distribution) + { + // gamma distribution + if (num_found -1 < recent_outputs_count + pre_fork_outputs_count) + { + do i = pick_gamma(); while (i >= segregation_limit[amount].first); + type = "pre-fork gamma"; + } + else if (num_found -1 < recent_outputs_count + pre_fork_outputs_count + post_fork_outputs_count) + { + do i = pick_gamma(); while (i < segregation_limit[amount].first || i >= num_outs); + type = "post-fork gamma"; + } + else + { + do i = pick_gamma(); while (i >= num_outs); + type = "gamma"; + } + } + else if (num_found - 1 < recent_outputs_count) // -1 to account for the real one we seeded with { // triangular distribution over [a,b) with a=0, mode c=b=up_index_limit uint64_t r = crypto::rand() % ((uint64_t)1 << 53); @@ -6209,7 +6302,7 @@ void wallet2::get_outs(std::vector> // get the keys for those m_daemon_rpc_mutex.lock(); - r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, daemon_resp, m_http_client, rpc_timeout); + bool r = epee::net_utils::invoke_http_bin("/get_outs.bin", req, daemon_resp, m_http_client, rpc_timeout); m_daemon_rpc_mutex.unlock(); THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_outs.bin"); THROW_WALLET_EXCEPTION_IF(daemon_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_outs.bin"); @@ -8119,7 +8212,7 @@ bool wallet2::use_fork_rules(uint8_t version, int64_t early_blocks) const result = m_node_rpc_proxy.get_earliest_height(version, earliest_height); throw_on_rpc_response_error(result, "get_hard_fork_info"); - bool close_enough = height >= earliest_height - early_blocks && earliest_height != std::numeric_limits::max(); // start using the rules that many blocks beforehand + bool close_enough = height >= earliest_height - early_blocks; // start using the rules that many blocks beforehand if (close_enough) LOG_PRINT_L2("Using v" << (unsigned)version << " rules"); else @@ -8902,6 +8995,7 @@ std::string wallet2::get_reserve_proof(const boost::optionalsecond == 0, error::wallet_internal_error, "Proved amount must be greater than 0"); // minimize the number of outputs included in the proof, by only picking the N largest outputs that can cover the requested min reserve amount std::sort(selected_transfers.begin(), selected_transfers.end(), [&](const size_t a, const size_t b) { return m_transfers[a].amount() > m_transfers[b].amount(); }); @@ -9567,6 +9661,17 @@ uint64_t wallet2::import_key_images(const std::vector spent_txids; // For each spent key image, search for a tx in m_transfers that uses it as input. std::vector swept_transfers; // If such a spending tx wasn't found in m_transfers, this means the spending tx // was created by sweep_all, so we can't know the spent height and other detailed info. + std::unordered_map spent_key_images; + + for (const transfer_details &td: m_transfers) + { + for (const cryptonote::txin_v& in : td.m_tx.vin) + { + if (in.type() == typeid(cryptonote::txin_to_key)) + spent_key_images.insert(std::make_pair(boost::get(in).k_image, td.m_txid)); + } + } + for(size_t i = 0; i < signed_key_images.size(); ++i) { transfer_details &td = m_transfers[i]; @@ -9580,28 +9685,11 @@ uint64_t wallet2::import_key_images(const std::vectorm_tx.vin) - { - if(in.type() == typeid(cryptonote::txin_to_key) && td.m_key_image == boost::get(in).k_image) - { - is_spent_tx = true; - break; - } - } - if (is_spent_tx) - { - is_spent_tx_found = true; - spent_txids.insert(it->m_txid); - break; - } - } - - if (!is_spent_tx_found) + const std::unordered_map::const_iterator skii = spent_key_images.find(td.m_key_image); + if (skii == spent_key_images.end()) swept_transfers.push_back(i); + else + spent_txids.insert(skii->second); } } MDEBUG("Total: " << print_money(spent) << " spent, " << print_money(unspent) << " unspent"); @@ -9724,11 +9812,10 @@ uint64_t wallet2::import_key_images(const std::vector(); // spent txid is unknown, so hypothetically set to random + pd.m_block_height = 0; // spent block height is unknown + const crypto::hash &spent_txid = crypto::null_hash; // spent txid is unknown m_confirmed_txs.insert(std::make_pair(spent_txid, pd)); } } @@ -9823,18 +9910,20 @@ size_t wallet2::import_outputs(const std::vector additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(td.m_tx); + THROW_WALLET_EXCEPTION_IF(td.m_tx.vout[td.m_internal_output_index].target.type() != typeid(cryptonote::txout_to_key), + error::wallet_internal_error, "Unsupported output type"); const crypto::public_key& out_key = boost::get(td.m_tx.vout[td.m_internal_output_index].target).key; bool r = cryptonote::generate_key_image_helper(m_account.get_keys(), m_subaddresses, out_key, tx_pub_key, additional_tx_pub_keys, td.m_internal_output_index, in_ephemeral, td.m_key_image, m_account.get_device()); THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to generate key image"); expand_subaddresses(td.m_subaddr_index); td.m_key_image_known = true; td.m_key_image_partial = false; - THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != boost::get(td.m_tx.vout[td.m_internal_output_index].target).key, + THROW_WALLET_EXCEPTION_IF(in_ephemeral.pub != out_key, error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key at index " + boost::lexical_cast(i)); m_key_images[td.m_key_image] = m_transfers.size(); m_pub_keys[td.get_public_key()] = m_transfers.size(); - m_transfers.push_back(td); + m_transfers.push_back(std::move(td)); } return m_transfers.size(); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 8c141be7e..8e79779a2 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -624,6 +624,7 @@ namespace tools // Subaddress scheme cryptonote::account_public_address get_subaddress(const cryptonote::subaddress_index& index) const; cryptonote::account_public_address get_address() const { return get_subaddress({0,0}); } + boost::optional get_subaddress_index(const cryptonote::account_public_address& address) const; crypto::public_key get_subaddress_spend_public_key(const cryptonote::subaddress_index& index) const; std::vector get_subaddress_spend_public_keys(uint32_t account, uint32_t begin, uint32_t end) const; std::string get_subaddress_as_str(const cryptonote::subaddress_index& index) const; @@ -1137,7 +1138,7 @@ namespace tools bool remove_rings(const cryptonote::transaction_prefix &tx); bool get_ring(const crypto::chacha_key &key, const crypto::key_image &key_image, std::vector &outs); - bool get_output_distribution(uint64_t &start_height, std::vector &distribution); + bool get_rct_distribution(uint64_t &start_height, std::vector &distribution); uint64_t get_segregation_fork_height() const; diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 0c0802048..8c75031a7 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -336,14 +336,20 @@ namespace tools std::map unlocked_balance_per_subaddress = m_wallet->unlocked_balance_per_subaddress(req.account_index); std::vector transfers; m_wallet->get_transfers(transfers); - for (const auto& i : balance_per_subaddress) + std::set address_indices = req.address_indices; + if (address_indices.empty()) + { + for (const auto& i : balance_per_subaddress) + address_indices.insert(i.first); + } + for (uint32_t i : address_indices) { wallet_rpc::COMMAND_RPC_GET_BALANCE::per_subaddress_info info; - info.address_index = i.first; + info.address_index = i; cryptonote::subaddress_index index = {req.account_index, info.address_index}; info.address = m_wallet->get_subaddress_as_str(index); - info.balance = i.second; - info.unlocked_balance = unlocked_balance_per_subaddress[i.first]; + info.balance = balance_per_subaddress[i]; + info.unlocked_balance = unlocked_balance_per_subaddress[i]; info.label = m_wallet->get_subaddress_label(index); info.num_unspent_outputs = std::count_if(transfers.begin(), transfers.end(), [&](const tools::wallet2::transfer_details& td) { return !td.m_spent && td.m_subaddr_index == index; }); res.per_subaddress.push_back(info); @@ -397,6 +403,27 @@ namespace tools return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_getaddress_index(const wallet_rpc::COMMAND_RPC_GET_ADDRESS_INDEX::request& req, wallet_rpc::COMMAND_RPC_GET_ADDRESS_INDEX::response& res, epee::json_rpc::error& er) + { + if (!m_wallet) return not_open(er); + cryptonote::address_parse_info info; + if(!get_account_address_from_str(info, m_wallet->nettype(), req.address)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; + er.message = "Invalid address"; + return false; + } + auto index = m_wallet->get_subaddress_index(info.address); + if (!index) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_ADDRESS; + er.message = "Address doesn't belong to the wallet"; + return false; + } + res.index = *index; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_create_address(const wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::response& res, epee::json_rpc::error& er) { if (!m_wallet) return not_open(er); @@ -1875,8 +1902,8 @@ namespace tools for (std::list>::const_iterator i = payments.begin(); i != payments.end(); ++i) { if (i->second.m_tx_hash == txid) { - fill_transfer_entry(res.transfer, i->second.m_tx_hash, i->first, i->second); - return true; + res.transfers.resize(res.transfers.size() + 1); + fill_transfer_entry(res.transfers.back(), i->second.m_tx_hash, i->first, i->second); } } @@ -1885,8 +1912,8 @@ namespace tools for (std::list>::const_iterator i = payments_out.begin(); i != payments_out.end(); ++i) { if (i->first == txid) { - fill_transfer_entry(res.transfer, i->first, i->second); - return true; + res.transfers.resize(res.transfers.size() + 1); + fill_transfer_entry(res.transfers.back(), i->first, i->second); } } @@ -1895,8 +1922,8 @@ namespace tools for (std::list>::const_iterator i = upayments.begin(); i != upayments.end(); ++i) { if (i->first == txid) { - fill_transfer_entry(res.transfer, i->first, i->second); - return true; + res.transfers.resize(res.transfers.size() + 1); + fill_transfer_entry(res.transfers.back(), i->first, i->second); } } @@ -1907,11 +1934,17 @@ namespace tools for (std::list>::const_iterator i = pool_payments.begin(); i != pool_payments.end(); ++i) { if (i->second.m_pd.m_tx_hash == txid) { - fill_transfer_entry(res.transfer, i->first, i->second); - return true; + res.transfers.resize(res.transfers.size() + 1); + fill_transfer_entry(res.transfers.back(), i->first, i->second); } } + if (!res.transfers.empty()) + { + res.transfer = res.transfers.front(); // backward compat + return true; + } + er.code = WALLET_RPC_ERROR_CODE_WRONG_TXID; er.message = "Transaction not found."; return false; diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 97275758b..04e57db11 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -69,6 +69,7 @@ namespace tools BEGIN_JSON_RPC_MAP("/json_rpc") MAP_JON_RPC_WE("get_balance", on_getbalance, wallet_rpc::COMMAND_RPC_GET_BALANCE) MAP_JON_RPC_WE("get_address", on_getaddress, wallet_rpc::COMMAND_RPC_GET_ADDRESS) + MAP_JON_RPC_WE("get_address_index", on_getaddress_index, wallet_rpc::COMMAND_RPC_GET_ADDRESS_INDEX) MAP_JON_RPC_WE("getbalance", on_getbalance, wallet_rpc::COMMAND_RPC_GET_BALANCE) MAP_JON_RPC_WE("getaddress", on_getaddress, wallet_rpc::COMMAND_RPC_GET_ADDRESS) MAP_JON_RPC_WE("create_address", on_create_address, wallet_rpc::COMMAND_RPC_CREATE_ADDRESS) @@ -139,6 +140,7 @@ namespace tools //json_rpc bool on_getbalance(const wallet_rpc::COMMAND_RPC_GET_BALANCE::request& req, wallet_rpc::COMMAND_RPC_GET_BALANCE::response& res, epee::json_rpc::error& er); bool on_getaddress(const wallet_rpc::COMMAND_RPC_GET_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_GET_ADDRESS::response& res, epee::json_rpc::error& er); + bool on_getaddress_index(const wallet_rpc::COMMAND_RPC_GET_ADDRESS_INDEX::request& req, wallet_rpc::COMMAND_RPC_GET_ADDRESS_INDEX::response& res, epee::json_rpc::error& er); bool on_create_address(const wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::response& res, epee::json_rpc::error& er); bool on_label_address(const wallet_rpc::COMMAND_RPC_LABEL_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_LABEL_ADDRESS::response& res, epee::json_rpc::error& er); bool on_get_accounts(const wallet_rpc::COMMAND_RPC_GET_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_GET_ACCOUNTS::response& res, epee::json_rpc::error& er); diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index f92ddd25f..e64f4a189 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -39,6 +39,17 @@ #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "wallet.rpc" +// When making *any* change here, bump minor +// If the change is incompatible, then bump major and set minor to 0 +// This ensures WALLET_RPC_VERSION always increases, that every change +// has its own version, and that clients can just test major to see +// whether they can talk to a given wallet without having to know in +// advance which version they will stop working with +// Don't go over 32767 for any of these +#define WALLET_RPC_VERSION_MAJOR 1 +#define WALLET_RPC_VERSION_MINOR 4 +#define MAKE_WALLET_RPC_VERSION(major,minor) (((major)<<16)|(minor)) +#define WALLET_RPC_VERSION MAKE_WALLET_RPC_VERSION(WALLET_RPC_VERSION_MAJOR, WALLET_RPC_VERSION_MINOR) namespace tools { namespace wallet_rpc @@ -51,8 +62,10 @@ namespace wallet_rpc struct request { uint32_t account_index; + std::set address_indices; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(account_index) + KV_SERIALIZE(address_indices) END_KV_SERIALIZE_MAP() }; @@ -130,6 +143,25 @@ namespace wallet_rpc }; }; + struct COMMAND_RPC_GET_ADDRESS_INDEX + { + struct request + { + std::string address; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(address) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + cryptonote::subaddress_index index; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(index) + END_KV_SERIALIZE_MAP() + }; + }; + struct COMMAND_RPC_CREATE_ADDRESS { struct request @@ -1264,9 +1296,11 @@ namespace wallet_rpc struct response { transfer_entry transfer; + std::list transfers; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(transfer); + KV_SERIALIZE(transfers); END_KV_SERIALIZE_MAP() }; };