diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 137d84b6e..d2acf5182 100755 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -64,6 +64,7 @@ #include "wallet/wallet_args.h" #include "version.h" #include +#include "wallet/message_store.h" #ifdef WIN32 #include @@ -787,6 +788,8 @@ bool simple_wallet::print_fee_info(const std::vector &args/* = std: bool simple_wallet::prepare_multisig(const std::vector &args) { + m_command_successful = false; + bool by_mms = called_by_mms(); if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); @@ -820,11 +823,20 @@ bool simple_wallet::prepare_multisig(const std::vector &args) success_msg_writer() << multisig_info; success_msg_writer() << tr("Send this multisig info to all other participants, then use make_multisig [...] with others' multisig info"); success_msg_writer() << tr("This includes the PRIVATE view key, so needs to be disclosed only to that multisig wallet's participants "); + + if (by_mms) + { + get_message_store().process_wallet_created_data(get_multisig_wallet_state(), mms::message_type::key_set, multisig_info); + } + + m_command_successful = true; return true; } bool simple_wallet::make_multisig(const std::vector &args) { + m_command_successful = false; + bool by_mms = called_by_mms(); if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); @@ -880,6 +892,11 @@ bool simple_wallet::make_multisig(const std::vector &args) success_msg_writer() << tr("Another step is needed"); success_msg_writer() << multisig_extra_info; success_msg_writer() << tr("Send this multisig info to all other participants, then use finalize_multisig [...] with others' multisig info"); + if (by_mms) + { + get_message_store().process_wallet_created_data(get_multisig_wallet_state(), mms::message_type::finalizing_key_set, multisig_extra_info); + } + m_command_successful = true; return true; } } @@ -898,11 +915,14 @@ bool simple_wallet::make_multisig(const std::vector &args) success_msg_writer() << std::to_string(threshold) << "/" << total << tr(" multisig address: ") << m_wallet->get_account().get_public_address_str(m_wallet->nettype()); + m_command_successful = true; return true; } bool simple_wallet::finalize_multisig(const std::vector &args) { + m_command_successful = false; + bool by_mms = called_by_mms(); bool ready; if (m_wallet->key_on_device()) { @@ -947,11 +967,14 @@ bool simple_wallet::finalize_multisig(const std::vector &args) return true; } + m_command_successful = true; return true; } bool simple_wallet::export_multisig(const std::vector &args) { + m_command_successful = false; + bool by_mms = called_by_mms(); bool ready; if (m_wallet->key_on_device()) { @@ -977,17 +1000,24 @@ bool simple_wallet::export_multisig(const std::vector &args) return true; const std::string filename = args[0]; - if (m_wallet->confirm_export_overwrite() && !check_file_overwrite(filename)) + if (!by_mms && m_wallet->confirm_export_overwrite() && !check_file_overwrite(filename)) return true; try { cryptonote::blobdata ciphertext = m_wallet->export_multisig(); - bool r = epee::file_io_utils::save_string_to_file(filename, ciphertext); - if (!r) + if (by_mms) { - fail_msg_writer() << tr("failed to save file ") << filename; - return true; + get_message_store().process_wallet_created_data(get_multisig_wallet_state(), mms::message_type::multisig_sync_data, ciphertext); + } + else + { + bool r = epee::file_io_utils::save_string_to_file(filename, ciphertext); + if (!r) + { + fail_msg_writer() << tr("failed to save file ") << filename; + return true; + } } } catch (const std::exception &e) @@ -998,11 +1028,14 @@ bool simple_wallet::export_multisig(const std::vector &args) } success_msg_writer() << tr("Multisig info exported to ") << filename; + m_command_successful = true; return true; } bool simple_wallet::import_multisig(const std::vector &args) { + m_command_successful = false; + bool by_mms = called_by_mms(); bool ready; uint32_t threshold, total; if (m_wallet->key_on_device()) @@ -1031,15 +1064,22 @@ bool simple_wallet::import_multisig(const std::vector &args) std::vector info; for (size_t n = 0; n < args.size(); ++n) { - const std::string filename = args[n]; - std::string data; - bool r = epee::file_io_utils::load_file_to_string(filename, data); - if (!r) + if (by_mms) { - fail_msg_writer() << tr("failed to read file ") << filename; - return true; + info.push_back(args[n]); + } + else + { + const std::string &filename = args[n]; + std::string data; + bool r = epee::file_io_utils::load_file_to_string(filename, data); + if (!r) + { + fail_msg_writer() << tr("failed to read file ") << filename; + return true; + } + info.push_back(std::move(data)); } - info.push_back(std::move(data)); } LOCK_IDLE_SCOPE(); @@ -1051,6 +1091,7 @@ bool simple_wallet::import_multisig(const std::vector &args) // Clear line "Height xxx of xxx" std::cout << "\r \r"; success_msg_writer() << tr("Multisig info imported"); + m_command_successful = true; } catch (const std::exception &e) { @@ -1083,6 +1124,8 @@ bool simple_wallet::accept_loaded_tx(const tools::wallet2::multisig_tx_set &txs) bool simple_wallet::sign_multisig(const std::vector &args) { + m_command_successful = false; + bool by_mms = called_by_mms(); bool ready; if (m_wallet->key_on_device()) { @@ -1111,11 +1154,48 @@ bool simple_wallet::sign_multisig(const std::vector &args) uint32_t signers = 0; try { - bool r = m_wallet->sign_multisig_tx_from_file(filename, txids, [&](const tools::wallet2::multisig_tx_set &tx){ signers = tx.m_signers.size(); return accept_loaded_tx(tx); }); - if (!r) + if (by_mms) { - fail_msg_writer() << tr("Failed to sign multisig transaction"); - return true; + tools::wallet2::multisig_tx_set exported_txs; + std::string ciphertext; + bool r = m_wallet->load_multisig_tx(args[0], exported_txs, [&](const tools::wallet2::multisig_tx_set &tx){ signers = tx.m_signers.size(); return accept_loaded_tx(tx); }); + if (r) + { + r = m_wallet->sign_multisig_tx(exported_txs, txids); + } + if (r) + { + ciphertext = m_wallet->save_multisig_tx(exported_txs); + if (ciphertext.empty()) + { + r = false; + } + } + if (r) + { + mms::message_type message_type = mms::message_type::fully_signed_tx; + if (txids.empty()) + { + message_type = mms::message_type::partially_signed_tx; + } + get_message_store().process_wallet_created_data(get_multisig_wallet_state(), message_type, ciphertext); + filename = "MMS"; // for the messages below + m_command_successful = true; + } + else + { + fail_msg_writer() << tr("Failed to sign multisig transaction"); + return true; + } + } + else + { + bool r = m_wallet->sign_multisig_tx_from_file(filename, txids, [&](const tools::wallet2::multisig_tx_set &tx){ signers = tx.m_signers.size(); return accept_loaded_tx(tx); }); + if (!r) + { + fail_msg_writer() << tr("Failed to sign multisig transaction"); + return true; + } } } catch (const tools::error::multisig_export_needed& e) @@ -1155,6 +1235,8 @@ bool simple_wallet::sign_multisig(const std::vector &args) bool simple_wallet::submit_multisig(const std::vector &args) { + m_command_successful = false; + bool by_mms = called_by_mms(); bool ready; uint32_t threshold; if (m_wallet->key_on_device()) @@ -1186,11 +1268,23 @@ bool simple_wallet::submit_multisig(const std::vector &args) try { tools::wallet2::multisig_tx_set txs; - bool r = m_wallet->load_multisig_tx_from_file(filename, txs, [&](const tools::wallet2::multisig_tx_set &tx){ return accept_loaded_tx(tx); }); - if (!r) + if (by_mms) { - fail_msg_writer() << tr("Failed to load multisig transaction from file"); - return true; + bool r = m_wallet->load_multisig_tx(args[0], txs, [&](const tools::wallet2::multisig_tx_set &tx){ return accept_loaded_tx(tx); }); + if (!r) + { + fail_msg_writer() << tr("Failed to load multisig transaction from MMS"); + return true; + } + } + else + { + bool r = m_wallet->load_multisig_tx_from_file(filename, txs, [&](const tools::wallet2::multisig_tx_set &tx){ return accept_loaded_tx(tx); }); + if (!r) + { + fail_msg_writer() << tr("Failed to load multisig transaction from file"); + return true; + } } if (txs.m_signers.size() < threshold) { @@ -1206,6 +1300,7 @@ bool simple_wallet::submit_multisig(const std::vector &args) success_msg_writer(true) << tr("Transaction successfully submitted, transaction ") << get_transaction_hash(ptx.tx) << ENDL << tr("You can check its status by using the `show_transfers` command."); } + m_command_successful = true; } catch (const std::exception &e) { @@ -1296,6 +1391,816 @@ bool simple_wallet::export_raw_multisig(const std::vector &args) return true; } +// MMS --------------------------------------------------------------------------------------------------- + +// Access to the message store, or more exactly to the list of the messages that can be changed +// by the idle thread, is guarded by the same mutex-based mechanism as access to the wallet +// as a whole and thus e.g. uses the "LOCK_IDLE_SCOPE" macro. This is a little over-cautious, but +// simple and safe. Care has to be taken however where MMS methods call other simplewallet methods +// that use "LOCK_IDLE_SCOPE" as this cannot be nested! + +// Methods for commands like "export_multisig_info" usually read data from file(s) or write data +// to files. The MMS calls now those methods as well, to produce data for messages and to process data +// from messages. As it would be quite inconvenient for the MMS to write data for such methods to files +// first or get data out of result files after the call, those methods detect a call from the MMS and +// expect data as arguments instead of files and give back data by calling 'process_wallet_created_data'. +bool simple_wallet::called_by_mms() +{ + bool by_mms = m_called_by_mms; + m_called_by_mms = false; + return by_mms; +} + +bool simple_wallet::user_confirms(const std::string &question) +{ + std::string answer = input_line(question + tr(" (Y/Yes/N/No): ")); + return !std::cin.eof() && command_line::is_yes(answer); +} + +bool simple_wallet::get_number_from_arg(const std::string &arg, uint32_t &number, const uint32_t lower_bound, const uint32_t upper_bound) +{ + bool valid = false; + try + { + number = boost::lexical_cast(arg); + valid = (number >= lower_bound) && (number <= upper_bound); + } + catch(const boost::bad_lexical_cast &) + { + } + return valid; +} + +bool simple_wallet::choose_mms_processing(const std::vector &data_list, uint32_t &choice) +{ + uint32_t choices = data_list.size(); + if (choices == 1) + { + choice = 0; + return true; + } + mms::message_store& ms = m_wallet->get_message_store(); + success_msg_writer() << tr("Choose processing:"); + std::string text; + for (size_t i = 0; i < choices; ++i) + { + const mms::processing_data &data = data_list[i]; + text = std::to_string(i+1) + ": "; + switch (data.processing) + { + case mms::message_processing::sign_tx: + text += tr("Sign tx"); + break; + case mms::message_processing::send_tx: + { + mms::message m; + ms.get_message_by_id(data.message_ids[0], m); + if (m.type == mms::message_type::fully_signed_tx) + { + text += tr("Send the tx for submission to "); + } + else + { + text += tr("Send the tx for signing to "); + } + mms::coalition_member member = ms.get_member(data.receiving_member_index); + text += ms.member_to_string(member, 50); + break; + } + case mms::message_processing::submit_tx: + text += tr("Submit tx"); + break; + default: + text += tr("unknown"); + break; + } + success_msg_writer() << text; + } + + std::string line = input_line(tr("Choice: ")); + if (std::cin.eof() || line.empty()) + { + return false; + } + bool choice_ok = get_number_from_arg(line, choice, 1, choices); + if (choice_ok) + { + choice--; + } + else + { + fail_msg_writer() << tr("Wrong choice"); + } + return choice_ok; +} + +static std::string get_human_readable_timestamp(uint64_t ts); +static std::string get_human_readable_timespan(std::chrono::seconds seconds); + +void simple_wallet::list_mms_messages(const std::vector &messages) +{ + success_msg_writer() << boost::format("%4s %-4s %-30s %-21s %7s %-15s %-40s") % tr("Id") % tr("I/O") % tr("Coalition Member") + % tr("Message Type") % tr("Height") % tr("Message State") % tr("Since"); + mms::message_store& ms = m_wallet->get_message_store(); + uint64_t now = time(NULL); + for (size_t i = 0; i < messages.size(); ++i) + { + const mms::message &m = messages[i]; + const mms::coalition_member &member = ms.get_member(m.member_index); + bool highlight = (m.state == mms::message_state::ready_to_send) || (m.state == mms::message_state::waiting); + message_writer(m.direction == mms::message_direction::out ? console_color_green : console_color_magenta, highlight) << + boost::format("%4s %-4s %-30s %-21s %7s %-15s %-40s") % + m.id % + ms.message_direction_to_string(m.direction) % + ms.member_to_string(member, 30) % + ms.message_type_to_string(m.type) % + m.wallet_height % + ms.message_state_to_string(m.state) % + (get_human_readable_timestamp(m.modified) + ", " + get_human_readable_timespan(std::chrono::seconds(now - m.modified)) + tr(" ago")); + } +} + +void simple_wallet::show_message(const mms::message &m) +{ + mms::message_store& ms = m_wallet->get_message_store(); + const mms::coalition_member &member = ms.get_member(m.member_index); + bool display_content; + switch (m.type) + { + case mms::message_type::key_set: + case mms::message_type::finalizing_key_set: + case mms::message_type::note: + display_content = true; + break; + default: + display_content = false; + } + uint64_t now = time(NULL); + success_msg_writer() << ""; + success_msg_writer() << tr("Message ") << m.id; + success_msg_writer() << tr("In/out: ") << ms.message_direction_to_string(m.direction); + success_msg_writer() << tr("Type: ") << ms.message_type_to_string(m.type); + success_msg_writer() << tr("State: ") << boost::format(tr("%s since %s, %s ago")) % + ms.message_state_to_string(m.state) % get_human_readable_timestamp(m.modified) % get_human_readable_timespan(std::chrono::seconds(now - m.modified)); + if (m.sent == 0) + { + success_msg_writer() << tr("Sent: Never"); + } + else + { + success_msg_writer() << boost::format(tr("Sent: %s, %s ago")) % + get_human_readable_timestamp(m.sent) % get_human_readable_timespan(std::chrono::seconds(now - m.sent)); + } + success_msg_writer() << tr("Member: ") << ms.member_to_string(member, 100); + success_msg_writer() << tr("Content size: ") << m.content.length() << tr(" bytes"); + success_msg_writer() << tr("Content: ") << (display_content ? m.content : tr("(binary data)")); + + if (m.type == mms::message_type::note) + { + // Showing a note and read its text is "processing" it: Set the state accordingly + // which will also delete it from Bitmessage as a side effect + // (Without this little "twist" it would never change the state, and never get deleted) + ms.set_message_processed_or_sent(m.id); + } +} + +void simple_wallet::ask_send_all_ready_messages() +{ + mms::message_store& ms = m_wallet->get_message_store(); + std::vector ready_messages; + const std::vector &messages = ms.get_all_messages(); + for (size_t i = 0; i < messages.size(); ++i) + { + const mms::message &m = messages[i]; + if (m.state == mms::message_state::ready_to_send) + { + ready_messages.push_back(m); + } + } + if (ready_messages.size() != 0) + { + list_mms_messages(ready_messages); + bool send = ms.get_auto_send(); + if (!send) + { + send = user_confirms(tr("Send these messages now?")); + } + if (send) + { + mms::multisig_wallet_state state = get_multisig_wallet_state(); + for (size_t i = 0; i < ready_messages.size(); ++i) + { + ms.send_message(state, ready_messages[i].id); + ms.set_message_processed_or_sent(ready_messages[i].id); + } + success_msg_writer() << tr("Sent."); + } + } +} + +bool simple_wallet::get_message_from_arg(const std::string &arg, mms::message &m) +{ + mms::message_store& ms = m_wallet->get_message_store(); + bool valid_id = false; + uint32_t id; + try + { + id = (uint32_t)boost::lexical_cast(arg); + valid_id = ms.get_message_by_id(id, m); + } + catch (const boost::bad_lexical_cast &) + { + } + if (!valid_id) + { + fail_msg_writer() << tr("Invalid message id"); + } + return valid_id; +} + +void simple_wallet::mms_init(const std::vector &args) +{ + // mms init / + // Example: mms init 2/3 rbrunner BM-2cUVEbbb3H6ojddYQziK3RafJ5GPcFQv7e + // For now, assume we still have the original Monero address available, before "make_multisig" + if (args.size() != 4) + { + fail_msg_writer() << tr("usage: mms init / "); + return; + } + mms::message_store& ms = m_wallet->get_message_store(); + if (ms.get_active()) + { + if (!user_confirms(tr("The MMS is already initialized. Re-initialize by deleting all member info and messages?"))) + { + return; + } + } + uint32_t threshold; + uint32_t coalition_size; + const std::string &mn = args[1]; + std::vector numbers; + boost::split(numbers, mn, boost::is_any_of("/")); + bool mn_ok = (numbers.size() == 2) + && get_number_from_arg(numbers[0], threshold, 1, 100) + && get_number_from_arg(numbers[1], coalition_size, 2, 100); + if (mn_ok) + { + mn_ok = (threshold == coalition_size) || (threshold == (coalition_size -1)); + // Fully general cases like 3/5 not yet supported + } + if (!mn_ok) + { + fail_msg_writer() << tr("Error in threshold and/or coalition size"); + return; + } + ms.init(get_multisig_wallet_state(), args[2], args[3], coalition_size, threshold); +} + +void simple_wallet::mms_info(const std::vector &args) +{ + mms::message_store& ms = m_wallet->get_message_store(); + success_msg_writer() << boost::format("The MMS is active for %s/%s multisig.") + % ms.get_threshold() % ms.get_coalition_size(); +} + +void simple_wallet::mms_member(const std::vector &args) +// mms 0:member [1: <2:label> [3: [4:]]] +{ + mms::message_store& ms = m_wallet->get_message_store(); + const std::vector &members = ms.get_all_members(); + if (args.size() == 1) + { + // Without further parameters list all defined members + success_msg_writer() << boost::format("%2s %-20s %-s") % tr("#") % tr("Label") % tr("Transport Address"); + success_msg_writer() << boost::format("%2s %-20s %-s") % "" % "" % tr("Wownero Address"); + for (size_t i = 0; i < members.size(); ++i) + { + const mms::coalition_member &member = members[i]; + std::string label = member.label.empty() ? tr("") : member.label; + std::string monero_address; + if (member.monero_address_known) + { + monero_address = get_account_address_as_str(m_wallet->nettype(), false, member.monero_address); + } + else + { + monero_address = tr(""); + } + std::string transport_address = member.transport_address.empty() ? tr("") : member.transport_address; + success_msg_writer() << boost::format("%2s %-20s %-s") % (i + 1) % label % transport_address; + success_msg_writer() << boost::format("%2s %-20s %-s") % "" % "" % monero_address; + success_msg_writer() << ""; + } + return; + } + + uint32_t index; + bool index_valid = get_number_from_arg(args[1], index, 1, ms.get_coalition_size()); + if (index_valid) + { + index--; + } + else + { + fail_msg_writer() << tr("Invalid coalition member number ") + args[1]; + return; + } + if (args.size() < 3) + { + fail_msg_writer() << tr("mms member [