From daa01fd53308034a992fa81c847593c65147aaaf Mon Sep 17 00:00:00 2001 From: sowle Date: Fri, 12 Apr 2019 09:48:33 +0300 Subject: [PATCH] cold-signing is finally working in CLI and via RPC --- contrib/epee/include/console_handler.h | 4 +- src/common/command_line.cpp | 5 +- src/common/command_line.h | 1 + .../currency_protocol_handler.inl | 2 +- src/simplewallet/simplewallet.cpp | 142 ++++++++-- src/simplewallet/simplewallet.h | 3 + src/wallet/wallet2.cpp | 254 +++++++++++------- src/wallet/wallet2.h | 51 ++-- src/wallet/wallet_rpc_server.cpp | 30 ++- src/wallet/wallet_rpc_server.h | 2 +- 10 files changed, 331 insertions(+), 163 deletions(-) diff --git a/contrib/epee/include/console_handler.h b/contrib/epee/include/console_handler.h index ef131696..2dbec160 100644 --- a/contrib/epee/include/console_handler.h +++ b/contrib/epee/include/console_handler.h @@ -422,7 +422,6 @@ namespace epee std::string get_usage() { std::stringstream ss; - ss << "Commands: " << ENDL; size_t max_command_len = 0; for(auto& x:m_command_handlers) if(x.first.size() > max_command_len) @@ -430,8 +429,7 @@ namespace epee for(auto& x:m_command_handlers) { - ss.width(max_command_len + 3); - ss << " " << std::left << x.first << " " << x.second.second << ENDL; + ss << " " << std::left << std::setw(max_command_len + 3) << x.first << " " << x.second.second << ENDL; } return ss.str(); } diff --git a/src/common/command_line.cpp b/src/common/command_line.cpp index c1b90b7b..920423bb 100644 --- a/src/common/command_line.cpp +++ b/src/common/command_line.cpp @@ -16,8 +16,11 @@ namespace command_line const arg_descriptor arg_config_file = { "config-file", "Specify configuration file", std::string(CURRENCY_NAME_SHORT ".conf") }; const arg_descriptor arg_os_version = { "os-version", "" }; + const arg_descriptor arg_log_dir = { "log-dir", "", "", true}; - const arg_descriptor arg_log_level = { "log-level", "", LOG_LEVEL_0, true}; + const arg_descriptor arg_log_file = { "log-file", "", "" }; + const arg_descriptor arg_log_level = { "log-level", "", LOG_LEVEL_0, true }; + const arg_descriptor arg_console = { "no-console", "Disable daemon console commands" }; const arg_descriptor arg_show_details = { "currency-details", "Display currency details" }; const arg_descriptor arg_show_rpc_autodoc = { "show_rpc_autodoc", "Display rpc auto-generated documentation template" }; diff --git a/src/common/command_line.h b/src/common/command_line.h index 152c923f..4d7105c5 100644 --- a/src/common/command_line.h +++ b/src/common/command_line.h @@ -179,6 +179,7 @@ namespace command_line extern const arg_descriptor arg_config_file; extern const arg_descriptor arg_os_version; extern const arg_descriptor arg_log_dir; + extern const arg_descriptor arg_log_file; extern const arg_descriptor arg_log_level; extern const arg_descriptor arg_console; extern const arg_descriptor arg_show_details; diff --git a/src/currency_protocol/currency_protocol_handler.inl b/src/currency_protocol/currency_protocol_handler.inl index 0485525f..b281e5c3 100644 --- a/src/currency_protocol/currency_protocol_handler.inl +++ b/src/currency_protocol/currency_protocol_handler.inl @@ -235,7 +235,7 @@ namespace currency if (it != m_blocks_id_que.end()) { //already have this block handler in que - LOG_PRINT("Block " << block_id << " already in processing que", LOG_LEVEL_2); + LOG_PRINT("Block " << block_id << " already in processing que", LOG_LEVEL_3); return 1; } else diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index f86dd2c6..8618a723 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -156,10 +156,7 @@ std::string simple_wallet::get_commands_str() { std::stringstream ss; ss << "Commands: " << ENDL; - std::string usage = m_cmd_binder.get_usage(); - boost::replace_all(usage, "\n", "\n "); - usage.insert(0, " "); - ss << usage << ENDL; + ss << m_cmd_binder.get_usage() << ENDL; return ss.str(); } @@ -186,6 +183,7 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("incoming_counts", boost::bind(&simple_wallet::show_incoming_transfers_counts, this, _1), "incoming_transfers counts"); m_cmd_binder.set_handler("list_recent_transfers", boost::bind(&simple_wallet::list_recent_transfers, this, _1), "list_recent_transfers - Show recent maximum 1000 transfers"); m_cmd_binder.set_handler("list_recent_transfers_ex", boost::bind(&simple_wallet::list_recent_transfers_ex, this, _1), "list_recent_transfers_tx - Write recent transfer in json to wallet_recent_transfers.txt"); + m_cmd_binder.set_handler("list_outputs", boost::bind(&simple_wallet::list_outputs, this, _1), "list_outputs [spent|unspent] - Lists all the outputs that have ever been sent to this wallet if called without arguments, otherwise it lists only the spent or unspent outputs"); m_cmd_binder.set_handler("dump_transfers", boost::bind(&simple_wallet::dump_trunsfers, this, _1), "dump_transfers - Write transfers in json to dump_transfers.txt"); m_cmd_binder.set_handler("dump_keyimages", boost::bind(&simple_wallet::dump_key_images, this, _1), "dump_keyimages - Write key_images in json to dump_key_images.txt"); m_cmd_binder.set_handler("payments", boost::bind(&simple_wallet::show_payments, this, _1), "payments [ ... ] - Show payments , ... "); @@ -206,7 +204,9 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("integrated_address", boost::bind(&simple_wallet::integrated_address, this, _1), "integrated_address [|"); - m_cmd_binder.set_handler("save_watch_only", boost::bind(&simple_wallet::save_watch_only, this, _1), "Save a watch-only keys file ."); + m_cmd_binder.set_handler("save_watch_only", boost::bind(&simple_wallet::save_watch_only, this, _1), "save_watch_only - save as watch-only wallet file."); + m_cmd_binder.set_handler("sign_transfer", boost::bind(&simple_wallet::sign_transfer, this, _1), "sign_transfer - sign unsigned tx from a watch-only wallet"); + m_cmd_binder.set_handler("submit_transfer", boost::bind(&simple_wallet::submit_transfer, this, _1), "submit_transfer - broadcast signed tx"); } //---------------------------------------------------------------------------------------------------- @@ -391,8 +391,8 @@ bool simple_wallet::open_wallet(const string &wallet_file, const std::string& pa try { m_wallet->load(epee::string_encoding::convert_to_unicode(m_wallet_file), password); - message_writer(epee::log_space::console_color_white, true) << "Opened wallet: " << m_wallet->get_account().get_public_address_str(); - + message_writer(epee::log_space::console_color_white, true) << "Opened" << (m_wallet->is_watch_only() ? " watch-only" : "") << " wallet: " << m_wallet->get_account().get_public_address_str(); + if (m_print_brain_wallet) std::cout << "Brain wallet: " << m_wallet->get_account().get_restore_braindata() << std::endl << std::flush; @@ -541,6 +541,12 @@ void simple_wallet::on_money_spent(uint64_t height, const currency::transaction& //---------------------------------------------------------------------------------------------------- bool simple_wallet::refresh(const std::vector& args) { + if (m_offline_mode) + { + success_msg_writer() << "refresh is meaningless in OFFLINE MODE"; + return true; + } + if (!try_connect_to_daemon()) return true; @@ -1079,7 +1085,11 @@ bool simple_wallet::transfer(const std::vector &args_) currency::transaction tx; std::vector extra; m_wallet->transfer(dsts, fake_outs_count, 0, m_wallet->get_core_runtime_config().tx_default_fee, extra, attachments, tx); - success_msg_writer(true) << "Money successfully sent, transaction " << get_transaction_hash(tx) << ", " << get_object_blobsize(tx) << " bytes"; + + if (!m_wallet->is_watch_only()) + success_msg_writer(true) << "Money successfully sent, transaction " << get_transaction_hash(tx) << ", " << get_object_blobsize(tx) << " bytes"; + else + success_msg_writer(true) << "Transaction prepared for signing and saved into \"zano_tx_unsigned\" file, use full wallet to sign transfer and then use \"submit_transfer\" on this wallet to broadcast the transaction to the network"; } catch (const tools::error::daemon_busy&) { @@ -1162,7 +1172,12 @@ bool simple_wallet::transfer(const std::vector &args_) bool simple_wallet::run() { std::string addr_start = m_wallet->get_account().get_public_address_str().substr(0, 6); - return m_cmd_binder.run_handling("[" CURRENCY_NAME_BASE " wallet " + addr_start + "]: ", ""); + std::string prompt; + if (m_wallet->is_watch_only()) + prompt = "[" CURRENCY_NAME_BASE " WO wallet " + addr_start + "]: "; + else + prompt = "[" CURRENCY_NAME_BASE " wallet " + addr_start + "]: "; + return m_cmd_binder.run_handling(prompt, ""); } //---------------------------------------------------------------------------------------------------- void simple_wallet::stop() @@ -1290,7 +1305,7 @@ bool simple_wallet::save_watch_only(const std::vector &args) } try { - m_wallet->store(epee::string_encoding::convert_to_unicode(args[0]), args[1], true); + m_wallet->store_watch_only(epee::string_encoding::convert_to_unicode(args[0]), args[1]); success_msg_writer() << "Watch-only wallet has been stored to " << args[0]; } catch (const std::exception& e) @@ -1306,6 +1321,92 @@ bool simple_wallet::save_watch_only(const std::vector &args) return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::list_outputs(const std::vector &args) +{ + if (args.size() > 1) + { + fail_msg_writer() << "invalid syntax: one or none parameters are expected, " << args.size() << " was given"; + return true; + } + + bool include_spent = true, include_unspent = true; + if (args.size() == 1) + { + if (args[0] == "unspent" || args[0] == "available") + include_spent = false; + else if (args[0] == "spent" || args[0] == "unavailable") + include_unspent = false; + else + { + fail_msg_writer() << "invalid parameter: " << args[0]; + return true; + } + } + + success_msg_writer() << "list of all the outputs that have ever been sent to this wallet:" << ENDL << + m_wallet->get_transfers_str(include_spent, include_unspent); + return true; +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::sign_transfer(const std::vector &args) +{ + if (m_wallet->is_watch_only()) + { + fail_msg_writer() << "You can't sign transaction in watch-only wallet"; + return true; + + } + + if (args.size() < 2) + { + fail_msg_writer() << "wrong parameters, expected: "; + return true; + } + try + { + currency::transaction res_tx; + m_wallet->sign_transfer_files(args[0], args[1], res_tx); + success_msg_writer(true) << "transaction signed and stored to file: " << args[1] << ", transaction " << get_transaction_hash(res_tx) << ", " << get_object_blobsize(res_tx) << " bytes"; + } + catch (const std::exception& e) + { + LOG_ERROR("unexpected error: " << e.what()); + fail_msg_writer() << "unexpected error: " << e.what(); + } + catch (...) + { + LOG_ERROR("Unknown error"); + fail_msg_writer() << "unknown error"; + } + return true; +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::submit_transfer(const std::vector &args) +{ + if (args.size() < 1) + { + fail_msg_writer() << "wrong parameters, expected filename"; + return true; + } + try + { + currency::transaction res_tx; + m_wallet->submit_transfer_files(args[0], res_tx); + success_msg_writer(true) << "transaction " << get_transaction_hash(res_tx) << " was successfully sent, size: " << get_object_blobsize(res_tx) << " bytes"; + } + catch (const std::exception& e) + { + LOG_ERROR("unexpected error: " << e.what()); + fail_msg_writer() << "unexpected error: " << e.what(); + } + catch (...) + { + LOG_ERROR("Unknown error"); + fail_msg_writer() << "unknown error"; + } + return true; +} +//---------------------------------------------------------------------------------------------------- int main(int argc, char* argv[]) { #ifdef WIN32 @@ -1341,6 +1442,8 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, arg_print_brain_wallet); command_line::add_arg(desc_params, arg_do_pos_mining); command_line::add_arg(desc_params, arg_offline_mode); + command_line::add_arg(desc_params, command_line::arg_log_file); + command_line::add_arg(desc_params, command_line::arg_log_level); tools::wallet_rpc_server::init_options(desc_params); @@ -1378,17 +1481,24 @@ int main(int argc, char* argv[]) //set up logging options log_space::get_set_log_detalisation_level(true, LOG_LEVEL_2); - log_space::log_singletone::add_logger(LOGGER_FILE, - log_space::log_singletone::get_default_log_file().c_str(), - log_space::log_singletone::get_default_log_folder().c_str(), LOG_LEVEL_4); - + boost::filesystem::path log_file_path(command_line::get_arg(vm, command_line::arg_log_file)); + if (log_file_path.empty()) + log_file_path = log_space::log_singletone::get_default_log_file(); + std::string log_dir; + log_dir = log_file_path.has_parent_path() ? log_file_path.parent_path().string() : log_space::log_singletone::get_default_log_folder(); + log_space::log_singletone::add_logger(LOGGER_FILE, log_file_path.filename().string().c_str(), log_dir.c_str(), LOG_LEVEL_4); message_writer(epee::log_space::console_color_white, true) << CURRENCY_NAME << " wallet v" << PROJECT_VERSION_LONG; - if(command_line::has_arg(vm, arg_log_level)) + if (command_line::has_arg(vm, arg_log_level)) { LOG_PRINT_L0("Setting log level = " << command_line::get_arg(vm, arg_log_level)); log_space::get_set_log_detalisation_level(true, command_line::get_arg(vm, arg_log_level)); } + if (command_line::has_arg(vm, command_line::arg_log_level)) + { + LOG_PRINT_L0("Setting log level = " << command_line::get_arg(vm, command_line::arg_log_level)); + log_space::get_set_log_detalisation_level(true, command_line::get_arg(vm, command_line::arg_log_level)); + } bool offline_mode = command_line::get_arg(vm, arg_offline_mode); @@ -1479,7 +1589,7 @@ int main(int argc, char* argv[]) wrpc.send_stop_signal(); }); LOG_PRINT_L0("Starting wallet rpc server"); - wrpc.run(command_line::get_arg(vm, arg_do_pos_mining) ); + wrpc.run(command_line::get_arg(vm, arg_do_pos_mining), offline_mode); LOG_PRINT_L0("Stopped wallet rpc server"); try { diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 30c76618..f8096249 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -58,6 +58,7 @@ namespace currency bool dump_key_images(const std::vector& args); bool show_incoming_transfers(const std::vector &args); bool show_incoming_transfers_counts(const std::vector &args); + bool list_outputs(const std::vector &args); bool show_payments(const std::vector &args); bool get_transfer_info(const std::vector &args); bool scan_for_key_image_collisions(const std::vector &args); @@ -75,6 +76,8 @@ namespace currency bool integrated_address(const std::vector &args); bool get_tx_key(const std::vector &args_); bool save_watch_only(const std::vector &args); + bool sign_transfer(const std::vector &args); + bool submit_transfer(const std::vector &args); bool get_alias_from_daemon(const std::string& alias_name, currency::extra_alias_entry_base& ai); bool get_transfer_address(const std::string& adr_str, currency::account_public_address& addr); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index cde719ec..49cd5241 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -52,6 +52,22 @@ void wallet2::fill_transfer_details(const currency::transaction& tx, const tools } } //---------------------------------------------------------------------------------------------------- +std::string wallet2::transfer_flags_to_str(uint32_t flags) +{ + std::string result(5, ' '); + if (flags & WALLET_TRANSFER_DETAIL_FLAG_SPENT) + result[0] = 's'; + if (flags & WALLET_TRANSFER_DETAIL_FLAG_BLOCKED) + result[1] = 'b'; + if (flags & WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION) + result[2] = 'e'; + if (flags & WALLET_TRANSFER_DETAIL_FLAG_MINED_TRANSFER) + result[3] = 'm'; + if (flags & WALLET_TRANSFER_DETAIL_FLAG_COLD_SIG_RESERVATION) + result[4] = 'c'; + return result; +} +//---------------------------------------------------------------------------------------------------- std::string wallet2::transform_tx_to_str(const currency::transaction& tx) { return currency::obj_to_json_str(tx); @@ -327,7 +343,6 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t td.m_internal_output_index = o; td.m_key_image = ki; td.m_global_output_index = res.o_indexes[o]; - td.m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_SPENT); if (coin_base_tx) { //last out in coinbase tx supposed to be change from coinstake @@ -349,7 +364,6 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t transfer_details_base& tdb = m_multisig_transfers[multisig_id]; tdb.m_ptx_wallet_info = pwallet_info; tdb.m_internal_output_index = o; - tdb.m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_SPENT); WLT_LOG_L0("Received multisig, multisig out id: " << multisig_id << ", amount: " << tdb.amount() << ", with tx: " << get_transaction_hash(tx)); } } @@ -1823,7 +1837,7 @@ void wallet2::load_keys2ki(bool create_if_not_exist, bool& need_to_resync) { WLT_LOG_RED("m_pending_key_images size: " << m_pending_key_images.size() << " is GREATER than m_pending_key_images_file_container size: " << m_pending_key_images_file_container.size(), LOG_LEVEL_0); WLT_LOG_RED("UNRECOVERABLE ERROR, wallet stops", LOG_LEVEL_0); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(false, "m_pending_key_images > m_pending_key_images_file_container"); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(false, "UNRECOVERABLE ERROR, wallet stops: m_pending_key_images > m_pending_key_images_file_container"); } } //---------------------------------------------------------------------------------------------------- @@ -1958,20 +1972,18 @@ void wallet2::load(const std::wstring& wallet_, const std::string& password) THROW_IF_TRUE_WALLET_EX(need_to_resync, error::wallet_load_notice_wallet_restored, epee::string_encoding::convert_to_ansii(m_wallet_file)); WLT_LOG_L0("Loaded wallet file" << (m_watch_only ? " (WATCH ONLY) " : " ") << string_encoding::convert_to_ansii(m_wallet_file) << " with public address: " << m_account.get_public_address_str()); - WLT_LOG_L0("(pending_key_images: " << m_pending_key_images.size() << ", pki file elements: " << m_pending_key_images_file_container.size() << ", tx_keys: " << m_tx_keys.size() << ")"); + WLT_LOG_L0("(after loading: pending_key_images: " << m_pending_key_images.size() << ", pki file elements: " << m_pending_key_images_file_container.size() << ", tx_keys: " << m_tx_keys.size() << ")"); } //---------------------------------------------------------------------------------------------------- void wallet2::store() { - store(m_wallet_file, m_password, false); + store(m_wallet_file, m_password); } //---------------------------------------------------------------------------------------------------- -void wallet2::store(const std::wstring& path_to_save, const std::string& password, bool store_as_watch_only) +void wallet2::store(const std::wstring& path_to_save, const std::string& password) { LOG_PRINT_L0("(before storing: pending_key_images: " << m_pending_key_images.size() << ", pki file elements: " << m_pending_key_images_file_container.size() << ", tx_keys: " << m_tx_keys.size() << ")"); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(!(store_as_watch_only && path_to_save == m_wallet_file), "ttrying to save watch-only wallet to the same wallet file!"); - //prepare data std::string keys_buff; bool r = store_keys(keys_buff, password); @@ -1996,22 +2008,58 @@ void wallet2::store(const std::wstring& path_to_save, const std::string& passwor data_file << header_buff << keys_buff; WLT_LOG_L0("Storing to file..."); - if (store_as_watch_only) - { - // TODO - r = tools::portble_serialize_obj_to_stream(*this, data_file); - CHECK_AND_ASSERT_THROW_MES(r, "failed to portble_serialize_obj_to_stream for wallet " << epee::string_encoding::convert_to_ansii(m_wallet_file)); - } - else - { - r = tools::portble_serialize_obj_to_stream(*this, data_file); - CHECK_AND_ASSERT_THROW_MES(r, "failed to portble_serialize_obj_to_stream for wallet " << epee::string_encoding::convert_to_ansii(m_wallet_file)); - } + r = tools::portble_serialize_obj_to_stream(*this, data_file); + CHECK_AND_ASSERT_THROW_MES(r, "failed to portble_serialize_obj_to_stream for wallet " << epee::string_encoding::convert_to_ansii(m_wallet_file)); data_file.flush(); data_file.close(); } //---------------------------------------------------------------------------------------------------- +void wallet2::store_watch_only(const std::wstring& path_to_save, const std::string& password) const +{ + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(path_to_save != m_wallet_file, "trying to save watch-only wallet to the same wallet file!"); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(!m_watch_only, "saving watch-only wallet into a watch-only wallet is not allowed"); + + // prepare data for watch-only wallet + wallet2 wo; + // wallet2 wo(*this); copy-constructor is not working, so do a this serialization workaround + std::stringstream stream_buffer; + tools::portble_serialize_obj_to_stream(*this, stream_buffer); + tools::portable_unserialize_obj_from_stream(wo, stream_buffer); + + wo.m_watch_only = true; + wo.m_account = m_account; + wo.m_account.make_account_watch_only(); + wo.prepare_file_names(path_to_save); + + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(!boost::filesystem::exists(wo.m_wallet_file), "file " << epee::string_encoding::convert_to_ansii(wo.m_wallet_file) << " already exists"); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(!boost::filesystem::exists(wo.m_pending_ki_file), "file " << epee::string_encoding::convert_to_ansii(wo.m_pending_ki_file) << " already exists"); + + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(wo.m_pending_key_images.empty(), "pending key images is expected to be empty"); + bool stub = false; + wo.load_keys2ki(true, stub); // to create outkey2ki file + + // populate pending key images for spent outputs (this will help to resync watch-only wallet) + for (size_t ti = 0; ti < wo.m_transfers.size(); ++ti) + { + const auto& td = wo.m_transfers[ti]; + if (!td.is_spent()) + continue; // only spent transfers really need to be stored, because watch-only wallet will not be able to figure out they were spent otherwise + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.m_internal_output_index < td.m_ptx_wallet_info->m_tx.vout.size(), "invalid transfer #" << ti); + const currency::txout_target_v& out_t = td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].target; + if (out_t.type() != typeid(currency::txout_to_key)) + continue; + const crypto::public_key& out_key = boost::get(out_t).key; + wo.m_pending_key_images.insert(std::make_pair(out_key, td.m_key_image)); + wo.m_pending_key_images_file_container.push_back(tools::out_key_to_ki(out_key, td.m_key_image)); + WLT_LOG_L1("preparing watch-only wallet: added pending ki (" << out_key << ", " << td.m_key_image << ")"); + } + + // TODO additional clearing for watch-only wallet's data + + wo.store(path_to_save, password); +} +//---------------------------------------------------------------------------------------------------- uint64_t wallet2::unlocked_balance() const { uint64_t stub = 0; @@ -2044,7 +2092,7 @@ uint64_t wallet2::balance(uint64_t& unloked, uint64_t& awaiting_in, uint64_t& aw for(auto& td : m_transfers) { - if (!td.is_spent()) + if (td.is_spendable()) { balance_total += td.amount(); if (is_transfer_unlocked(td)) @@ -2100,18 +2148,6 @@ uint64_t wallet2::balance(uint64_t& unloked, uint64_t& awaiting_in, uint64_t& aw //---------------------------------------------------------------------------------------------------- uint64_t wallet2::balance() const { -// uint64_t amount = 0; -// BOOST_FOREACH(auto& td, m_transfers) -// if(!td.m_spent) -// amount += td.amount(); -// -// -// BOOST_FOREACH(auto& utx, m_unconfirmed_txs) -// if (utx.second.is_income) -// amount+= utx.second.amount; -// -// return amount; - uint64_t stub = 0; return balance(stub, stub, stub, stub); } @@ -2121,6 +2157,37 @@ void wallet2::get_transfers(wallet2::transfer_container& incoming_transfers) con incoming_transfers = m_transfers; } //---------------------------------------------------------------------------------------------------- +std::string wallet2::get_transfers_str(bool include_spent /*= true*/, bool include_unspent /*= true*/) const +{ + static const char* header = "index amount g_index flags block tx out# key image"; + std::stringstream ss; + ss << header << ENDL; + size_t count = 0; + for (size_t i = 0; i != m_transfers.size(); ++i) + { + const transfer_details& td = m_transfers[i]; + + if ((td.is_spent() && !include_spent) || (!td.is_spent() && !include_unspent)) + continue; + + ss << std::right << + std::setw(5) << i << " " << + std::setw(21) << print_money(td.amount()) << " " << + std::setw(7) << td.m_global_output_index << " " << + std::setw(2) << std::setfill('0') << td.m_flags << std::setfill(' ') << ":" << + std::setw(5) << transfer_flags_to_str(td.m_flags) << " " << + std::setw(7) << td.m_ptx_wallet_info->m_block_height << " " << + get_transaction_hash(td.m_ptx_wallet_info->m_tx) << " " << + std::setw(4) << td.m_internal_output_index << " " << + td.m_key_image << ENDL; + + ++count; + } + + ss << "printed " << count << " outputs of " << m_transfers.size() << " total" << ENDL; + return ss.str(); +} +//---------------------------------------------------------------------------------------------------- void wallet2::get_payments(const std::string& payment_id, std::list& payments, uint64_t min_height) const { auto range = m_payments.equal_range(payment_id); @@ -2141,16 +2208,14 @@ void wallet2::sign_transfer(const std::string& tx_sources_blob, std::string& sig // decrypt the blob std::string decrypted_src_blob = crypto::chacha_crypt(tx_sources_blob, m_account.get_keys().m_view_secret_key); - // deserialize create_tx_arg - construct_tx_param ctp = AUTO_VAL_INIT(ctp); - bool r = t_unserializable_object_from_blob(ctp, decrypted_src_blob); + // deserialize args + finalized_tx ft = AUTO_VAL_INIT(ft); + bool r = t_unserializable_object_from_blob(ft.ftp, decrypted_src_blob); THROW_IF_FALSE_WALLET_EX(r, error::wallet_common_error, "Failed to decrypt tx sources blob"); // make sure unsigned tx was created with the same keys - THROW_IF_FALSE_WALLET_EX(ctp.spend_pub_key == m_account.get_keys().m_account_address.m_spend_public_key, error::wallet_common_error, "The tx sources was created in a different wallet, keys missmatch"); + THROW_IF_FALSE_WALLET_EX(ft.ftp.spend_pub_key == m_account.get_keys().m_account_address.m_spend_public_key, error::wallet_common_error, "The was created in a different wallet, keys missmatch"); - finalized_tx ft = AUTO_VAL_INIT(ft); - prepare_transaction(ctp, ft.ftp); finalize_transaction(ft.ftp, ft.tx, ft.one_time_key, false); // calculate key images for each change output @@ -2186,19 +2251,21 @@ void wallet2::sign_transfer(const std::string& tx_sources_blob, std::string& sig // serialize and encrypt the result signed_tx_blob = t_serializable_object_to_blob(ft); crypto::chacha_crypt(signed_tx_blob, m_account.get_keys().m_view_secret_key); + + tx = ft.tx; } //---------------------------------------------------------------------------------------------------- void wallet2::sign_transfer_files(const std::string& tx_sources_file, const std::string& signed_tx_file, currency::transaction& tx) { std::string sources_blob; bool r = epee::file_io_utils::load_file_to_string(tx_sources_file, sources_blob); - THROW_IF_FALSE_WALLET_EX(r, error::wallet_common_error, std::string("failed to open file ") + tx_sources_file); + THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "failed to open file " << tx_sources_file); std::string signed_tx_blob; sign_transfer(sources_blob, signed_tx_blob, tx); r = epee::file_io_utils::save_string_to_file(signed_tx_file, signed_tx_blob); - THROW_IF_FALSE_WALLET_EX(r, error::wallet_common_error, std::string("failed to store signed tx to file ") + signed_tx_file); + THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "failed to store signed tx to file " << signed_tx_file); } //---------------------------------------------------------------------------------------------------- void wallet2::submit_transfer(const std::string& signed_tx_blob, currency::transaction& tx) @@ -2209,22 +2276,26 @@ void wallet2::submit_transfer(const std::string& signed_tx_blob, currency::trans // deserialize tx data finalized_tx ft = AUTO_VAL_INIT(ft); bool r = t_unserializable_object_from_blob(ft, decrypted_src_blob); - THROW_IF_FALSE_WALLET_EX(r, error::wallet_common_error, "Failed to decrypt tx sources blob"); + THROW_IF_FALSE_WALLET_EX(r, error::wallet_common_error, "Failed to decrypt signed tx data"); + tx = ft.tx; + crypto::hash tx_hash = get_transaction_hash(tx); - crypto::hash tx_hash = get_transaction_hash(ft.tx); + // foolproof + THROW_IF_FALSE_WALLET_CMN_ERR_EX(ft.ftp.spend_pub_key == m_account.get_keys().m_account_address.m_spend_public_key, "The given tx was created in a different wallet, keys missmatch, tx hash: " << tx_hash); try { - send_transaction_to_network(ft.tx); + send_transaction_to_network(tx); } catch (...) { - // clear spent transfers if smth went wrong - clear_transfers_from_flag(ft.ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, "broadcasting tx " + epee::string_tools::pod_to_hex(tx_hash) + " was unsuccessful"); + // clear transfers flags if smth went wrong + uint32_t flag = WALLET_TRANSFER_DETAIL_FLAG_SPENT | WALLET_TRANSFER_DETAIL_FLAG_COLD_SIG_RESERVATION; + clear_transfers_from_flag(ft.ftp.selected_transfers, flag, "broadcasting tx " + epee::string_tools::pod_to_hex(tx_hash) + " was unsuccessful"); throw; } - add_sent_tx_detailed_info(ft.tx, ft.ftp.prepared_destinations, ft.ftp.selected_transfers); + add_sent_tx_detailed_info(tx, ft.ftp.prepared_destinations, ft.ftp.selected_transfers); m_tx_keys.insert(std::make_pair(tx_hash, ft.one_time_key)); if (m_watch_only) @@ -2286,7 +2357,7 @@ void wallet2::submit_transfer(const std::string& signed_tx_blob, currency::trans } // TODO: print inputs' key images - print_tx_sent_message(ft.tx, "(from submit_transfer)"); + print_tx_sent_message(tx, "(from submit_transfer)"); } //---------------------------------------------------------------------------------------------------- void wallet2::submit_transfer_files(const std::string& signed_tx_file, currency::transaction& tx) @@ -2323,7 +2394,7 @@ bool wallet2::get_transfer_address(const std::string& adr_str, currency::account //---------------------------------------------------------------------------------------------------- bool wallet2::is_transfer_okay_for_pos(const transfer_details& tr) { - if (tr.m_flags & WALLET_TRANSFER_DETAIL_FLAG_SPENT) + if (!tr.is_spendable()) return false; //blockchain conditions @@ -3339,11 +3410,19 @@ void wallet2::add_sent_tx_detailed_info(const transaction& tx, add_sent_unconfirmed_tx(tx, recipients, selected_transfers, destinations); } //---------------------------------------------------------------------------------------------------- -void wallet2::mark_transfers_with_flag(const std::vector& selected_transfers, uint32_t flag, const std::string& reason /* = empty_string */) +void wallet2::mark_transfers_with_flag(const std::vector& selected_transfers, uint32_t flag, const std::string& reason /* = empty_string */, bool throw_if_flag_already_set /* = false */) { + if (throw_if_flag_already_set) + { + for (uint64_t i : selected_transfers) + { + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(i < m_transfers.size(), "invalid transfer index given: " << i << ", m_transfers.size() == " << m_transfers.size()); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX((m_transfers[i].m_flags & flag) == 0, "transfer #" << i << " already has flag " << flag << ": " << m_transfers[i].m_flags << ", transfer info:" << ENDL << epee::serialization::store_t_to_json(m_transfers[i])); + } + } for (uint64_t i : selected_transfers) { - THROW_IF_TRUE_WALLET_EX(i >= m_transfers.size(), error::wallet_internal_error, "i >= m_transfers.size()"); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(i < m_transfers.size(), "invalid transfer index given: " << i << ", m_transfers.size() == " << m_transfers.size()); uint32_t flags_before = m_transfers[i].m_flags; m_transfers[i].m_flags |= flag; WLT_LOG_L1("marking transfer #" << std::setfill('0') << std::right << std::setw(3) << i << " with flag " << flag << " : " << flags_before << " -> " << m_transfers[i].m_flags << @@ -3368,6 +3447,12 @@ void wallet2::exception_handler() m_found_free_amounts.clear(); } //---------------------------------------------------------------------------------------------------- +void wallet2::exception_handler() const +{ + // do nothing + // TODO: is it correct? +} +//---------------------------------------------------------------------------------------------------- void wallet2::mark_transfers_as_spent(const std::vector& selected_transfers, const std::string& reason /* = empty_string */) { // TODO: design a safe undo for this operation @@ -3535,12 +3620,13 @@ bool wallet2::is_transfer_ready_to_go(const transfer_details& td, uint64_t fake_ //---------------------------------------------------------------------------------------------------- bool wallet2::is_transfer_able_to_go(const transfer_details& td, uint64_t fake_outputs_count) { - if (!static_cast(td.m_flags&WALLET_TRANSFER_DETAIL_FLAG_SPENT) - && currency::is_mixattr_applicable_for_fake_outs_counter(boost::get(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].target).mix_attr, fake_outputs_count)) - { - return true; - } - return false; + if (!td.is_spendable()) + return false; + + if (!currency::is_mixattr_applicable_for_fake_outs_counter(boost::get(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].target).mix_attr, fake_outputs_count)) + return false; + + return true; } //---------------------------------------------------------------------------------------------------- bool wallet2::prepare_free_transfers_cache(uint64_t fake_outputs_count) @@ -3898,22 +3984,6 @@ void wallet2::transfer(const std::vector& dsts, transfer(dsts, fake_outputs_count, unlock_time, fee, extra, attachments, destination_split_strategy_id, dust_policy, tx); } //---------------------------------------------------------------------------------------------------- -/* -tx workflow: - -prepare_transaction(construct_param, construct_res, tools::detail::digit_split_strategy); -mark_transfers_as_spent(construct_res.selected_transfers, std::string("contract <") + epee::string_tools::pod_to_hex(contract_id) + "> has been accepted with tx <" + epee::string_tools::pod_to_hex(get_transaction_hash(construct_res.tx)) + ">"); --- -store/load --- -finalize_transaction() - construct_tx - sign ms input - send_transaction_to_network(construct_res.tx); - add_sent_tx_detailed_info(construct_res.tx, construct_res.prepared_destinations, construct_res.selected_transfers); -print_tx_sent_message(construct_res.tx, "(contract)", construct_param.fee); -*/ -//---------------------------------------------------------------------------------------------------- void wallet2::transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, @@ -3930,34 +4000,34 @@ void wallet2::transfer(const std::vector& dsts, std::string* p_signed_tx_blob_str) { TIME_MEASURE_START(precalculation_time); - construct_tx_param tx_param = AUTO_VAL_INIT(tx_param); - tx_param.attachments = attachments; - tx_param.crypt_address = currency::get_crypt_address_from_destinations(m_account.get_keys(), dsts); - tx_param.dsts = dsts; - tx_param.dust_policy = dust_policy; - tx_param.extra = extra; - tx_param.fake_outputs_count = fake_outputs_count; - tx_param.fee = fee; - tx_param.flags = flags; - // tx_param.mark_tx_as_complete - // tx_param.multisig_id - tx_param.shuffle = shuffle; - tx_param.split_strategy_id = destination_split_strategy_id; - tx_param.tx_outs_attr = tx_outs_attr; - tx_param.unlock_time = unlock_time; + construct_tx_param ctp = AUTO_VAL_INIT(ctp); + ctp.attachments = attachments; + ctp.crypt_address = currency::get_crypt_address_from_destinations(m_account.get_keys(), dsts); + ctp.dsts = dsts; + ctp.dust_policy = dust_policy; + ctp.extra = extra; + ctp.fake_outputs_count = fake_outputs_count; + ctp.fee = fee; + ctp.flags = flags; + // ctp.mark_tx_as_complete + // ctp.multisig_id + ctp.shuffle = shuffle; + ctp.split_strategy_id = destination_split_strategy_id; + ctp.tx_outs_attr = tx_outs_attr; + ctp.unlock_time = unlock_time; TIME_MEASURE_FINISH(precalculation_time); TIME_MEASURE_START(prepare_transaction_time); finalize_tx_param ftp = AUTO_VAL_INIT(ftp); - prepare_transaction(tx_param, ftp); + prepare_transaction(ctp, ftp); TIME_MEASURE_FINISH(prepare_transaction_time); if (m_watch_only) { - tx_param.spend_pub_key = m_account.get_public_address().m_spend_public_key; - blobdata bl = t_serializable_object_to_blob(tx_param); + ftp.spend_pub_key = m_account.get_public_address().m_spend_public_key; + blobdata bl = t_serializable_object_to_blob(ftp); crypto::chacha_crypt(bl, m_account.get_keys().m_view_secret_key); - epee::file_io_utils::save_string_to_file("unsigned_zano_tx", bl); + epee::file_io_utils::save_string_to_file("zano_tx_unsigned", bl); LOG_PRINT_L0("Transaction stored to unsigned_zano_tx. You need to sign this tx using a full-access wallet."); if (p_signed_tx_blob_str != nullptr) @@ -3965,7 +4035,7 @@ void wallet2::transfer(const std::vector& dsts, // unlock transfers at the very end TIME_MEASURE_START(mark_transfers_as_spent_time); - mark_transfers_as_spent(ftp.selected_transfers, std::string("money transfer")); + mark_transfers_with_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_COLD_SIG_RESERVATION, std::string("cold sig reservation for money transfer"), true); TIME_MEASURE_FINISH(mark_transfers_as_spent_time); return; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index d33a393e..0664f08c 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -254,37 +254,10 @@ namespace tools currency::account_public_address crypt_address; uint8_t tx_outs_attr; bool shuffle; - - crypto::public_key spend_pub_key; // only for validations - - BEGIN_SERIALIZE_OBJECT() - FIELD(dsts) - FIELD(fake_outputs_count) - FIELD(fee) - FIELD(dust_policy) - FIELD(multisig_id) - FIELD(flags) - FIELD(split_strategy_id) - FIELD(mark_tx_as_complete) - FIELD(unlock_time) - FIELD(extra) - FIELD(attachments) - FIELD(crypt_address) - FIELD(tx_outs_attr) - FIELD(shuffle) - FIELD(spend_pub_key) - END_SERIALIZE() }; struct finalize_tx_param { - //std::vector dsts; - //size_t fake_outputs_count; - //uint64_t fee; - //tx_dust_policy dust_policy; - //bool mark_tx_as_complete; - //detail::split_strategy_id_t split_strategy_id; - uint64_t unlock_time; std::vector extra; std::vector attachments; @@ -354,11 +327,13 @@ namespace tools m_core_runtime_config = currency::get_default_core_runtime_config(); }; -#define WALLET_TRANSFER_DETAIL_FLAG_SPENT 0x00000001 -#define WALLET_TRANSFER_DETAIL_FLAG_BLOCKED 0x00000002 -#define WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION 0x00000004 -#define WALLET_TRANSFER_DETAIL_FLAG_MINED_TRANSFER 0x00000008 - +#define WALLET_TRANSFER_DETAIL_FLAG_SPENT uint32_t(1 << 0) +#define WALLET_TRANSFER_DETAIL_FLAG_BLOCKED uint32_t(1 << 1) +#define WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION uint32_t(1 << 2) +#define WALLET_TRANSFER_DETAIL_FLAG_MINED_TRANSFER uint32_t(1 << 3) +#define WALLET_TRANSFER_DETAIL_FLAG_COLD_SIG_RESERVATION uint32_t(1 << 4) // transfer is reserved for cold-signing (unsigned tx was created and passed for signing) + + static std::string transfer_flags_to_str(uint32_t flags); static std::string transform_tx_to_str(const currency::transaction& tx); static currency::transaction transform_str_to_tx(const std::string& tx_str); @@ -393,9 +368,9 @@ namespace tools uint64_t amount() const { return m_ptx_wallet_info->m_tx.vout[m_internal_output_index].amount; } bool is_spent() const { return m_flags & WALLET_TRANSFER_DETAIL_FLAG_SPENT; } + bool is_spendable() const { return (m_flags & (~WALLET_TRANSFER_DETAIL_FLAG_MINED_TRANSFER)) == 0; } // spenable = has no flags or mined flag only BEGIN_KV_SERIALIZE_MAP() - //KV_SERIALIZE(*m_ptx_wallet_info) KV_SERIALIZE_CUSTOM(m_ptx_wallet_info, const transaction_wallet_info&, tools::wallet2::transform_ptr_to_value, tools::wallet2::transform_value_to_ptr) KV_SERIALIZE(m_internal_output_index) KV_SERIALIZE(m_spent_height) @@ -471,7 +446,8 @@ namespace tools void restore(const std::wstring& path, const std::string& pass, const std::string& restore_key); void load(const std::wstring& wallet, const std::string& password); void store(); - void store(const std::wstring& path, const std::string& password, bool store_as_watch_only = false); + void store(const std::wstring& path, const std::string& password); + void store_watch_only(const std::wstring& path, const std::string& password) const; bool store_keys(std::string& buff, const std::string& password, bool store_as_watch_only = false); std::wstring get_wallet_path(){ return m_wallet_file; } currency::account_base& get_account() { return m_account; } @@ -613,7 +589,8 @@ namespace tools static bool scan_pos(mining_context& cxt, std::atomic& stop, idle_condition_cb_t idle_condition_cb, const currency::core_runtime_config &runtime_config); bool fill_mining_context(mining_context& ctx); void get_transfers(wallet2::transfer_container& incoming_transfers) const; - + std::string get_transfers_str(bool include_spent /*= true*/, bool include_unspent /*= true*/) const; + // Returns all payments by given id in unspecified order void get_payments(const std::string& payment_id, std::list& payments, uint64_t min_height = 0) const; @@ -634,6 +611,7 @@ namespace tools a & m_transfers; a & m_multisig_transfers; a & m_key_images; + a & m_pending_key_images; a & m_unconfirmed_txs; a & m_unconfirmed_multisig_transfers; a & m_tx_keys; @@ -813,9 +791,10 @@ private: const std::vector& destinations, const std::vector& selected_indicies); void mark_transfers_as_spent(const std::vector& selected_transfers, const std::string& reason = std::string()); - void mark_transfers_with_flag(const std::vector& selected_transfers, uint32_t flag, const std::string& reason = std::string()); + void mark_transfers_with_flag(const std::vector& selected_transfers, uint32_t flag, const std::string& reason = std::string(), bool throw_if_flag_already_set = false); void clear_transfers_from_flag(const std::vector& selected_transfers, uint32_t flag, const std::string& reason = std::string()); void exception_handler(); + void exception_handler() const; uint64_t get_minimum_allowed_fee_for_contract(const crypto::hash& ms_id); diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 7dfc3f64..debe1300 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -33,22 +33,26 @@ namespace tools wallet_rpc_server::wallet_rpc_server(wallet2& w):m_wallet(w), m_do_mint(false) {} //------------------------------------------------------------------------------------------------------------------------------ - bool wallet_rpc_server::run(bool do_mint) + bool wallet_rpc_server::run(bool do_mint, bool offline_mode) { m_do_mint = do_mint; - m_net_server.add_idle_handler([this](){ - size_t blocks_fetched = 0; - bool received_money = false; - bool ok; - std::atomic stop(false); - m_wallet.refresh(blocks_fetched, received_money, ok, stop); - if (stop) - return true; - if(m_do_mint) - m_wallet.try_mint_pos(); - return true; - }, 2000); + if (!offline_mode) + { + m_net_server.add_idle_handler([this]() { + size_t blocks_fetched = 0; + bool received_money = false; + bool ok; + std::atomic stop(false); + m_wallet.refresh(blocks_fetched, received_money, ok, stop); + if (stop) + return true; + + if (m_do_mint) + m_wallet.try_mint_pos(); + return true; + }, 2000); + } //DO NOT START THIS SERVER IN MORE THEN 1 THREADS WITHOUT REFACTORING return epee::http_server_impl_base::run(1, true); diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index d643129c..4f6ce82e 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -32,7 +32,7 @@ namespace tools static void init_options(boost::program_options::options_description& desc); bool init(const boost::program_options::variables_map& vm); - bool run(bool do_mint); + bool run(bool do_mint, bool offline_mode); CHAIN_HTTP_TO_MAP2(connection_context); //forward http requests to uri map