From d2d1bf5d7ac727d857473813a1a0986e7de1fe4f Mon Sep 17 00:00:00 2001 From: sowle Date: Wed, 3 Apr 2019 12:48:09 +0300 Subject: [PATCH 01/13] wallet: refactoring for cold-signing process + offline mode + watch-only mode (work in progress) --- contrib/epee/include/string_tools.h | 13 +- src/common/pod_array_file_container.h | 127 ++++ src/crypto/chacha8.h | 15 +- src/crypto/crypto.h | 2 +- src/currency_core/currency_basic.h | 5 +- src/currency_core/currency_config.h | 2 +- src/currency_core/currency_format_utils.cpp | 11 +- src/currency_core/currency_format_utils.h | 14 +- src/simplewallet/simplewallet.cpp | 74 ++- src/simplewallet/simplewallet.h | 5 +- src/wallet/wallet2.cpp | 643 ++++++++++++++----- src/wallet/wallet2.h | 644 ++++++++------------ src/wallet/wallet_errors.h | 8 + src/wallet/wallet_rpc_server.cpp | 35 +- src/wallet/wallet_rpc_server.h | 17 +- src/wallet/wallet_rpc_server_commans_defs.h | 25 + 16 files changed, 1073 insertions(+), 567 deletions(-) create mode 100644 src/common/pod_array_file_container.h diff --git a/contrib/epee/include/string_tools.h b/contrib/epee/include/string_tools.h index f66abfcf..e1cb977f 100644 --- a/contrib/epee/include/string_tools.h +++ b/contrib/epee/include/string_tools.h @@ -648,9 +648,6 @@ POP_WARNINGS return res; } //---------------------------------------------------------------------------- - - - inline std::string cut_off_extension(const std::string& str) { std::string res; @@ -661,7 +658,17 @@ POP_WARNINGS res = str.substr(0, pos); return res; } + //---------------------------------------------------------------------------- + inline std::wstring cut_off_extension(const std::wstring& str) + { + std::wstring res; + std::wstring::size_type pos = str.rfind('.'); + if (std::wstring::npos == pos) + return str; + res = str.substr(0, pos); + return res; + } //---------------------------------------------------------------------------- // replaces all non-ascii characters with mask_character inline std::string mask_non_ascii_chars(const std::string& str, const char mask_character = '?') diff --git a/src/common/pod_array_file_container.h b/src/common/pod_array_file_container.h new file mode 100644 index 00000000..7c168d79 --- /dev/null +++ b/src/common/pod_array_file_container.h @@ -0,0 +1,127 @@ +// Copyright (c) 2012-2013 The Cryptonote developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once +#include +#include +#include + +namespace tools +{ + + template + class pod_array_file_container + { + public: + pod_array_file_container() + {} + + ~pod_array_file_container() + { + close(); + } + + bool open(const std::wstring& filename, bool create_if_not_exist, bool* p_corrupted = nullptr, std::string* p_reason = nullptr) + { + if (!create_if_not_exist && !boost::filesystem::exists(filename)) + { + if (p_reason) + *p_reason = "file doest not exist"; + return false; + } + + m_stream.open(filename, std::ios::binary | std::ios::app | std::ios::in); + if (m_stream.rdstate() != std::ios::goodbit && m_stream.rdstate() != std::ios::eofbit) + { + if (p_reason) + *p_reason = "file could not be opened"; + return false; + } + + if (p_corrupted) + *p_corrupted = false; + + size_t file_size = size_bytes(); + if (file_size % sizeof(pod_t) != 0) + { + // currupted + if (p_corrupted) + *p_corrupted = true; + + size_t corrected_size = file_size - file_size % sizeof(pod_t); + + // truncate to nearest item boundary + close(); + boost::filesystem::resize_file(filename, corrected_size); + m_stream.open(filename, std::ios::binary | std::ios::app | std::ios::in); + if ((m_stream.rdstate() != std::ios::goodbit && m_stream.rdstate() != std::ios::eofbit) || + size_bytes() != corrected_size) + { + if (p_reason) + *p_reason = "truncation failed"; + return false; + } + + if (p_reason) + *p_reason = std::string("file was corrupted, truncated: ") + epee::string_tools::num_to_string_fast(file_size) + " -> " + epee::string_tools::num_to_string_fast(corrected_size); + } + + return true; + } + + void close() + { + m_stream.close(); + } + + bool push_back(const pod_t& item) + { + if (!m_stream.is_open() || (m_stream.rdstate() != std::ios::goodbit && m_stream.rdstate() != std::ios::eofbit)) + return false; + + m_stream.seekp(0, std::ios_base::end); + m_stream.write(reinterpret_cast(&item), sizeof item); + + if (m_stream.rdstate() != std::ios::goodbit && m_stream.rdstate() != std::ios::eofbit) + return false; + + m_stream.flush(); + + return true; + } + + bool get_item(size_t index, pod_t& result) const + { + if (!m_stream.is_open() || (m_stream.rdstate() != std::ios::goodbit && m_stream.rdstate() != std::ios::eofbit)) + return false; + + size_t offset = index * sizeof result; + m_stream.seekg(offset); + if (m_stream.rdstate() != std::ios::goodbit) + return false; + + m_stream.read(reinterpret_cast(&result), sizeof result); + + return m_stream.gcount() == sizeof result; + } + + size_t size_bytes() const + { + if (!m_stream.is_open() || (m_stream.rdstate() != std::ios::goodbit && m_stream.rdstate() != std::ios::eofbit)) + return 0; + + m_stream.seekg(0, std::ios_base::end); + return m_stream.tellg(); + } + + size_t size() const + { + return size_bytes() / sizeof(pod_t); + } + + private: + mutable boost::filesystem::fstream m_stream; + }; + +} // namespace tools diff --git a/src/crypto/chacha8.h b/src/crypto/chacha8.h index 98e14280..cebc6abe 100644 --- a/src/crypto/chacha8.h +++ b/src/crypto/chacha8.h @@ -78,10 +78,7 @@ namespace crypto { buff = decrypted_buff; return true; } -// inline bool chacha_decrypt(std::string& buff, const std::string& pass) -// { -// return chacha_crypt(buff, pass); -// } + template inline bool chacha_crypt(pod_to_encrypt& crypt, const pod_pass& pass) { @@ -91,6 +88,7 @@ namespace crypto { memcpy(&crypt, buff.data(), sizeof(crypt)); return true; } + template inline bool chacha_crypt(std::string& buff, const pod_pass& pass) { @@ -101,6 +99,15 @@ namespace crypto { return true; } + template + inline std::string chacha_crypt(const std::string& input, const pod_pass& pass) + { + std::string result; + result.resize(input.size()); + if (chacha_crypt(input.data(), input.size(), (void*)result.data(), &pass, sizeof pass)) + return result; + return ""; + } } diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h index d039eee8..3a3fafd8 100644 --- a/src/crypto/crypto.h +++ b/src/crypto/crypto.h @@ -229,7 +229,7 @@ namespace crypto { } -POD_MAKE_COMPARABLE(crypto, public_key) +POD_MAKE_HASHABLE(crypto, public_key) POD_MAKE_COMPARABLE(crypto, secret_key) POD_MAKE_HASHABLE(crypto, key_image) POD_MAKE_COMPARABLE(crypto, signature) diff --git a/src/currency_core/currency_basic.h b/src/currency_core/currency_basic.h index 81d5bac3..f6facfc8 100644 --- a/src/currency_core/currency_basic.h +++ b/src/currency_core/currency_basic.h @@ -51,7 +51,7 @@ namespace currency const static crypto::signature null_sig = AUTO_VAL_INIT(null_sig); const static crypto::key_derivation null_derivation = AUTO_VAL_INIT(null_derivation); - + typedef std::string payment_id_t; /************************************************************************/ @@ -563,7 +563,8 @@ namespace currency KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(keyimage) END_KV_SERIALIZE_MAP() }; -} + +} // namespace currency POD_MAKE_HASHABLE(currency, account_public_address); diff --git a/src/currency_core/currency_config.h b/src/currency_core/currency_config.h index 71608c76..2b40c168 100644 --- a/src/currency_core/currency_config.h +++ b/src/currency_core/currency_config.h @@ -218,7 +218,7 @@ #define BC_OFFERS_CURRENCY_MARKET_FILENAME "market.bin" -#define WALLET_FILE_SERIALIZATION_VERSION (CURRENCY_FORMATION_VERSION+62) +#define WALLET_FILE_SERIALIZATION_VERSION (CURRENCY_FORMATION_VERSION+63) #define CURRENT_MEMPOOL_ARCHIVE_VER (CURRENCY_FORMATION_VERSION+31) diff --git a/src/currency_core/currency_format_utils.cpp b/src/currency_core/currency_format_utils.cpp index 20cb55ce..a4166763 100644 --- a/src/currency_core/currency_format_utils.cpp +++ b/src/currency_core/currency_format_utils.cpp @@ -2483,7 +2483,7 @@ namespace currency return true; } //----------------------------------------------------------------------- - bool is_payment_id_size_ok(const std::string& payment_id) + bool is_payment_id_size_ok(const payment_id_t& payment_id) { return payment_id.size() <= BC_PAYMENT_ID_SERVICE_SIZE_MAX; } @@ -2493,7 +2493,7 @@ namespace currency return tools::base58::encode_addr(CURRENCY_PUBLIC_ADDRESS_BASE58_PREFIX, t_serializable_object_to_blob(addr)); } //----------------------------------------------------------------------- - std::string get_account_address_and_payment_id_as_str(const account_public_address& addr, const std::string& payment_id) + std::string get_account_address_and_payment_id_as_str(const account_public_address& addr, const payment_id_t& payment_id) { return tools::base58::encode_addr(CURRENCY_PUBLIC_INTEG_ADDRESS_BASE58_PREFIX, t_serializable_object_to_blob(addr) + payment_id); } @@ -2504,7 +2504,7 @@ namespace currency return get_account_address_and_payment_id_from_str(addr, integrated_payment_id, str); } //----------------------------------------------------------------------- - bool get_account_address_and_payment_id_from_str(account_public_address& addr, std::string& payment_id, const std::string& str) + bool get_account_address_and_payment_id_from_str(account_public_address& addr, payment_id_t& payment_id, const std::string& str) { static const size_t addr_blob_size = sizeof(account_public_address); blobdata blob; @@ -2556,6 +2556,11 @@ namespace currency return true; } + //--------------------------------------------------------------- + bool parse_payment_id_from_hex_str(const std::string& payment_id_str, payment_id_t& payment_id) + { + return epee::string_tools::parse_hexstr_to_binbuff(payment_id_str, payment_id); + } //------------------------------------------------------------------ bool is_tx_expired(const transaction& tx, uint64_t expiration_ts_median) { diff --git a/src/currency_core/currency_format_utils.h b/src/currency_core/currency_format_utils.h index 263e258c..3bbf8aa2 100644 --- a/src/currency_core/currency_format_utils.h +++ b/src/currency_core/currency_format_utils.h @@ -85,6 +85,13 @@ namespace currency tx_destination_entry() : amount(0), minimum_sigs(0), amount_to_provide(0){} tx_destination_entry(uint64_t a, const account_public_address& ad) : amount(a), addr(1, ad), minimum_sigs(0), amount_to_provide(0){} tx_destination_entry(uint64_t a, const std::list& addr) : amount(a), addr(addr), minimum_sigs(addr.size()), amount_to_provide(0){} + + BEGIN_SERIALIZE_OBJECT() + FIELD(amount) + FIELD(addr) + FIELD(minimum_sigs) + FIELD(amount_to_provide) + END_SERIALIZE() }; struct tx_extra_info @@ -391,11 +398,12 @@ namespace currency uint64_t get_base_block_reward(bool is_pos, uint64_t already_generated_coins, uint64_t height); uint64_t get_scratchpad_last_update_rebuild_height(uint64_t h); uint64_t get_scratchpad_size_for_height(uint64_t h); - bool is_payment_id_size_ok(const std::string& payment_id); + bool is_payment_id_size_ok(const payment_id_t& payment_id); std::string get_account_address_as_str(const account_public_address& addr); - std::string get_account_address_and_payment_id_as_str(const account_public_address& addr, const std::string& payment_id); + std::string get_account_address_and_payment_id_as_str(const account_public_address& addr, const payment_id_t& payment_id); bool get_account_address_from_str(account_public_address& addr, const std::string& str); - bool get_account_address_and_payment_id_from_str(account_public_address& addr, std::string& payment_id, const std::string& str); + bool get_account_address_and_payment_id_from_str(account_public_address& addr, payment_id_t& payment_id, const std::string& str); + bool parse_payment_id_from_hex_str(const std::string& payment_id_str, payment_id_t& payment_id); bool is_coinbase(const transaction& tx); bool is_coinbase(const transaction& tx, bool& pos_coinbase); bool have_attachment_service_in_container(const std::vector& av, const std::string& service_id, const std::string& instruction); diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index b885ea86..83615919 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -47,7 +47,7 @@ namespace const command_line::arg_descriptor arg_daemon_port = {"daemon-port", "Use daemon instance at port instead of default", 0}; const command_line::arg_descriptor arg_log_level = {"set-log", "", 0, true}; const command_line::arg_descriptor arg_do_pos_mining = { "do-pos-mining", "Do PoS mining", false, false }; - + const command_line::arg_descriptor arg_offline_mode = { "offline-mode", "Don't connect to daemon, work offline (for cold-signing process)", false, true }; const command_line::arg_descriptor< std::vector > arg_command = {"command", ""}; @@ -172,10 +172,11 @@ bool simple_wallet::help(const std::vector &args/* = std::vector - Start mining in daemon"); m_cmd_binder.set_handler("stop_mining", boost::bind(&simple_wallet::stop_mining, this, _1), "Stop mining in daemon"); @@ -203,7 +204,8 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("scan_transfers_for_id", boost::bind(&simple_wallet::scan_transfers_for_id, this, _1), "Rescan transfers for tx_id"); m_cmd_binder.set_handler("scan_transfers_for_ki", boost::bind(&simple_wallet::scan_transfers_for_ki, this, _1), "Rescan transfers for key image"); m_cmd_binder.set_handler("integrated_address", boost::bind(&simple_wallet::integrated_address, this, _1), "integrated_address [|"); + } //---------------------------------------------------------------------------------------------------- @@ -278,10 +280,10 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) } } - m_do_refresh = true; + m_do_refresh_after_load = true; if (command_line::has_arg(vm, arg_dont_refresh)) { - m_do_refresh = false; + m_do_refresh_after_load = false; } if (command_line::has_arg(vm, arg_print_brain_wallet)) @@ -411,7 +413,7 @@ bool simple_wallet::open_wallet(const string &wallet_file, const std::string& pa m_wallet->init(m_daemon_address); - if (!m_do_refresh) + if (m_do_refresh_after_load && !m_offline_mode) refresh(std::vector()); success_msg_writer() << @@ -1237,6 +1239,47 @@ bool simple_wallet::integrated_address(const std::vector &args) return true; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::get_tx_key(const std::vector &args_) +{ + std::vector local_args = args_; + + if (local_args.size() != 1) { + fail_msg_writer() << "usage: get_tx_key "; + return true; + } + + currency::blobdata txid_data; + if (!epee::string_tools::parse_hexstr_to_binbuff(local_args.front(), txid_data) || txid_data.size() != sizeof(crypto::hash)) + { + fail_msg_writer() << "failed to parse txid"; + return true; + } + crypto::hash txid = *reinterpret_cast(txid_data.data()); + + crypto::secret_key tx_key; + std::vector amount_keys; + if (m_wallet->get_tx_key(txid, tx_key)) + { + success_msg_writer() << "tx one-time secret key: " << epee::string_tools::pod_to_hex(tx_key); + return true; + } + else + { + fail_msg_writer() << "no tx keys found for this txid"; + return true; + } +} +//---------------------------------------------------------------------------------------------------- +void simple_wallet::set_offline_mode(bool offline_mode) +{ + if (offline_mode && !m_offline_mode) + { + message_writer(epee::log_space::console_color_yellow, true, std::string(), LOG_LEVEL_0) + << "WARNING: the wallet is running in OFFLINE MODE!"; + } + m_offline_mode = offline_mode; +} +//---------------------------------------------------------------------------------------------------- int main(int argc, char* argv[]) { #ifdef WIN32 @@ -1271,13 +1314,15 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, arg_dont_set_date); 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); + tools::wallet_rpc_server::init_options(desc_params); po::positional_options_description positional_options; positional_options.add(arg_command.name, -1); + shared_ptr sw(new currency::simple_wallet); po::options_description desc_all; desc_all.add(desc_general).add(desc_params); po::variables_map vm; @@ -1287,9 +1332,8 @@ int main(int argc, char* argv[]) if (command_line::get_arg(vm, command_line::arg_help)) { - simple_wallet w; success_msg_writer() << "Usage: simplewallet [--wallet-file=|--generate-new-wallet=] [--daemon-address=:] []"; - success_msg_writer() << desc_all << '\n' << w.get_commands_str(); + success_msg_writer() << desc_all << '\n' << sw->get_commands_str(); return false; } else if (command_line::get_arg(vm, command_line::arg_version)) @@ -1320,10 +1364,13 @@ int main(int argc, char* argv[]) log_space::get_set_log_detalisation_level(true, command_line::get_arg(vm, arg_log_level)); } + bool offline_mode = command_line::get_arg(vm, arg_offline_mode); + if(command_line::has_arg(vm, tools::wallet_rpc_server::arg_rpc_bind_port)) { // runs wallet as RPC server log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL, LOG_LEVEL_2); + sw->set_offline_mode(offline_mode); LOG_PRINT_L0("Starting wallet RPC server..."); if (!command_line::has_arg(vm, arg_wallet_file) || command_line::get_arg(vm, arg_wallet_file).empty()) @@ -1332,7 +1379,7 @@ int main(int argc, char* argv[]) return 1; } - if (!command_line::has_arg(vm, arg_daemon_address) ) + if (!command_line::has_arg(vm, arg_daemon_address) && !command_line::has_arg(vm, arg_offline_mode)) { LOG_ERROR("Daemon address is not set."); return 1; @@ -1385,7 +1432,8 @@ int main(int argc, char* argv[]) wal.init(daemon_address); if (command_line::get_arg(vm, arg_generate_new_wallet).size()) return 1; - wal.refresh(); + if (!offline_mode) + wal.refresh(); LOG_PRINT_GREEN("Loaded ok", LOG_LEVEL_0); } catch (const std::exception& e) @@ -1420,8 +1468,8 @@ int main(int argc, char* argv[]) } }else { - shared_ptr sw(new currency::simple_wallet); //runs wallet with console interface + sw->set_offline_mode(offline_mode); r = sw->init(vm); CHECK_AND_ASSERT_MES(r, 1, "Failed to initialize wallet"); if (command_line::get_arg(vm, arg_generate_new_wallet).size()) diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 70fa3f85..dca21675 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -33,6 +33,7 @@ namespace currency bool deinit(); bool run(); void stop(); + void set_offline_mode(bool offline_mode); //wallet *create_wallet(); bool process_command(const std::vector &args); @@ -72,6 +73,7 @@ namespace currency bool set_log(const std::vector &args); bool enable_concole_logger(const std::vector &args); bool integrated_address(const std::vector &args); + bool get_tx_key(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); @@ -145,10 +147,11 @@ namespace currency std::string m_daemon_address; std::string m_daemon_host; int m_daemon_port; - bool m_do_refresh; + bool m_do_refresh_after_load; bool m_do_not_set_date; bool m_print_brain_wallet; bool m_do_pos_mining; + bool m_offline_mode; epee::console_handlers_binder m_cmd_binder; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index f788d0d0..bb648001 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -436,23 +436,23 @@ void wallet2::accept_proposal(const crypto::hash& contract_id, uint64_t b_accept construct_tx_param construct_param = AUTO_VAL_INIT(construct_param); construct_param.fee = b_acceptance_fee; - constructed_tx_data construct_res = AUTO_VAL_INIT(construct_res); - construct_res.tx = contr_it->second.proposal.tx_template; - construct_res.one_time_key.sec = contr_it->second.proposal.tx_onetime_secret_key; + currency::transaction tx = contr_it->second.proposal.tx_template; + crypto::secret_key one_time_key = contr_it->second.proposal.tx_onetime_secret_key; construct_param.crypt_address = m_account.get_public_address(); construct_param.flags = TX_FLAG_SIGNATURE_MODE_SEPARATE; construct_param.mark_tx_as_complete = true; + construct_param.split_strategy_id = detail::ssi_digit; //little hack for now, we add multisig_entry before transaction actually get to blockchain //to let prepare_transaction (which is called from build_escrow_release_templates) work correct //this code definitely need to be rewritten later (very bad design) - size_t n = get_multisig_out_index(construct_res.tx.vout); - THROW_IF_FALSE_WALLET_EX(n != construct_res.tx.vout.size(), error::wallet_internal_error, "Multisig out not found in tx template in proposal"); + size_t n = get_multisig_out_index(tx.vout); + THROW_IF_FALSE_WALLET_EX(n != tx.vout.size(), error::wallet_internal_error, "Multisig out not found in tx template in proposal"); transfer_details_base& tdb = m_multisig_transfers[contract_id]; //create once instance of tx for all entries std::shared_ptr pwallet_info(new transaction_wallet_info()); - pwallet_info->m_tx = construct_res.tx;; + pwallet_info->m_tx = tx;; pwallet_info->m_block_height = 0; pwallet_info->m_block_timestamp = 0; tdb.m_ptx_wallet_info = pwallet_info; @@ -460,10 +460,10 @@ void wallet2::accept_proposal(const crypto::hash& contract_id, uint64_t b_accept tdb.m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_SPENT); //--------------------------------- //figure out fee that was left for release contract - THROW_IF_FALSE_WALLET_INT_ERR_EX(construct_res.tx.vout[n].amount > (contr_it->second.private_detailes.amount_to_pay + + THROW_IF_FALSE_WALLET_INT_ERR_EX(tx.vout[n].amount > (contr_it->second.private_detailes.amount_to_pay + contr_it->second.private_detailes.amount_b_pledge + contr_it->second.private_detailes.amount_a_pledge), "THere is no left money for fee, contract_id: " << contract_id); - uint64_t left_for_fee_in_multisig = construct_res.tx.vout[n].amount - (contr_it->second.private_detailes.amount_to_pay + + uint64_t left_for_fee_in_multisig = tx.vout[n].amount - (contr_it->second.private_detailes.amount_to_pay + contr_it->second.private_detailes.amount_b_pledge + contr_it->second.private_detailes.amount_a_pledge); @@ -490,17 +490,14 @@ void wallet2::accept_proposal(const crypto::hash& contract_id, uint64_t b_accept construct_param.extra.push_back(tsa); //build transaction - prepare_transaction(construct_param, construct_res, tools::detail::digit_split_strategy); - - send_transaction_to_network(construct_res.tx); - - 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)) + ">"); - 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); + finalize_tx_param ftp = AUTO_VAL_INIT(ftp); + prepare_transaction(construct_param, ftp, tx); + finalize_transaction(ftp, tx, one_time_key, true); + mark_transfers_as_spent(ftp.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(tx)) + ">"); + print_tx_sent_message(tx, "(contract <" + epee::string_tools::pod_to_hex(contract_id) + ">)", construct_param.fee); if (p_acceptance_tx != nullptr) - *p_acceptance_tx = construct_res.tx; + *p_acceptance_tx = tx; } //--------------------------- void wallet2::finish_contract(const crypto::hash& contract_id, const std::string& release_type, currency::transaction* p_release_tx /* = nullptr */) @@ -594,7 +591,6 @@ void wallet2::request_cancel_contract(const crypto::hash& contract_id, uint64_t ////////////////////////////////////////////////////////////////////////// construct_tx_param construct_param = AUTO_VAL_INIT(construct_param); - constructed_tx_data constructed_data = AUTO_VAL_INIT(constructed_data); construct_param.fee = fee; //------- @@ -612,18 +608,18 @@ void wallet2::request_cancel_contract(const crypto::hash& contract_id, uint64_t tsa.flags |= TX_SERVICE_ATTACHMENT_ENCRYPT_BODY; construct_param.extra.push_back(tsa); construct_param.crypt_address = contr_it->second.private_detailes.b_addr; + construct_param.split_strategy_id = detail::ssi_digit; - prepare_transaction(construct_param, constructed_data, tools::detail::digit_split_strategy); + finalize_tx_param ftp = AUTO_VAL_INIT(ftp); + prepare_transaction(construct_param, ftp); + currency::transaction tx = AUTO_VAL_INIT(tx); + finalize_transaction(ftp, tx, crypto::secret_key(), true); + mark_transfers_as_spent(ftp.selected_transfers, std::string("contract <") + epee::string_tools::pod_to_hex(contract_id) + "> has been requested for cancellaton with tx <" + epee::string_tools::pod_to_hex(get_transaction_hash(tx)) + ">"); - send_transaction_to_network(constructed_data.tx); - - mark_transfers_as_spent(constructed_data.selected_transfers, std::string("contract <") + epee::string_tools::pod_to_hex(contract_id) + "> has been requested for cancellaton with tx <" + epee::string_tools::pod_to_hex(get_transaction_hash(constructed_data.tx)) + ">"); - add_sent_tx_detailed_info(constructed_data.tx, constructed_data.prepared_destinations, constructed_data.selected_transfers); - - print_tx_sent_message(constructed_data.tx, "(transport for cancel proposal)", fee); + print_tx_sent_message(tx, "(transport for cancel proposal)", fee); if (p_cancellation_proposal_tx != nullptr) - *p_cancellation_proposal_tx = constructed_data.tx; + *p_cancellation_proposal_tx = tx; } //----------------------------------------------------------------------------------------------------- void wallet2::scan_tx_to_key_inputs(std::vector& found_transfers, const currency::transaction& tx) @@ -1159,7 +1155,7 @@ void wallet2::transfer(uint64_t amount, const currency::account_public_address& dst.back().addr.push_back(acc); dst.back().amount = amount; transaction result_tx = AUTO_VAL_INIT(result_tx); - this->transfer(dst, 0, 0, TX_DEFAULT_FEE, extra, attachments, tools::detail::digit_split_strategy, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), result_tx); + this->transfer(dst, 0, 0, TX_DEFAULT_FEE, extra, attachments, tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), result_tx); } //---------------------------------------------------------------------------------------------------- @@ -1693,6 +1689,7 @@ bool wallet2::reset_all() m_unconfirmed_in_transfers.clear(); m_unconfirmed_txs.clear(); m_unconfirmed_multisig_transfers.clear(); + // m_tx_keys is not cleared intentionally, considered to be safe m_multisig_transfers.clear(); m_payments.clear(); m_transfer_history.clear(); @@ -1762,6 +1759,58 @@ void wallet2::init_log_prefix() m_log_prefix = m_account.get_public_address_str().substr(0, 6); } //---------------------------------------------------------------------------------------------------- +void wallet2::load_keys2ki(bool create_if_not_exist, bool& need_to_resync) +{ + m_pending_key_images_file_container.close(); // just in case it was opened + bool pki_corrupted = false; + std::string reason; + bool ok = m_pending_key_images_file_container.open(m_pending_ki_file, create_if_not_exist, &pki_corrupted, &reason); + THROW_IF_FALSE_WALLET_EX(ok, error::file_not_found, m_log_prefix + ": error opening file " + string_encoding::convert_to_ansii(m_pending_ki_file)); + if (pki_corrupted) + { + WLT_LOG_ERROR("file " << string_encoding::convert_to_ansii(m_pending_ki_file) << " is corrupted! " << reason); + } + + if (m_pending_key_images.size() < m_pending_key_images_file_container.size()) + { + WLT_LOG_RED("m_pending_key_images size: " << m_pending_key_images.size() << " is LESS than m_pending_key_images_file_container size: " << m_pending_key_images_file_container.size(), LOG_LEVEL_0); + WLT_LOG_L0("Restoring m_pending_key_images from file container..."); + m_pending_key_images.clear(); + for (size_t i = 0, size = m_pending_key_images_file_container.size(); i < size; ++i) + { + out_key_to_ki item = AUTO_VAL_INIT(item); + ok = m_pending_key_images_file_container.get_item(i, item); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(ok, "m_pending_key_images_file_container.get_item() failed for index " << i << ", size: " << m_pending_key_images_file_container.size()); + ok = m_pending_key_images.insert(std::make_pair(item.out_key, item.key_image)).second; + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(ok, "m_pending_key_images.insert failed for index " << i << ", size: " << m_pending_key_images_file_container.size()); + WLT_LOG_L2("pending key image restored: (" << item.out_key << ", " << item.key_image << ")"); + } + WLT_LOG_L0(m_pending_key_images.size() << " elements restored, requesting full wallet resync"); + WLT_LOG_L0("m_pending_key_images size: " << m_pending_key_images.size() << ", m_pending_key_images_file_container size: " << m_pending_key_images_file_container.size()); + need_to_resync = true; + } + else if (m_pending_key_images.size() > m_pending_key_images_file_container.size()) + { + 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"); + } +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::prepare_file_names(const std::wstring& file_path) +{ + m_wallet_file = file_path; + + m_pending_ki_file = string_tools::cut_off_extension(m_wallet_file) + L".outkey2ki"; + + // make sure file path is accessible and exists + boost::filesystem::path pp = boost::filesystem::path(file_path).parent_path(); + if (!pp.empty()) + boost::filesystem::create_directories(pp); + + return true; +} +//---------------------------------------------------------------------------------------------------- void wallet2::load_keys(const std::string& buff, const std::string& password) { wallet2::keys_file_data keys_file_data; @@ -1780,7 +1829,10 @@ void wallet2::load_keys(const std::string& buff, const std::string& password) const currency::account_keys& keys = m_account.get_keys(); r = epee::serialization::load_t_from_binary(m_account, account_data); r = r && verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key); - r = r && verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key); + if (keys.m_spend_secret_key == currency::null_skey) + m_watch_only = true; + else + r = r && verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key); if (!r) { WLT_LOG_L0("Wrong password for wallet " << string_encoding::convert_to_ansii(m_wallet_file)); @@ -1799,19 +1851,21 @@ void wallet2::assign_account(const currency::account_base& acc) void wallet2::generate(const std::wstring& path, const std::string& pass) { clear(); - m_wallet_file = path; + prepare_file_names(path); m_password = pass; m_account.generate(); init_log_prefix(); boost::system::error_code ignored_ec; THROW_IF_TRUE_WALLET_EX(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, epee::string_encoding::convert_to_ansii(m_wallet_file)); + bool stub; + load_keys2ki(true, stub); store(); } //---------------------------------------------------------------------------------------------------- void wallet2::restore(const std::wstring& path, const std::string& pass, const std::string& restore_key) { clear(); - m_wallet_file = path; + prepare_file_names(path); m_password = pass; bool r = m_account.restore_keys_from_braindata(restore_key); init_log_prefix(); @@ -1829,7 +1883,7 @@ bool wallet2::check_connection() void wallet2::load(const std::wstring& wallet_, const std::string& password) { clear(); - m_wallet_file = wallet_; + prepare_file_names(wallet_); m_password = password; std::string keys_buff; @@ -1857,16 +1911,21 @@ void wallet2::load(const std::wstring& wallet_, const std::string& password) data_file.read((char*)keys_buff.data(), wbh.m_cb_keys); load_keys(keys_buff, password); - WLT_LOG_L0("Loaded wallet keys file, with public address: " << m_account.get_public_address_str()); - bool r = tools::portable_unserialize_obj_from_stream(*this, data_file); + bool need_to_resync = !tools::portable_unserialize_obj_from_stream(*this, data_file); - if (!r) + if (m_watch_only) + load_keys2ki(true, need_to_resync); + + if (need_to_resync) { reset_history(); } m_local_bc_height = m_blockchain.size(); - THROW_IF_TRUE_WALLET_EX(!r, error::wallet_load_notice_wallet_restored, epee::string_encoding::convert_to_ansii(m_wallet_file)); + 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() << ")"); } //---------------------------------------------------------------------------------------------------- void wallet2::store() @@ -1876,6 +1935,8 @@ void wallet2::store() //---------------------------------------------------------------------------------------------------- void wallet2::store(const std::wstring& path_to_save) { + 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() << ")"); + //prepare data std::string keys_buff; bool r = store_keys(keys_buff, m_password); @@ -2020,14 +2081,59 @@ void wallet2::get_transfers(wallet2::transfer_container& incoming_transfers) con incoming_transfers = m_transfers; } //---------------------------------------------------------------------------------------------------- -void wallet2::get_payments(const std::string& payment_id, std::list& payments) const +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); - std::for_each(range.first, range.second, [&payments](const payment_container::value_type& x) { - payments.push_back(x.second); + std::for_each(range.first, range.second, [&payments, &min_height](const payment_container::value_type& x) + { + if (min_height <= x.second.m_block_height) + { + payments.push_back(x.second); + } }); } //---------------------------------------------------------------------------------------------------- +/*void wallet2::sign_transfer(const std::string& tx_sources_blob, std::string& signed_tx_blob, currency::transaction& tx) +{ + // assumed to be called from normal, non-watch-only wallet + THROW_IF_FALSE_WALLET_EX(!m_watch_only, error::wallet_common_error, "watch-only wallet is unable to sign transfers, you need to use normal wallet for that"); + + // 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 + create_tx_arg create_tx_param = AUTO_VAL_INIT(create_tx_param); + bool r = t_unserializable_object_from_blob(create_tx_param, 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(create_tx_param.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"); + + // construct the transaction + create_tx_res create_tx_result = AUTO_VAL_INIT(create_tx_result); + r = currency::construct_tx(m_account.get_keys(), create_tx_param, create_tx_result); + THROW_IF_FALSE_WALLET_EX(r, error::wallet_common_error, "construct_tx failed at sign_transfer"); + tx = create_tx_result.tx; + + // serialize and encrypt the result + signed_tx_blob = t_serializable_object_to_blob(create_tx_result); + crypto::do_chacha_crypt(signed_tx_blob, m_account.get_keys().m_view_secret_key); +} +//---------------------------------------------------------------------------------------------------- +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); + + 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); +} +*/ +//---------------------------------------------------------------------------------------------------- uint64_t wallet2::get_recent_transfers_total_count() { return m_transfer_history.size(); @@ -2235,7 +2341,7 @@ bool wallet2::reset_history() m_blockchain[0] = genesis_hash; m_account = acc_tmp; m_password = pass; - m_wallet_file = file_path; + prepare_file_names(file_path); return true; } //------------------------------- @@ -2371,7 +2477,7 @@ void wallet2::push_offer(const bc_services::offer_details_ex& od, currency::tran bc_services::put_offer_into_attachment(static_cast(od), attachments); destinations.push_back(tx_dest); - transfer(destinations, 0, 0, od.fee, extra, attachments, detail::digit_split_strategy, tx_dust_policy(DEFAULT_DUST_THRESHOLD), res_tx); + transfer(destinations, 0, 0, od.fee, extra, attachments, detail::ssi_digit, tx_dust_policy(DEFAULT_DUST_THRESHOLD), res_tx); } //---------------------------------------------------------------------------------------------------- const transaction& wallet2::get_transaction_by_id(const crypto::hash& tx_hash) @@ -2408,7 +2514,7 @@ void wallet2::cancel_offer_by_id(const crypto::hash& tx_id, uint64_t of_ind, cur destinations.push_back(tx_dest); uint64_t fee = 0; // use zero fee for offer cancellation transaction - transfer(destinations, 0, 0, fee, extra, attachments, detail::digit_split_strategy, tx_dust_policy(DEFAULT_DUST_THRESHOLD), res_tx); + transfer(destinations, 0, 0, fee, extra, attachments, detail::ssi_digit, tx_dust_policy(DEFAULT_DUST_THRESHOLD), res_tx); } //---------------------------------------------------------------------------------------------------- void wallet2::update_offer_by_id(const crypto::hash& tx_id, uint64_t of_ind, const bc_services::offer_details_ex& od, currency::transaction& res_tx) @@ -2434,7 +2540,7 @@ void wallet2::update_offer_by_id(const crypto::hash& tx_id, uint64_t of_ind, con bc_services::put_offer_into_attachment(uo, attachments); destinations.push_back(tx_dest); - transfer(destinations, 0, 0, od.fee, extra, attachments, detail::digit_split_strategy, tx_dust_policy(DEFAULT_DUST_THRESHOLD), res_tx); + transfer(destinations, 0, 0, od.fee, extra, attachments, detail::ssi_digit, tx_dust_policy(DEFAULT_DUST_THRESHOLD), res_tx); } //---------------------------------------------------------------------------------------------------- void wallet2::request_alias_registration(const currency::extra_alias_entry& ai, currency::transaction& res_tx, uint64_t fee, uint64_t reward) @@ -2455,7 +2561,7 @@ void wallet2::request_alias_registration(const currency::extra_alias_entry& ai, tx_dest_alias_reward.amount = reward; destinations.push_back(tx_dest_alias_reward); - transfer(destinations, 0, 0, fee, extra, attachments, detail::digit_split_strategy, tx_dust_policy(DEFAULT_DUST_THRESHOLD), res_tx, CURRENCY_TO_KEY_OUT_RELAXED, false); + transfer(destinations, 0, 0, fee, extra, attachments, detail::ssi_digit, tx_dust_policy(DEFAULT_DUST_THRESHOLD), res_tx, CURRENCY_TO_KEY_OUT_RELAXED, false); } //---------------------------------------------------------------------------------------------------- void wallet2::request_alias_update(currency::extra_alias_entry& ai, currency::transaction& res_tx, uint64_t fee, uint64_t reward) @@ -2478,7 +2584,7 @@ void wallet2::request_alias_update(currency::extra_alias_entry& ai, currency::tr std::vector extra; std::vector attachments; extra.push_back(ai); - transfer(destinations, 0, 0, fee, extra, attachments, detail::digit_split_strategy, tx_dust_policy(DEFAULT_DUST_THRESHOLD), res_tx, CURRENCY_TO_KEY_OUT_RELAXED, false); + transfer(destinations, 0, 0, fee, extra, attachments, detail::ssi_digit, tx_dust_policy(DEFAULT_DUST_THRESHOLD), res_tx, CURRENCY_TO_KEY_OUT_RELAXED, false); } //---------------------------------------------------------------------------------------------------- bool wallet2::check_available_sources(std::list& amounts) @@ -2625,9 +2731,10 @@ void wallet2::build_escrow_release_templates(crypto::hash multisig_id, const bc_services::contract_private_details& ecrow_details) { construct_tx_param construct_params = AUTO_VAL_INIT(construct_params); - constructed_tx_data constructed_data = AUTO_VAL_INIT(constructed_data); + finalize_tx_param ftp = AUTO_VAL_INIT(ftp); construct_params.fee = fee; construct_params.multisig_id = multisig_id; + construct_params.split_strategy_id = detail::ssi_digit; construct_params.dsts.resize(2); //0 - addr_a //1 - addr_b @@ -2648,8 +2755,8 @@ void wallet2::build_escrow_release_templates(crypto::hash multisig_id, tsa.service_id = BC_ESCROW_SERVICE_ID; tsa.instruction = BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_NORMAL; construct_params.extra.push_back(tsa); - prepare_transaction(construct_params, constructed_data, detail::digit_split_strategy); - tx_release_template = constructed_data.tx; + prepare_transaction(construct_params, ftp); + finalize_transaction(ftp, tx_release_template, crypto::secret_key(), false); //generate burn escrow construct_params.dsts.resize(1); @@ -2660,8 +2767,8 @@ void wallet2::build_escrow_release_templates(crypto::hash multisig_id, construct_params.extra.clear(); tsa.instruction = BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_BURN; construct_params.extra.push_back(tsa); - prepare_transaction(construct_params, constructed_data, detail::digit_split_strategy); - tx_burn_template = constructed_data.tx; + prepare_transaction(construct_params, ftp); + finalize_transaction(ftp, tx_burn_template, crypto::secret_key(), false); } //---------------------------------------------------------------------------------------------------- void wallet2::build_escrow_cancel_template(crypto::hash multisig_id, @@ -2677,9 +2784,10 @@ void wallet2::build_escrow_cancel_template(crypto::hash multisig_id, "multisig id out amount no more than escrow total amount"); construct_tx_param construct_params = AUTO_VAL_INIT(construct_params); - constructed_tx_data constructed_data = AUTO_VAL_INIT(constructed_data); + finalize_tx_param ftp = AUTO_VAL_INIT(ftp); construct_params.fee = it->second.amount() - (ecrow_details.amount_a_pledge + ecrow_details.amount_to_pay + ecrow_details.amount_b_pledge); construct_params.multisig_id = multisig_id; + construct_params.split_strategy_id = detail::ssi_digit; construct_params.dsts.resize(2); //0 - addr_a //1 - addr_b @@ -2698,8 +2806,8 @@ void wallet2::build_escrow_cancel_template(crypto::hash multisig_id, expir.v = m_core_runtime_config.get_core_time() + expiration_period; construct_params.extra.push_back(expir); - prepare_transaction(construct_params, constructed_data, detail::digit_split_strategy); - tx_cancel_template = constructed_data.tx; + prepare_transaction(construct_params, ftp); + finalize_transaction(ftp, tx_cancel_template, crypto::secret_key(), false); } //---------------------------------------------------------------------------------------------------- @@ -2711,56 +2819,52 @@ void wallet2::build_escrow_template(const bc_services::contract_private_details& const std::string& payment_id, currency::transaction& tx, std::vector& selected_transfers, - currency::keypair& one_time_key) + crypto::secret_key& one_time_key) { + construct_tx_param ctp = AUTO_VAL_INIT(ctp); + ctp.crypt_address = ecrow_details.b_addr; + ctp.dust_policy = tx_dust_policy(DEFAULT_DUST_THRESHOLD); + ctp.fake_outputs_count = fake_outputs_count; + ctp.fee = 0; + ctp.flags = TX_FLAG_SIGNATURE_MODE_SEPARATE; + ctp.mark_tx_as_complete = false; + ctp.multisig_id = currency::null_hash; + ctp.shuffle = true; + ctp.split_strategy_id = detail::ssi_digit; + ctp.tx_outs_attr = CURRENCY_TO_KEY_OUT_RELAXED; + ctp.unlock_time = unlock_time; - std::vector destinations; - std::vector extra; - std::vector attachments; - etc_tx_details_expiration_time t = AUTO_VAL_INIT(t); - t.v = expiration_time; - extra.push_back(t); + t.v = expiration_time; // TODO: move it to construct_tx_param + ctp.extra.push_back(t); currency::tx_service_attachment att = AUTO_VAL_INIT(att); bc_services::pack_attachment_as_gzipped_json(ecrow_details, att); att.flags |= TX_SERVICE_ATTACHMENT_ENCRYPT_BODY; att.service_id = BC_ESCROW_SERVICE_ID; att.instruction = BC_ESCROW_SERVICE_INSTRUCTION_PRIVATE_DETAILS; - extra.push_back(att); + ctp.extra.push_back(att); - destinations.resize(1); - destinations.back().amount = ecrow_details.amount_a_pledge + ecrow_details.amount_b_pledge + ecrow_details.amount_to_pay + b_release_fee; - destinations.back().amount_to_provide = ecrow_details.amount_a_pledge + ecrow_details.amount_to_pay; - destinations.back().addr.push_back(ecrow_details.a_addr); - destinations.back().addr.push_back(ecrow_details.b_addr); - destinations.back().minimum_sigs = 2; - std::vector prepared_destinations; + ctp.dsts.resize(1); + ctp.dsts.back().amount = ecrow_details.amount_a_pledge + ecrow_details.amount_b_pledge + ecrow_details.amount_to_pay + b_release_fee; + ctp.dsts.back().amount_to_provide = ecrow_details.amount_a_pledge + ecrow_details.amount_to_pay; + ctp.dsts.back().addr.push_back(ecrow_details.a_addr); + ctp.dsts.back().addr.push_back(ecrow_details.b_addr); + ctp.dsts.back().minimum_sigs = 2; if (payment_id.size()) { currency::tx_service_attachment att = AUTO_VAL_INIT(att); att.body = payment_id; att.service_id = BC_PAYMENT_ID_SERVICE_ID; att.flags = TX_SERVICE_ATTACHMENT_DEFLATE_BODY; - attachments.push_back(att); + ctp.attachments.push_back(att); } - - prepare_transaction(destinations, - fake_outputs_count, - unlock_time, - 0, - extra, - attachments, - detail::digit_split_strategy, - tx_dust_policy(DEFAULT_DUST_THRESHOLD), - ecrow_details.b_addr, - tx, - CURRENCY_TO_KEY_OUT_RELAXED, - true, - false, - TX_FLAG_SIGNATURE_MODE_SEPARATE, - selected_transfers, - one_time_key, - prepared_destinations); + + finalize_tx_param ftp = AUTO_VAL_INIT(ftp); + prepare_transaction(ctp, ftp, tx); + + selected_transfers = ftp.selected_transfers; + + finalize_transaction(ftp, tx, one_time_key, false); } //---------------------------------------------------------------------------------------------------- void wallet2::add_transfers_to_expiration_list(const std::vector& selected_transfers, uint64_t expiration, uint64_t change_amount, const crypto::hash& related_tx_id) @@ -2834,7 +2938,7 @@ void wallet2::remove_transfer_from_expiration_list(uint64_t transfer_index) // (don't change m_spent flag, because transfer status is unclear - the caller should take care of it) } //---------------------------------------------------------------------------------------------------- -void wallet2::send_escrow_proposal(const bc_services::contract_private_details& ecrow_detaild, +void wallet2::send_escrow_proposal(const bc_services::contract_private_details& ecrow_details, size_t fake_outputs_count, uint64_t unlock_time, uint64_t expiration_period, @@ -2842,8 +2946,7 @@ void wallet2::send_escrow_proposal(const bc_services::contract_private_details& uint64_t b_release_fee, const std::string& payment_id, currency::transaction &tx, - currency::transaction &template_tx/*, - crypto::hash& contract_id*/) + currency::transaction &template_tx) { if (!is_connected_to_net()) { @@ -2851,54 +2954,41 @@ void wallet2::send_escrow_proposal(const bc_services::contract_private_details& "Transfer attempt while daemon offline"); } - using namespace currency; - std::vector selected_transfers; - std::vector destinations; - std::vector prepared_destinations; - std::vector selected_transfers_for_template; - - currency::keypair one_time_key = AUTO_VAL_INIT(one_time_key); + crypto::secret_key one_time_key = AUTO_VAL_INIT(one_time_key); uint64_t expiration_time = m_core_runtime_config.get_core_time() + expiration_period; - - build_escrow_template(ecrow_detaild, fake_outputs_count, unlock_time, expiration_time, b_release_fee, payment_id, template_tx, selected_transfers_for_template, one_time_key); + std::vector selected_transfers_for_template; + build_escrow_template(ecrow_details, fake_outputs_count, unlock_time, expiration_time, b_release_fee, payment_id, template_tx, selected_transfers_for_template, one_time_key); + crypto::hash ms_id = get_multisig_out_id(template_tx, 0); const uint32_t mask_to_mark_escrow_template_locked_transfers = WALLET_TRANSFER_DETAIL_FLAG_BLOCKED | WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION; - mark_transfers_with_flag(selected_transfers_for_template, mask_to_mark_escrow_template_locked_transfers, "preparing escrow template tx"); + mark_transfers_with_flag(selected_transfers_for_template, mask_to_mark_escrow_template_locked_transfers, "preparing escrow template tx, contract: " + epee::string_tools::pod_to_hex(ms_id)); - currency::tx_service_attachment att = AUTO_VAL_INIT(att); + construct_tx_param ctp = AUTO_VAL_INIT(ctp); bc_services::proposal_body pb = AUTO_VAL_INIT(pb); - pb.tx_onetime_secret_key = one_time_key.sec; + pb.tx_onetime_secret_key = one_time_key; pb.tx_template = template_tx; + currency::tx_service_attachment att = AUTO_VAL_INIT(att); att.body = t_serializable_object_to_blob(pb); att.service_id = BC_ESCROW_SERVICE_ID; att.instruction = BC_ESCROW_SERVICE_INSTRUCTION_PROPOSAL; att.flags = TX_SERVICE_ATTACHMENT_ENCRYPT_BODY | TX_SERVICE_ATTACHMENT_DEFLATE_BODY; + ctp.attachments.push_back(att); - std::vector extra; - std::vector attachments; - attachments.push_back(att); - currency::keypair one_time_key_transport_tx = AUTO_VAL_INIT(one_time_key_transport_tx); + ctp.crypt_address = ecrow_details.b_addr; + ctp.dust_policy = tx_dust_policy(DEFAULT_DUST_THRESHOLD); + ctp.fake_outputs_count = fake_outputs_count; + ctp.fee = fee; + ctp.shuffle = true; + ctp.split_strategy_id = detail::ssi_digit; + ctp.tx_outs_attr = CURRENCY_TO_KEY_OUT_RELAXED; + ctp.unlock_time = unlock_time; + finalize_tx_param ftp = AUTO_VAL_INIT(ftp); try { - prepare_transaction(destinations, - fake_outputs_count, - unlock_time, - fee, - extra, - attachments, - detail::digit_split_strategy, - tx_dust_policy(DEFAULT_DUST_THRESHOLD), - ecrow_detaild.b_addr, - tx, - CURRENCY_TO_KEY_OUT_RELAXED, - true, - false, - 0, - selected_transfers, - one_time_key_transport_tx, - prepared_destinations); + prepare_transaction(ctp, ftp); + finalize_transaction(ftp, tx, crypto::secret_key(), false); } catch (...) { @@ -2909,15 +2999,14 @@ void wallet2::send_escrow_proposal(const bc_services::contract_private_details& send_transaction_to_network(tx); - mark_transfers_as_spent(selected_transfers, std::string("escrow proposal sent, tx <") + epee::string_tools::pod_to_hex(get_transaction_hash(tx)) + ">"); - add_sent_tx_detailed_info(tx, prepared_destinations, selected_transfers); + mark_transfers_as_spent(ftp.selected_transfers, std::string("escrow proposal sent, tx <") + epee::string_tools::pod_to_hex(get_transaction_hash(tx)) + ">, contract: " + epee::string_tools::pod_to_hex(ms_id)); + add_sent_tx_detailed_info(tx, ftp.prepared_destinations, ftp.selected_transfers); print_tx_sent_message(tx, "(from multisig)", fee); } //---------------------------------------------------------------------------------------------------- bool wallet2::prepare_tx_sources(uint64_t needed_money, size_t fake_outputs_count, uint64_t dust_threshold, std::vector& sources, std::vector& selected_indicies, uint64_t& found_money) { - found_money = select_transfers(needed_money, fake_outputs_count, dust_threshold, selected_indicies); THROW_IF_FALSE_WALLET_EX_MES(found_money >= needed_money, error::not_enough_money, "wallet_dump: " << ENDL << dump_trunsfers(false), found_money, needed_money, 0); @@ -2933,29 +3022,28 @@ bool wallet2::prepare_tx_sources(uint64_t needed_money, size_t fake_outputs_coun for (uint64_t i: selected_indicies) { auto it = m_transfers.begin() + i; - THROW_IF_TRUE_WALLET_EX(it->m_ptx_wallet_info->m_tx.vout.size() <= it->m_internal_output_index, error::wallet_internal_error, - "m_internal_output_index = " + std::to_string(it->m_internal_output_index) + - " is greater or equal to outputs count = " + std::to_string(it->m_ptx_wallet_info->m_tx.vout.size())); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it->m_ptx_wallet_info->m_tx.vout.size() > it->m_internal_output_index, + "m_internal_output_index = " << it->m_internal_output_index << + " is greater or equal to outputs count = " << it->m_ptx_wallet_info->m_tx.vout.size()); req.amounts.push_back(it->amount()); } bool r = m_core_proxy->call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS(req, daemon_resp); - THROW_IF_TRUE_WALLET_EX(!r, error::no_connection_to_daemon, "getrandom_outs.bin"); - THROW_IF_TRUE_WALLET_EX(daemon_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "getrandom_outs.bin"); - THROW_IF_TRUE_WALLET_EX(daemon_resp.status != CORE_RPC_STATUS_OK, error::get_random_outs_error, daemon_resp.status); - THROW_IF_TRUE_WALLET_EX(daemon_resp.outs.size() != selected_indicies.size(), error::wallet_internal_error, - "daemon returned wrong response for getrandom_outs.bin, wrong amounts count = " + - std::to_string(daemon_resp.outs.size()) + ", expected " + std::to_string(selected_indicies.size())); + THROW_IF_FALSE_WALLET_EX(r, error::no_connection_to_daemon, "getrandom_outs.bin"); + THROW_IF_FALSE_WALLET_EX(daemon_resp.status != CORE_RPC_STATUS_BUSY, error::daemon_busy, "getrandom_outs.bin"); + THROW_IF_FALSE_WALLET_EX(daemon_resp.status == CORE_RPC_STATUS_OK, error::get_random_outs_error, daemon_resp.status); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(daemon_resp.outs.size() == selected_indicies.size(), + "daemon returned wrong response for getrandom_outs.bin, wrong amounts count = " << daemon_resp.outs.size() << ", expected: " << selected_indicies.size()); std::vector scanty_outs; - BOOST_FOREACH(COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& amount_outs, daemon_resp.outs) + for(COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& amount_outs : daemon_resp.outs) { if (amount_outs.outs.size() < req.outs_count) { scanty_outs.push_back(amount_outs); } } - THROW_IF_TRUE_WALLET_EX(!scanty_outs.empty(), error::not_enough_outs_to_mix, scanty_outs, fake_outputs_count); + THROW_IF_FALSE_WALLET_EX(scanty_outs.empty(), error::not_enough_outs_to_mix, scanty_outs, fake_outputs_count); } //prepare inputs @@ -2972,7 +3060,7 @@ bool wallet2::prepare_tx_sources(uint64_t needed_money, size_t fake_outputs_coun if (daemon_resp.outs.size()) { daemon_resp.outs[i].outs.sort([](const out_entry& a, const out_entry& b){return a.global_amount_index < b.global_amount_index; }); - BOOST_FOREACH(out_entry& daemon_oe, daemon_resp.outs[i].outs) + for(out_entry& daemon_oe : daemon_resp.outs[i].outs) { if (td.m_global_output_index == daemon_oe.global_amount_index) continue; @@ -3012,14 +3100,13 @@ bool wallet2::prepare_tx_sources(crypto::hash multisig_id, std::vectorsecond.is_spent(), "output with multisig_id: " + epee::string_tools::pod_to_hex(multisig_id) + " has already been spent by other party at height " + epee::string_tools::num_to_string_fast(it->second.m_spent_height)); - sources.push_back(AUTO_VAL_INIT(currency::tx_source_entry())); - currency::tx_source_entry& src = sources.back(); - THROW_IF_FALSE_WALLET_INT_ERR_EX(it->second.m_internal_output_index < it->second.m_ptx_wallet_info->m_tx.vout.size(), "it->second.m_internal_output_index < it->second.m_tx.vout.size()"); const tx_out& out = it->second.m_ptx_wallet_info->m_tx.vout[it->second.m_internal_output_index]; THROW_IF_FALSE_WALLET_INT_ERR_EX(out.target.type() == typeid(txout_multisig), "ms out target type is " << out.target.type().name() << ", expected: txout_multisig"); - const txout_multisig& ms_out = boost::get(it->second.m_ptx_wallet_info->m_tx.vout[it->second.m_internal_output_index].target); + const txout_multisig& ms_out = boost::get(out.target); + sources.push_back(AUTO_VAL_INIT(currency::tx_source_entry())); + currency::tx_source_entry& src = sources.back(); src.amount = found_money = out.amount; src.real_output_in_tx_index = it->second.m_internal_output_index; src.real_out_tx_key = get_tx_pub_key_from_extra(it->second.m_ptx_wallet_info->m_tx); @@ -3394,15 +3481,15 @@ std::string wallet2::get_alias_for_address(const std::string& addr) //---------------------------------------------------------------------------------------------------- void wallet2::transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector& extra, - const std::vector attachments, + const std::vector& attachments, currency::transaction& tx) { - transfer(dsts, fake_outputs_count, unlock_time, fee, extra, attachments, detail::digit_split_strategy, tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx); + transfer(dsts, fake_outputs_count, unlock_time, fee, extra, attachments, detail::ssi_digit, tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx); } //---------------------------------------------------------------------------------------------------- void wallet2::transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector& extra, - const std::vector attachments) + const std::vector& attachments) { currency::transaction tx; transfer(dsts, fake_outputs_count, unlock_time, fee, extra, attachments, tx); @@ -3483,5 +3570,273 @@ void wallet2::print_source_entry(const currency::tx_source_entry& src) const WLT_LOG_L0("amount=" << currency::print_money(src.amount) << ", real_output=" << src.real_output << ", real_output_in_tx_index=" << src.real_output_in_tx_index << ", indexes: " << indexes.str()); } //---------------------------------------------------------------------------------------------------- +bool wallet2::get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key) const +{ + const std::unordered_map::const_iterator i = m_tx_keys.find(txid); + if (i == m_tx_keys.end()) + return false; + tx_key = i->second; + return true; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::prepare_tx_destinations(uint64_t needed_money, + uint64_t found_money, + detail::split_strategy_id_t destination_split_strategy_id, + const tx_dust_policy& dust_policy, + const std::vector& dsts, + std::vector& final_detinations) +{ + currency::tx_destination_entry change_dts = AUTO_VAL_INIT(change_dts); + if (needed_money < found_money) + { + change_dts.addr.push_back(m_account.get_keys().m_account_address); + change_dts.amount = found_money - needed_money; + } + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(found_money >= needed_money, "needed_money==" << needed_money << " < found_money==" << found_money); + + uint64_t dust = 0; + bool r = detail::apply_split_strategy_by_id(destination_split_strategy_id, dsts, change_dts, dust_policy.dust_threshold, final_detinations, dust, WALLET_MAX_ALLOWED_OUTPUT_AMOUNT); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "invalid split strategy id: " << destination_split_strategy_id); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(dust_policy.dust_threshold >= dust, "invalid dust value: dust = " << dust << ", dust_threshold = " << dust_policy.dust_threshold); + + //@#@ +#ifdef _DEBUG + if (final_detinations.size() > 10) + { + WLT_LOG_L0("final_detinations.size()=" << final_detinations.size()); + } +#endif + //@#@ + + if (0 != dust && !dust_policy.add_to_fee) + { + final_detinations.push_back(currency::tx_destination_entry(dust, dust_policy.addr_for_dust)); + } +} +//---------------------------------------------------------------------------------------------------- +/*void wallet2::prepare_transaction(const std::vector& dsts, + size_t fake_outputs_count, + uint64_t unlock_time, + uint64_t fee, + const std::vector& extra, + const std::vector& attachments, + tools::detail::split_strategy_id_t destination_split_strategy_id, + const tx_dust_policy& dust_policy, + const currency::account_public_address& crypt_address, + currency::transaction &tx, + uint8_t tx_outs_attr, + bool shuffle, + bool mark_tx_as_complete, + uint8_t flags, + std::vector& selected_transfers, + currency::keypair& one_time_key, + std::vector& prepared_destinations, + crypto::hash multisig_id) +{ + // TODO +}*/ +//---------------------------------------------------------------------------------------------------- +void wallet2::prepare_transaction(const construct_tx_param& ctp, finalize_tx_param& ftp, const currency::transaction& tx_for_mode_separate /* = currency::transaction() */) +{ + TIME_MEASURE_START_MS(get_needed_money_time); + uint64_t needed_money = get_needed_money(ctp.fee, ctp.dsts); + if (ctp.flags & TX_FLAG_SIGNATURE_MODE_SEPARATE && tx_for_mode_separate.vout.size()) + { + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(get_tx_flags(tx_for_mode_separate) & TX_FLAG_SIGNATURE_MODE_SEPARATE, "tx_param.flags differs from tx.flags"); + needed_money += (currency::get_outs_money_amount(tx_for_mode_separate) - get_inputs_money_amount(tx_for_mode_separate)); + } + TIME_MEASURE_FINISH_MS(get_needed_money_time); + + uint64_t found_money = 0; + + TIME_MEASURE_START_MS(prepare_tx_sources_time); + if (ctp.multisig_id == currency::null_hash) + prepare_tx_sources(needed_money, ctp.fake_outputs_count, ctp.dust_policy.dust_threshold, ftp.sources, ftp.selected_transfers, found_money); + else + prepare_tx_sources(ctp.multisig_id, ftp.sources, found_money); + TIME_MEASURE_FINISH_MS(prepare_tx_sources_time); + + TIME_MEASURE_START_MS(prepare_tx_destinations_time); + prepare_tx_destinations(needed_money, found_money, static_cast(ctp.split_strategy_id), ctp.dust_policy, ctp.dsts, ftp.prepared_destinations); + TIME_MEASURE_FINISH_MS(prepare_tx_destinations_time); + + if (ctp.mark_tx_as_complete && !ftp.sources.empty()) + ftp.sources.back().separately_signed_tx_complete = true; + + + ftp.unlock_time = ctp.unlock_time; + ftp.extra = ctp.extra; // TODO consider move semantic + ftp.attachments = ctp.attachments; // TODO consider move semantic + ftp.crypt_address = ctp.crypt_address; + ftp.tx_outs_attr = ctp.tx_outs_attr; + ftp.shuffle = ctp.shuffle; + ftp.flags = ctp.flags; + ftp.multisig_id = ctp.multisig_id; + + /* TODO + WLT_LOG_GREEN("[prepare_transaction]: get_needed_money_time: " << get_needed_money_time << " ms" + << ", prepare_tx_sources_time: " << prepare_tx_sources_time << " ms" + << ", prepare_tx_destinations_time: " << prepare_tx_destinations_time << " ms" + << ", construct_tx_time: " << construct_tx_time << " ms" + << ", sign_ms_input_time: " << sign_ms_input_time << " ms", + LOG_LEVEL_0);*/ +} +//---------------------------------------------------------------------------------------------------- +void wallet2::finalize_transaction(const finalize_tx_param& ftp, currency::transaction& tx, crypto::secret_key& tx_key, bool broadcast_tx) +{ + TIME_MEASURE_START_MS(construct_tx_time); + bool r = currency::construct_tx(m_account.get_keys(), + ftp.sources, + ftp.prepared_destinations, + ftp.extra, + ftp.attachments, + tx, + tx_key, + ftp.unlock_time, + ftp.crypt_address, + 0, // expiration time + ftp.tx_outs_attr, + ftp.shuffle, + ftp.flags); + TIME_MEASURE_FINISH_MS(construct_tx_time); + THROW_IF_FALSE_WALLET_EX(r, error::tx_not_constructed, ftp.sources, ftp.prepared_destinations, ftp.unlock_time); + + TIME_MEASURE_START_MS(sign_ms_input_time); + if (ftp.multisig_id != currency::null_hash) + { + // In case there's multisig input is used -- sign it partially with this wallet's keys (we don't have any others here). + // NOTE: this tx will not be ready to send until all other necessary signs for ms input would made. + auto it = m_multisig_transfers.find(ftp.multisig_id); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it != m_multisig_transfers.end(), "can't find multisig_id: " << ftp.multisig_id); + const currency::transaction& ms_source_tx = it->second.m_ptx_wallet_info->m_tx; + bool is_tx_input_fully_signed = false; + r = sign_multisig_input_in_tx(tx, 0, m_account.get_keys(), ms_source_tx, &is_tx_input_fully_signed); // it's assumed that ms input is the first one (index 0) + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(r && !is_tx_input_fully_signed, "sign_multisig_input_in_tx failed: r = " << r << ", is_tx_input_fully_signed = " << is_tx_input_fully_signed); + } + TIME_MEASURE_FINISH_MS(sign_ms_input_time); + + m_tx_keys.insert(std::make_pair(get_transaction_hash(tx), tx_key)); + + THROW_IF_FALSE_WALLET_EX(get_object_blobsize(tx) < CURRENCY_MAX_TRANSACTION_BLOB_SIZE, error::tx_too_big, tx, m_upper_transaction_size_limit); + + TIME_MEASURE_START(send_transaction_to_network_time); + if (broadcast_tx) + send_transaction_to_network(tx); + TIME_MEASURE_FINISH(send_transaction_to_network_time); + + TIME_MEASURE_START(add_sent_tx_detailed_info_time); + if (broadcast_tx) + add_sent_tx_detailed_info(tx, ftp.prepared_destinations, ftp.selected_transfers); + TIME_MEASURE_FINISH(add_sent_tx_detailed_info_time); + + /* TODO + WLT_LOG_GREEN("[prepare_transaction]: get_needed_money_time: " << get_needed_money_time << " ms" + << ", prepare_tx_sources_time: " << prepare_tx_sources_time << " ms" + << ", prepare_tx_destinations_time: " << prepare_tx_destinations_time << " ms" + << ", construct_tx_time: " << construct_tx_time << " ms" + << ", sign_ms_input_time: " << sign_ms_input_time << " ms", + LOG_LEVEL_0);*/ +} +//---------------------------------------------------------------------------------------------------- +void wallet2::transfer(const std::vector& dsts, size_t fake_outputs_count, + uint64_t unlock_time, uint64_t fee, const std::vector& extra, const std::vector& attachments, detail::split_strategy_id_t destination_split_strategy_id, const tx_dust_policy& dust_policy) +{ + currency::transaction tx; + 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, + uint64_t fee, + const std::vector& extra, + const std::vector& attachments, + detail::split_strategy_id_t destination_split_strategy_id, + const tx_dust_policy& dust_policy, + currency::transaction &tx, + uint8_t tx_outs_attr, + bool shuffle, + uint8_t flags, + bool send_to_network) +{ + TIME_MEASURE_START(precalculation_time); + //using namespace currency; + currency::keypair onetime_keys = AUTO_VAL_INIT(onetime_keys); + + 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; + TIME_MEASURE_FINISH(precalculation_time); + + TIME_MEASURE_START(prepare_transaction_time); + finalize_tx_param ftp = AUTO_VAL_INIT(ftp); + prepare_transaction(tx_param, ftp); + TIME_MEASURE_FINISH(prepare_transaction_time); + + + if (m_watch_only) + { + blobdata bl = t_serializable_object_to_blob(tx_param); + crypto::chacha_crypt(bl, m_account.get_keys().m_view_secret_key); + epee::file_io_utils::save_string_to_file("unsigned_zano_tx", bl); + LOG_PRINT_L0("Transaction stored to unsigned_zano_tx. You need to sign this tx using a full-access wallet."); + //relay_blob = bl; // TODO: save encrypted tx param blob to relay_blob + + // 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")); + TIME_MEASURE_FINISH(mark_transfers_as_spent_time); + + return; + } + + TIME_MEASURE_START(finalize_transaction_time); + finalize_transaction(ftp, tx, crypto::secret_key(), send_to_network); + TIME_MEASURE_FINISH(finalize_transaction_time); + + // 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, tx: ") + epee::string_tools::pod_to_hex(get_transaction_hash(tx))); + TIME_MEASURE_FINISH(mark_transfers_as_spent_time); + + /* TODO + WLT_LOG_GREEN("[wallet::transfer] prepare_transaction_time: " << print_fixed_decimal_point(prepare_transaction_time, 3) + << ", precalculation_time: " << print_fixed_decimal_point(precalculation_time, 3) + << ", send_transaction_to_network_time: " << print_fixed_decimal_point(send_transaction_to_network_time, 3) + << ", mark_transfers_as_spent_time: " << print_fixed_decimal_point(mark_transfers_as_spent_time, 3) + << ", add_sent_tx_detailed_info_time: " << print_fixed_decimal_point(add_sent_tx_detailed_info_time, 3), + LOG_LEVEL_0);*/ + + //print_tx_sent_message(tx, std::string() + "(transaction)", fee); +} + } // namespace tools diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index f8f0af05..1599cdd1 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -37,6 +37,7 @@ #include "currency_core/offers_services_helpers.h" #include "currency_core/bc_offers_serialization.h" #include "currency_core/bc_escrow_service.h" +#include "common/pod_array_file_container.h" #define WALLET_DEFAULT_TX_SPENDABLE_AGE 10 @@ -61,6 +62,7 @@ ENABLE_CHANNEL_BY_DEFAULT("wallet"); #define WLT_LOG_YELLOW(msg, log_level) LOG_PRINT_YELLOW("[W:" << m_log_prefix << "]" << msg, log_level) #define WLT_CHECK_AND_ASSERT_MES(expr, ret, msg) CHECK_AND_ASSERT_MES(expr, ret, "[W:" << m_log_prefix << "]" << msg) #define WLT_CHECK_AND_ASSERT_MES_NO_RET(expr, msg) CHECK_AND_ASSERT_MES_NO_RET(expr, "[W:" << m_log_prefix << "]" << msg) +#define WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(cond, msg) THROW_IF_FALSE_WALLET_INT_ERR_EX(cond, "[W:" << m_log_prefix << "]" << msg) namespace tools { @@ -105,22 +107,233 @@ namespace tools , addr_for_dust(an_addr_for_dust) { } - }; - - - - + BEGIN_SERIALIZE_OBJECT() + FIELD(dust_threshold) + FIELD(add_to_fee) + FIELD(addr_for_dust) + END_SERIALIZE() + }; class test_generator; +#pragma pack(push, 1) + struct out_key_to_ki + { + out_key_to_ki() {} + out_key_to_ki(const crypto::public_key& out_key, const crypto::key_image& key_image) : out_key(out_key), key_image(key_image) {} + crypto::public_key out_key; + crypto::key_image key_image; + }; +#pragma pack(pop) + + typedef tools::pod_array_file_container pending_ki_file_container_t; + + namespace detail + { + //---------------------------------------------------------------------------------------------------- + inline void digit_split_strategy(const std::vector& dsts, + const currency::tx_destination_entry& change_dst, uint64_t dust_threshold, + std::vector& splitted_dsts, uint64_t& dust, uint64_t max_output_allowed) + { + splitted_dsts.clear(); + dust = 0; + + for(auto& de : dsts) + { + if (de.addr.size() > 1) + { + //for multisig we don't split + splitted_dsts.push_back(de); + } + else + { + currency::decompose_amount_into_digits(de.amount, dust_threshold, + [&](uint64_t chunk) { splitted_dsts.push_back(currency::tx_destination_entry(chunk, de.addr)); }, + [&](uint64_t a_dust) { splitted_dsts.push_back(currency::tx_destination_entry(a_dust, de.addr)); }, max_output_allowed); + } + } + + if (change_dst.amount > 0) + { + if (change_dst.addr.size() > 1) + { + //for multisig we don't split + splitted_dsts.push_back(change_dst); + } + else + { + currency::decompose_amount_into_digits(change_dst.amount, dust_threshold, + [&](uint64_t chunk) { splitted_dsts.push_back(currency::tx_destination_entry(chunk, change_dst.addr)); }, + [&](uint64_t a_dust) { dust = a_dust; }, max_output_allowed); + } + } + } + //---------------------------------------------------------------------------------------------------- + inline void null_split_strategy(const std::vector& dsts, + const currency::tx_destination_entry& change_dst, uint64_t dust_threshold, + std::vector& splitted_dsts, uint64_t& dust, uint64_t max_output_allowed) + { + splitted_dsts = dsts; + + dust = 0; + uint64_t change = change_dst.amount; + if (0 < dust_threshold) + { + for (uint64_t order = 10; order <= 10 * dust_threshold; order *= 10) + { + uint64_t dust_candidate = change_dst.amount % order; + uint64_t change_candidate = (change_dst.amount / order) * order; + if (dust_candidate <= dust_threshold) + { + dust = dust_candidate; + change = change_candidate; + } + else + { + break; + } + } + } + + if (0 != change) + { + splitted_dsts.push_back(currency::tx_destination_entry(change, change_dst.addr)); + } + } + //---------------------------------------------------------------------------------------------------- + inline void void_split_strategy(const std::vector& dsts, + const currency::tx_destination_entry& change_dst, uint64_t dust_threshold, + std::vector& splitted_dsts, uint64_t& dust, uint64_t max_output_allowed) + { + splitted_dsts = dsts; + if (change_dst.amount > 0) + splitted_dsts.push_back(change_dst); + } + //---------------------------------------------------------------------------------------------------- + enum split_strategy_id_t { ssi_none = 0, ssi_digit = 1, ssi_null = 2, ssi_void = 3 }; + //---------------------------------------------------------------------------------------------------- + inline bool apply_split_strategy_by_id(split_strategy_id_t id, const std::vector& dsts, + const currency::tx_destination_entry& change_dst, uint64_t dust_threshold, + std::vector& splitted_dsts, uint64_t& dust, uint64_t max_output_allowed) + { + switch (id) + { + case ssi_digit: + digit_split_strategy(dsts, change_dst, dust_threshold, splitted_dsts, dust, max_output_allowed); + return true; + case ssi_null: + null_split_strategy(dsts, change_dst, dust_threshold, splitted_dsts, dust, max_output_allowed); + return true; + case ssi_void: + void_split_strategy(dsts, change_dst, dust_threshold, splitted_dsts, dust, max_output_allowed); + return true; + default: + return false; + } + } + + } // namespace detail + + struct construct_tx_param + { + // preparing data for tx + std::vector dsts; + size_t fake_outputs_count; + uint64_t fee; + tx_dust_policy dust_policy; + crypto::hash multisig_id; + uint8_t flags; + uint8_t split_strategy_id; + bool mark_tx_as_complete; + + // constructing tx + uint64_t unlock_time; + std::vector extra; + std::vector attachments; + currency::account_public_address crypt_address; + uint8_t tx_outs_attr; + bool shuffle; + + 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) + 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; + currency::account_public_address crypt_address; + uint8_t tx_outs_attr; + bool shuffle; + uint8_t flags; + crypto::hash multisig_id; + std::vector sources; + std::vector selected_transfers; + std::vector prepared_destinations; + + + BEGIN_SERIALIZE_OBJECT() + FIELD(unlock_time) + FIELD(extra) + FIELD(attachments) + FIELD(crypt_address) + FIELD(tx_outs_attr) + FIELD(shuffle) + FIELD(flags) + FIELD(multisig_id) + FIELD(sources) + FIELD(selected_transfers) + FIELD(prepared_destinations) + END_SERIALIZE() + }; + + /*uct constructed_tx_data + { + //std::vector prepared_destinations; + currency::transaction tx; + //std::vector selected_transfers; + crypto::secret_key one_time_key; + + BEGIN_SERIALIZE_OBJECT() + //FIELD(prepared_destinations) + FIELD(tx) + //FIELD(selected_transfers) + FIELD(one_time_key) + END_SERIALIZE() + };*/ + + class wallet2 { wallet2(const wallet2&) : m_stop(false), m_wcallback(new i_wallet2_callback()), m_height_of_start_sync(0), m_last_sync_percent(0), - m_do_rise_transfer(false) + m_do_rise_transfer(false), + m_watch_only(false) {}; public: wallet2() : m_stop(false), @@ -131,7 +344,8 @@ namespace tools m_last_sync_percent(0), m_fake_outputs_count(0), m_do_rise_transfer(false), - m_log_prefix("???") + m_log_prefix("???"), + m_watch_only(false) { m_core_runtime_config = currency::get_default_core_runtime_config(); }; @@ -298,24 +512,22 @@ namespace tools void transfer(uint64_t amount, const currency::account_public_address& acc); - template void transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector& extra, - const std::vector attachments, - T destination_split_strategy, + const std::vector& attachments, + detail::split_strategy_id_t destination_split_strategy_id, const tx_dust_policy& dust_policy); - template void transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector& extra, - const std::vector attachments, - destination_split_strategy_t destination_split_strategy, + const std::vector& attachments, + detail::split_strategy_id_t destination_split_strategy_id, const tx_dust_policy& dust_policy, currency::transaction &tx, uint8_t tx_outs_attr = CURRENCY_TO_KEY_OUT_RELAXED, @@ -328,14 +540,14 @@ namespace tools uint64_t unlock_time, uint64_t fee, const std::vector& extra, - const std::vector attachments); + const std::vector& attachments); void transfer(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector& extra, - const std::vector attachments, + const std::vector& attachments, currency::transaction& tx); template @@ -347,7 +559,7 @@ namespace tools uint64_t unlock_time, uint64_t fee, const std::vector& extra, - const std::vector attachments, + const std::vector& attachments, destination_split_strategy_t destination_split_strategy, const tx_dust_policy& dust_policy, currency::transaction &tx, @@ -378,7 +590,7 @@ namespace tools const std::string& payment_id, currency::transaction& tx, std::vector& selected_transfers, - currency::keypair& one_time_key); + crypto::secret_key& one_time_key); void send_escrow_proposal(const bc_services::contract_private_details& ecrow_detaild, size_t fake_outputs_count, @@ -388,8 +600,7 @@ namespace tools uint64_t b_release_fee, const std::string& payment_id, currency::transaction &proposal_tx, - currency::transaction &escrow_template_tx /*, - crypto::hash& contract_id*/ ); + currency::transaction &escrow_template_tx); bool check_connection(); template //do refresh as external callback @@ -398,7 +609,11 @@ namespace tools void get_transfers(wallet2::transfer_container& incoming_transfers) const; // Returns all payments by given id in unspecified order - void get_payments(const std::string& payment_id, std::list& payments) const; + void get_payments(const std::string& payment_id, std::list& payments, uint64_t min_height = 0) const; + + bool is_watch_only() const { return m_watch_only; } + void sign_transfer(const std::string& tx_sources_blob, std::string& signed_tx_blob, currency::transaction& tx); + bool get_transfer_address(const std::string& adr_str, currency::account_public_address& addr, std::string& payment_id); uint64_t get_blockchain_current_height() const { return m_blockchain.size(); } @@ -413,6 +628,7 @@ namespace tools a & m_key_images; a & m_unconfirmed_txs; a & m_unconfirmed_multisig_transfers; + a & m_tx_keys; a & m_payments; a & m_transfer_history; a & m_unconfirmed_in_transfers; @@ -477,26 +693,29 @@ namespace tools bool get_contracts(escrow_contracts_container& contracts); const std::list& get_expiration_entries() const { return m_money_expirations; }; + bool get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key) const; - template - void prepare_transaction(const std::vector& dsts, + /*void prepare_transaction(const std::vector& dsts, size_t fake_outputs_count, uint64_t unlock_time, uint64_t fee, const std::vector& extra, - const std::vector attachments, - destination_split_strategy_t destination_split_strategy, + const std::vector& attachments, + detail::split_strategy_id_t destination_split_strategy_id, const tx_dust_policy& dust_policy, const currency::account_public_address& crypt_address, - currency::transaction &tx, + OUT currency::transaction &tx, uint8_t tx_outs_attr, bool shuffle, bool mark_tx_as_complete, uint8_t flags, - std::vector& selected_transfers, - currency::keypair& one_time_key, - std::vector& prepared_destinations, - crypto::hash multisig_id = currency::null_hash); + OUT std::vector& selected_transfers, + OUT currency::keypair& one_time_key, + OUT std::vector& prepared_destinations, + crypto::hash multisig_id = currency::null_hash);*/ + + void prepare_transaction(const construct_tx_param& ctp, finalize_tx_param& ftp, const currency::transaction& tx_for_mode_separate = currency::transaction()); + void finalize_transaction(const finalize_tx_param& ftp, currency::transaction& tx, crypto::secret_key& tx_key, bool broadcast_tx); std::string get_log_prefix() const { return m_log_prefix; } @@ -520,7 +739,7 @@ private: uint64_t select_transfers(uint64_t needed_money, size_t fake_outputs_count, uint64_t dust, std::vector& selected_indicies); void add_transfers_to_transfers_cache(const std::vector& indexs); void add_transfer_to_transfers_cache(uint64_t amount, uint64_t index); - bool prepare_file_names(const std::string& file_path); + bool prepare_file_names(const std::wstring& file_path); void process_unconfirmed(const currency::transaction& tx, std::vector& recipients, std::vector& recipients_aliases); void add_sent_unconfirmed_tx(const currency::transaction& tx, const std::vector& recipients, @@ -557,10 +776,9 @@ private: bool prepare_tx_sources(uint64_t needed_money, size_t fake_outputs_count, uint64_t dust_threshold, std::vector& sources, std::vector& selected_indicies, uint64_t& found_money); bool prepare_tx_sources(crypto::hash multisig_id, std::vector& sources, uint64_t& found_money); uint64_t get_needed_money(uint64_t fee, const std::vector& dsts); - template void prepare_tx_destinations(uint64_t needed_money, uint64_t found_money, - destination_split_strategy_t destination_split_strategy, + detail::split_strategy_id_t destination_split_strategy_id, const tx_dust_policy& dust_policy, const std::vector& dsts, std::vector& final_detinations); @@ -600,37 +818,7 @@ private: void print_source_entry(const currency::tx_source_entry& src) const; void init_log_prefix(); - - struct construct_tx_param - { - std::vector dsts; - size_t fake_outputs_count; - uint64_t unlock_time; - uint64_t fee; - std::vector extra; - std::vector attachments; - tx_dust_policy dust_policy; - currency::account_public_address crypt_address; - uint8_t tx_outs_attr; - bool shuffle; - bool mark_tx_as_complete; - uint8_t flags; - crypto::hash multisig_id; - }; - - struct constructed_tx_data - { - std::vector prepared_destinations; - currency::transaction tx; - std::vector selected_transfers; - currency::keypair one_time_key; - }; - - - template - void prepare_transaction(const construct_tx_param& construct_tx_data, - constructed_tx_data& res, destination_split_strategy_t destination_split_strategy); - + void load_keys2ki(bool create_if_not_exist, bool& need_to_resync); void send_transaction_to_network(const currency::transaction& tx); void add_sent_tx_detailed_info(const currency::transaction& tx, @@ -646,8 +834,10 @@ private: currency::account_base m_account; + bool m_watch_only; std::string m_log_prefix; // part of pub address, prefix for logging functions std::wstring m_wallet_file; + std::wstring m_pending_ki_file; std::string m_password; std::vector m_blockchain; std::atomic m_local_bc_height; //temporary workaround @@ -658,6 +848,8 @@ private: multisig_transfer_container m_multisig_transfers; payment_container m_payments; std::unordered_map m_key_images; + std::unordered_map m_pending_key_images; // (out_pk -> ki) pairs of change outputs to be added in watch-only wallet without spend sec key + pending_ki_file_container_t m_pending_key_images_file_container; uint64_t m_upper_transaction_size_limit; //TODO: auto-calc this value or request from daemon, now use some fixed value std::atomic m_stop; @@ -665,6 +857,7 @@ private: std::unordered_map m_unconfirmed_in_transfers; std::unordered_map m_unconfirmed_txs; std::unordered_set m_unconfirmed_multisig_transfers; + std::unordered_map m_tx_keys; std::shared_ptr m_core_proxy; std::shared_ptr m_wcallback; @@ -680,10 +873,9 @@ private: std::string m_miner_text_info; - }; - -} + }; // class wallet2 +} // namespace tools BOOST_CLASS_VERSION(tools::wallet2, WALLET_FILE_SERIALIZATION_VERSION) BOOST_CLASS_VERSION(tools::wallet_rpc::wallet_transfer_info, 9) @@ -814,322 +1006,6 @@ namespace boost namespace tools { - namespace detail - { - - - - //---------------------------------------------------------------------------------------------------- - inline void digit_split_strategy(const std::vector& dsts, - const currency::tx_destination_entry& change_dst, uint64_t dust_threshold, - std::vector& splitted_dsts, uint64_t& dust, uint64_t max_output_allowed) - { - splitted_dsts.clear(); - dust = 0; - - BOOST_FOREACH(auto& de, dsts) - { - if (de.addr.size() > 1) - { - //for multisig we don't split - splitted_dsts.push_back(de); - } - else - { - currency::decompose_amount_into_digits(de.amount, dust_threshold, - [&](uint64_t chunk) { splitted_dsts.push_back(currency::tx_destination_entry(chunk, de.addr)); }, - [&](uint64_t a_dust) { splitted_dsts.push_back(currency::tx_destination_entry(a_dust, de.addr)); }, max_output_allowed); - } - } - - if (change_dst.amount > 0) - { - if (change_dst.addr.size() > 1) - { - //for multisig we don't split - splitted_dsts.push_back(change_dst); - } - else - { - currency::decompose_amount_into_digits(change_dst.amount, dust_threshold, - [&](uint64_t chunk) { splitted_dsts.push_back(currency::tx_destination_entry(chunk, change_dst.addr)); }, - [&](uint64_t a_dust) { dust = a_dust; }, max_output_allowed); - - } - } - } - //---------------------------------------------------------------------------------------------------- - - inline void null_split_strategy(const std::vector& dsts, - const currency::tx_destination_entry& change_dst, uint64_t dust_threshold, - std::vector& splitted_dsts, uint64_t& dust, uint64_t max_output_allowed) - { - splitted_dsts = dsts; - - dust = 0; - uint64_t change = change_dst.amount; - if (0 < dust_threshold) - { - for (uint64_t order = 10; order <= 10 * dust_threshold; order *= 10) - { - uint64_t dust_candidate = change_dst.amount % order; - uint64_t change_candidate = (change_dst.amount / order) * order; - if (dust_candidate <= dust_threshold) - { - dust = dust_candidate; - change = change_candidate; - } - else - { - break; - } - } - } - - if (0 != change) - { - splitted_dsts.push_back(currency::tx_destination_entry(change, change_dst.addr)); - } - } - //---------------------------------------------------------------------------------------------------- - inline void void_split_strategy(const std::vector& dsts, - const currency::tx_destination_entry& change_dst, uint64_t dust_threshold, - std::vector& splitted_dsts, uint64_t& dust, uint64_t max_output_allowed) - { - splitted_dsts = dsts; - if (change_dst.amount > 0) - splitted_dsts.push_back(change_dst); - } - //---------------------------------------------------------------------------------------------------- - } - //---------------------------------------------------------------------------------------------------- - template - void wallet2::transfer(const std::vector& dsts, size_t fake_outputs_count, - uint64_t unlock_time, uint64_t fee, const std::vector& extra, const std::vector attachments, T destination_split_strategy, const tx_dust_policy& dust_policy) - { - currency::transaction tx; - transfer(dsts, fake_outputs_count, unlock_time, fee, extra, attachments, destination_split_strategy, dust_policy, tx); - } - //---------------------------------------------------------------------------------------------------- - template - void wallet2::prepare_tx_destinations(uint64_t needed_money, - uint64_t found_money, - destination_split_strategy_t destination_split_strategy, - const tx_dust_policy& dust_policy, - const std::vector& dsts, - std::vector& final_detinations) - { - currency::tx_destination_entry change_dts = AUTO_VAL_INIT(change_dts); - if (needed_money < found_money) - { - change_dts.addr.push_back(m_account.get_keys().m_account_address); - change_dts.amount = found_money - needed_money; - } - THROW_IF_FALSE_WALLET_EX(found_money >= needed_money, error::wallet_internal_error, "needed_money(" + std::to_string(needed_money) - + ") < found_money(" + std::to_string(found_money) + ") "); - - uint64_t dust = 0; - destination_split_strategy(dsts, change_dts, dust_policy.dust_threshold, final_detinations, dust, WALLET_MAX_ALLOWED_OUTPUT_AMOUNT); - THROW_IF_FALSE_WALLET_EX(dust_policy.dust_threshold >= dust, error::wallet_internal_error, "invalid dust value: dust = " + - std::to_string(dust) + ", dust_threshold = " + std::to_string(dust_policy.dust_threshold)); - - //@#@ -#ifdef _DEBUG - if (final_detinations.size() > 10) - { - WLT_LOG_L0("final_detinations.size()=" << final_detinations.size()); - } -#endif - //@#@ - - if (0 != dust && !dust_policy.add_to_fee) - { - final_detinations.push_back(currency::tx_destination_entry(dust, dust_policy.addr_for_dust)); - } - } - - //---------------------------------------------------------------------------------------------------- - template - void wallet2::prepare_transaction(const std::vector& dsts, - size_t fake_outputs_count, - uint64_t unlock_time, - uint64_t fee, - const std::vector& extra, - const std::vector attachments, - destination_split_strategy_t destination_split_strategy, - const tx_dust_policy& dust_policy, - const currency::account_public_address& crypt_address, - currency::transaction &tx, - uint8_t tx_outs_attr, - bool shuffle, - bool mark_tx_as_complete, - uint8_t flags, - std::vector& selected_transfers, - currency::keypair& one_time_key, - std::vector& prepared_destinations, - crypto::hash multisig_id) - { - TIME_MEASURE_START_MS(get_needed_money_time); - uint64_t needed_money = get_needed_money(fee, dsts); - if (flags&TX_FLAG_SIGNATURE_MODE_SEPARATE && tx.vout.size()) - { - needed_money += (currency::get_outs_money_amount(tx) - get_inputs_money_amount(tx)); - } - TIME_MEASURE_FINISH_MS(get_needed_money_time); - - std::vector sources; - uint64_t found_money = 0; - - TIME_MEASURE_START_MS(prepare_tx_sources_time); - if (multisig_id == currency::null_hash) - { - prepare_tx_sources(needed_money, fake_outputs_count, dust_policy.dust_threshold, sources, selected_transfers, found_money); - } - else - { - prepare_tx_sources(multisig_id, sources, found_money); - } - TIME_MEASURE_FINISH_MS(prepare_tx_sources_time); - - TIME_MEASURE_START_MS(prepare_tx_destinations_time); - prepare_tx_destinations(needed_money, found_money, destination_split_strategy, dust_policy, dsts, prepared_destinations); - TIME_MEASURE_FINISH_MS(prepare_tx_destinations_time); - - if (mark_tx_as_complete && !sources.empty()) - sources.back().separately_signed_tx_complete = true; - - TIME_MEASURE_START_MS(construct_tx_time); - bool r = currency::construct_tx(m_account.get_keys(), - sources, - prepared_destinations, - extra, - attachments, - tx, - one_time_key.sec, - unlock_time, - crypt_address, - 0, - tx_outs_attr, - shuffle, - flags); - TIME_MEASURE_FINISH_MS(construct_tx_time); - THROW_IF_TRUE_WALLET_EX(!r, error::tx_not_constructed, sources, prepared_destinations, unlock_time); - - TIME_MEASURE_START_MS(sign_ms_input_time); - if (multisig_id != currency::null_hash) - { - // In case there's multisig input is used -- sign it partially with this wallet's keys (we don't have any others here). - // NOTE: this tx will not be ready to send until all other necessary signs for ms input would made. - auto it = m_multisig_transfers.find(multisig_id); - THROW_IF_FALSE_WALLET_INT_ERR_EX(it != m_multisig_transfers.end(), "can't find multisig_id: " << multisig_id); - const currency::transaction& ms_source_tx = it->second.m_ptx_wallet_info->m_tx; - bool is_tx_input_fully_signed = false; - r = sign_multisig_input_in_tx(tx, 0, m_account.get_keys(), ms_source_tx, &is_tx_input_fully_signed); - THROW_IF_FALSE_WALLET_INT_ERR_EX(r && !is_tx_input_fully_signed, "sign_multisig_input_in_tx failed: r = " << r << ", is_tx_input_fully_signed = " << is_tx_input_fully_signed); - } - TIME_MEASURE_FINISH_MS(sign_ms_input_time); - - THROW_IF_TRUE_WALLET_EX(CURRENCY_MAX_TRANSACTION_BLOB_SIZE <= get_object_blobsize(tx), error::tx_too_big, tx, m_upper_transaction_size_limit); - WLT_LOG_GREEN("[prepare_transaction]: get_needed_money_time: " << get_needed_money_time << " ms" - << ", prepare_tx_sources_time: " << prepare_tx_sources_time << " ms" - << ", prepare_tx_destinations_time: " << prepare_tx_destinations_time << " ms" - << ", construct_tx_time: " << construct_tx_time << " ms" - << ", sign_ms_input_time: " << sign_ms_input_time << " ms", - LOG_LEVEL_0); - } - - template - void wallet2::prepare_transaction(const construct_tx_param& construct_tx_data, constructed_tx_data& res, - destination_split_strategy_t destination_split_strategy) - { - return prepare_transaction(construct_tx_data.dsts, - construct_tx_data.fake_outputs_count, - construct_tx_data.unlock_time, - construct_tx_data.fee, - construct_tx_data.extra, - construct_tx_data.attachments, - destination_split_strategy, - construct_tx_data.dust_policy, - construct_tx_data.crypt_address, - res.tx, - construct_tx_data.tx_outs_attr, - construct_tx_data.shuffle, - construct_tx_data.mark_tx_as_complete, - construct_tx_data.flags, - res.selected_transfers, - res.one_time_key, - res.prepared_destinations, - construct_tx_data.multisig_id); - } - - - //---------------------------------------------------------------------------------------------------- - template - void wallet2::transfer(const std::vector& dsts, - size_t fake_outputs_count, - uint64_t unlock_time, - uint64_t fee, - const std::vector& extra, - const std::vector attachments, - destination_split_strategy_t destination_split_strategy, - const tx_dust_policy& dust_policy, - currency::transaction &tx, - uint8_t tx_outs_attr, - bool shuffle, - uint8_t flags, - bool send_to_network) - { - TIME_MEASURE_START(precalculation_time); - using namespace currency; - std::vector selected_transfers; - std::vector prepared_destinations; - account_public_address crypt_address = currency::get_crypt_address_from_destinations(m_account.get_keys(), dsts); - currency::keypair onetime_keys = AUTO_VAL_INIT(onetime_keys); - TIME_MEASURE_FINISH(precalculation_time); - - TIME_MEASURE_START(prepare_transaction_time); - prepare_transaction(dsts, - fake_outputs_count, - unlock_time, - fee, - extra, - attachments, - destination_split_strategy, - dust_policy, - crypt_address, - tx, - tx_outs_attr, - shuffle, - false, - flags, - selected_transfers, - onetime_keys, - prepared_destinations); - TIME_MEASURE_FINISH(prepare_transaction_time); - - TIME_MEASURE_START(send_transaction_to_network_time); - if (send_to_network) - send_transaction_to_network(tx); - TIME_MEASURE_FINISH(send_transaction_to_network_time); - - TIME_MEASURE_START(mark_transfers_as_spent_time); - mark_transfers_as_spent(selected_transfers, std::string("money transfer")); - TIME_MEASURE_FINISH(mark_transfers_as_spent_time); - - TIME_MEASURE_START(add_sent_tx_detailed_info_time); - add_sent_tx_detailed_info(tx, prepared_destinations, selected_transfers); - TIME_MEASURE_FINISH(add_sent_tx_detailed_info_time); - - WLT_LOG_GREEN("[wallet::transfer] prepare_transaction_time: " << print_fixed_decimal_point(prepare_transaction_time, 3) - << ", precalculation_time: " << print_fixed_decimal_point(precalculation_time, 3) - << ", send_transaction_to_network_time: " << print_fixed_decimal_point(send_transaction_to_network_time, 3) - << ", mark_transfers_as_spent_time: " << print_fixed_decimal_point(mark_transfers_as_spent_time, 3) - << ", add_sent_tx_detailed_info_time: " << print_fixed_decimal_point(add_sent_tx_detailed_info_time, 3), - LOG_LEVEL_0); - - //print_tx_sent_message(tx, std::string() + "(transaction)", fee); - } - //---------------------------------------------------------------------------------------------------- template //do refresh as external callback bool wallet2::scan_pos(mining_context& cxt, std::atomic& stop, @@ -1231,8 +1107,7 @@ namespace tools return false; } - -} +} // namespace tools #if !defined(KEEP_WALLET_LOG_MACROS) #undef WLT_LOG_L0 @@ -1249,6 +1124,7 @@ namespace tools #undef WLT_LOG_YELLOW #undef WLT_CHECK_AND_ASSERT_MES #undef WLT_CHECK_AND_ASSERT_MES_NO_RET +// TODO update this list #endif diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index 6e608769..d5b2009f 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -124,6 +124,14 @@ namespace tools } }; //---------------------------------------------------------------------------------------------------- + struct wallet_common_error : public wallet_runtime_error + { + explicit wallet_common_error(std::string&& loc, const std::string& message) + : wallet_runtime_error(std::move(loc), message) + { + } + }; + //---------------------------------------------------------------------------------------------------- struct unexpected_txin_type : public wallet_internal_error { explicit unexpected_txin_type(std::string&& loc, const currency::transaction& tx) diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 5d7846b2..047e7839 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -201,7 +201,7 @@ namespace tools bool wallet_rpc_server::on_get_payments(const wallet_rpc::COMMAND_RPC_GET_PAYMENTS::request& req, wallet_rpc::COMMAND_RPC_GET_PAYMENTS::response& res, epee::json_rpc::error& er, connection_context& cntx) { std::string payment_id; - if (!epee::string_tools::parse_hexstr_to_binbuff(req.payment_id, payment_id)) + if (!currency::parse_payment_id_from_hex_str(req.payment_id, payment_id)) { er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; er.message = std::string("invalid payment id given: \'") + req.payment_id + "\', hex-encoded string was expected"; @@ -214,6 +214,7 @@ namespace tools for (auto payment : payment_list) { wallet_rpc::payment_details rpc_payment; + rpc_payment.payment_id = req.payment_id; rpc_payment.tx_hash = epee::string_tools::pod_to_hex(payment.m_tx_hash); rpc_payment.amount = payment.m_amount; rpc_payment.block_height = payment.m_block_height; @@ -224,6 +225,38 @@ namespace tools return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_get_bulk_payments(const wallet_rpc::COMMAND_RPC_GET_BULK_PAYMENTS::request& req, wallet_rpc::COMMAND_RPC_GET_BULK_PAYMENTS::response& res, epee::json_rpc::error& er, connection_context& cntx) + { + res.payments.clear(); + + for (auto & payment_id_str : req.payment_ids) + { + currency::payment_id_t payment_id; + if (!currency::parse_payment_id_from_hex_str(payment_id_str, payment_id)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID; + er.message = std::string("invalid payment id given: \'") + payment_id_str + "\', hex-encoded string was expected"; + return false; + } + + std::list payment_list; + m_wallet.get_payments(payment_id, payment_list, req.min_block_height); + + for (auto & payment : payment_list) + { + wallet_rpc::payment_details rpc_payment; + rpc_payment.payment_id = payment_id_str; + rpc_payment.tx_hash = epee::string_tools::pod_to_hex(payment.m_tx_hash); + rpc_payment.amount = payment.m_amount; + rpc_payment.block_height = payment.m_block_height; + rpc_payment.unlock_time = payment.m_unlock_time; + res.payments.push_back(std::move(rpc_payment)); + } + } + + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_make_integrated_address(const wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS::response& res, epee::json_rpc::error& er, connection_context& cntx) { std::string payment_id; diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 6336d712..980badf9 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -38,13 +38,15 @@ namespace tools BEGIN_URI_MAP2() BEGIN_JSON_RPC_MAP("/json_rpc") - 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("transfer", on_transfer, wallet_rpc::COMMAND_RPC_TRANSFER) - MAP_JON_RPC_WE("store", on_store, wallet_rpc::COMMAND_RPC_STORE) - MAP_JON_RPC_WE("get_payments", on_get_payments, wallet_rpc::COMMAND_RPC_GET_PAYMENTS) - MAP_JON_RPC_WE("make_integrated_address", on_make_integrated_address, wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS) - MAP_JON_RPC_WE("split_integrated_address", on_split_integrated_address, wallet_rpc::COMMAND_RPC_SPLIT_INTEGRATED_ADDRESS) + 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("transfer", on_transfer, wallet_rpc::COMMAND_RPC_TRANSFER) + MAP_JON_RPC_WE("store", on_store, wallet_rpc::COMMAND_RPC_STORE) + MAP_JON_RPC_WE("get_payments", on_get_payments, wallet_rpc::COMMAND_RPC_GET_PAYMENTS) + MAP_JON_RPC_WE("get_bulk_payments", on_get_bulk_payments, wallet_rpc::COMMAND_RPC_GET_BULK_PAYMENTS) + MAP_JON_RPC_WE("make_integrated_address", on_make_integrated_address, wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS) + MAP_JON_RPC_WE("split_integrated_address", on_split_integrated_address, wallet_rpc::COMMAND_RPC_SPLIT_INTEGRATED_ADDRESS) + // supernet api MAP_JON_RPC_WE("maketelepod", on_maketelepod, wallet_rpc::COMMAND_RPC_MAKETELEPOD) MAP_JON_RPC_WE("clonetelepod", on_clonetelepod, wallet_rpc::COMMAND_RPC_CLONETELEPOD) @@ -59,6 +61,7 @@ namespace tools bool on_transfer(const wallet_rpc::COMMAND_RPC_TRANSFER::request& req, wallet_rpc::COMMAND_RPC_TRANSFER::response& res, epee::json_rpc::error& er, connection_context& cntx); bool on_store(const wallet_rpc::COMMAND_RPC_STORE::request& req, wallet_rpc::COMMAND_RPC_STORE::response& res, epee::json_rpc::error& er, connection_context& cntx); bool on_get_payments(const wallet_rpc::COMMAND_RPC_GET_PAYMENTS::request& req, wallet_rpc::COMMAND_RPC_GET_PAYMENTS::response& res, epee::json_rpc::error& er, connection_context& cntx); + bool on_get_bulk_payments(const wallet_rpc::COMMAND_RPC_GET_BULK_PAYMENTS::request& req, wallet_rpc::COMMAND_RPC_GET_BULK_PAYMENTS::response& res, epee::json_rpc::error& er, connection_context& cntx); bool on_make_integrated_address(const wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS::response& res, epee::json_rpc::error& er, connection_context& cntx); bool on_split_integrated_address(const wallet_rpc::COMMAND_RPC_SPLIT_INTEGRATED_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_SPLIT_INTEGRATED_ADDRESS::response& res, epee::json_rpc::error& er, connection_context& cntx); bool on_maketelepod(const wallet_rpc::COMMAND_RPC_MAKETELEPOD::request& req, wallet_rpc::COMMAND_RPC_MAKETELEPOD::response& res, epee::json_rpc::error& er, connection_context& cntx); diff --git a/src/wallet/wallet_rpc_server_commans_defs.h b/src/wallet/wallet_rpc_server_commans_defs.h index dd511ac5..11724c34 100644 --- a/src/wallet/wallet_rpc_server_commans_defs.h +++ b/src/wallet/wallet_rpc_server_commans_defs.h @@ -255,12 +255,14 @@ namespace wallet_rpc struct payment_details { + std::string payment_id; std::string tx_hash; uint64_t amount; uint64_t block_height; uint64_t unlock_time; BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(payment_id) KV_SERIALIZE(tx_hash) KV_SERIALIZE(amount) KV_SERIALIZE(block_height) @@ -289,6 +291,29 @@ namespace wallet_rpc }; }; + struct COMMAND_RPC_GET_BULK_PAYMENTS + { + struct request + { + std::vector payment_ids; + uint64_t min_block_height; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(payment_ids) + KV_SERIALIZE(min_block_height) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::list payments; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(payments) + END_KV_SERIALIZE_MAP() + }; + }; + struct COMMAND_RPC_MAKE_INTEGRATED_ADDRESS { struct request From 9e05f08024345181cd55cb0320ff9afa29dc8631 Mon Sep 17 00:00:00 2001 From: sowle Date: Wed, 3 Apr 2019 17:53:49 +0300 Subject: [PATCH 02/13] coretests adapted --- .../escrow_wallet_altchain_test.cpp | 2 +- tests/core_tests/multisig_wallet_tests.cpp | 84 +++++++++---------- tests/core_tests/offers_test.cpp | 4 +- tests/core_tests/wallet_tests.cpp | 2 +- 4 files changed, 44 insertions(+), 48 deletions(-) diff --git a/tests/core_tests/escrow_wallet_altchain_test.cpp b/tests/core_tests/escrow_wallet_altchain_test.cpp index 737d90b0..35e8576f 100644 --- a/tests/core_tests/escrow_wallet_altchain_test.cpp +++ b/tests/core_tests/escrow_wallet_altchain_test.cpp @@ -397,7 +397,7 @@ bool escrow_altchain_meta_impl::c1(currency::core& c, size_t ev_index, const std currency::transaction tx = AUTO_VAL_INIT(tx); std::vector destinations{ tx_destination_entry(se.amount, wlt_to->get_account().get_public_address()) }; - wlt_from->transfer(destinations, 0, 0, se.fee, empty_extra, empty_attachment, tools::detail::digit_split_strategy, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx); + wlt_from->transfer(destinations, 0, 0, se.fee, empty_extra, empty_attachment, tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx); CHECK_AND_NO_ASSERT_MES(mine_next_block_with_tx(c, tx), false, "mine_next_block_with_tx failed"); mine_empty_block = false; diff --git a/tests/core_tests/multisig_wallet_tests.cpp b/tests/core_tests/multisig_wallet_tests.cpp index 73bd4162..52a9f1c8 100644 --- a/tests/core_tests/multisig_wallet_tests.cpp +++ b/tests/core_tests/multisig_wallet_tests.cpp @@ -27,16 +27,15 @@ void exception_handler(){} //============================================================================================================================== // helper routine: creates multisig-spending tx using a wallet and keys of other ms-participants, then sends it to the core proxy -template void transfer_multisig(tools::wallet2& w, const std::list& owner_keys, - crypto::hash multisig_id, + const crypto::hash& multisig_id, const std::vector& dsts, uint64_t unlock_time, uint64_t fee, const std::vector& extra, - const std::vector attachments, - destination_split_strategy_t destination_split_strategy, + const std::vector& attachments, + tools::detail::split_strategy_id_t split_strategy_id, const tools::tx_dust_policy& dust_policy, currency::transaction &tx, uint8_t tx_outs_attr = CURRENCY_TO_KEY_OUT_RELAXED, @@ -44,31 +43,28 @@ void transfer_multisig(tools::wallet2& w, bool shuffle = true, bool send_to_network = true) { - using namespace currency; - std::vector selected_transfers; - std::vector prepared_destinations; currency::account_public_address crypt_address = get_crypt_address_from_destinations(w.get_account().get_keys(), dsts); - currency::keypair one_time_keys = AUTO_VAL_INIT(one_time_keys); // prepare transaction will sign ms input partially with wallet's keys - it needed to be signed fully with the others - w.prepare_transaction(dsts, - 0, - unlock_time, - fee, - extra, - attachments, - destination_split_strategy, - dust_policy, - crypt_address, - tx, - tx_outs_attr, - shuffle, - false, - flags, - selected_transfers, - one_time_keys, - prepared_destinations, - multisig_id); + tools::construct_tx_param ctp = AUTO_VAL_INIT(ctp); + tools::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); + ctp.attachments = attachments; + ctp.crypt_address = crypt_address; + ctp.dsts = dsts; + ctp.dust_policy = dust_policy; + ctp.extra = extra; + ctp.fake_outputs_count = 0; + ctp.fee = fee; + ctp.flags = flags; + ctp.mark_tx_as_complete = false; + ctp.multisig_id = multisig_id; + ctp.shuffle = shuffle; + ctp.split_strategy_id = split_strategy_id; + ctp.tx_outs_attr = tx_outs_attr; + ctp.unlock_time = unlock_time; + + w.prepare_transaction(ctp, ftp, tx); + w.finalize_transaction(ftp, tx, crypto::secret_key(), false); // sign ms input with all other non-wallet keys auto it = w.get_multisig_transfers().find(multisig_id); @@ -158,7 +154,7 @@ bool multisig_wallet_test::c1(currency::core& c, size_t ev_index, const std::vec dst.back().amount = AMOUNT_TO_TRANSFER_MULTISIG + TX_DEFAULT_FEE; dst.back().minimum_sigs = dst.back().addr.size(); transaction result_tx = AUTO_VAL_INIT(result_tx); - miner_wlt->transfer(dst, 0, 0, TX_DEFAULT_FEE, extra, attachments, tools::detail::digit_split_strategy, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), result_tx); + miner_wlt->transfer(dst, 0, 0, TX_DEFAULT_FEE, extra, attachments, tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), result_tx); bool r = mine_next_pow_blocks_in_playtime(m_scratchpad_keeper, m_mining_accunt.get_public_address(), c, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_blocks_in_playtime failed"); @@ -204,7 +200,7 @@ bool multisig_wallet_test::c1(currency::core& c, size_t ev_index, const std::vec TX_DEFAULT_FEE, extra, attachments, - tools::detail::digit_split_strategy, + tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), result_tx); @@ -293,7 +289,7 @@ bool multisig_wallet_test_many_dst::c1(currency::core& c, size_t ev_index, const transaction result_tx = AUTO_VAL_INIT(result_tx); TMP_LOG_SILENT; - miner_wlt->transfer(std::vector({ de }), 0, 0, TESTS_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::digit_split_strategy, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), result_tx); + miner_wlt->transfer(std::vector({ de }), 0, 0, TESTS_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), result_tx); TMP_LOG_RESTORE; auto it = std::find_if(result_tx.vout.begin(), result_tx.vout.end(), [](tx_out& o) { return o.target.type() == typeid(txout_multisig); }); @@ -314,7 +310,7 @@ bool multisig_wallet_test_many_dst::c1(currency::core& c, size_t ev_index, const tx_destination_entry de2(amount - TESTS_DEFAULT_FEE, m_accounts[ALICE_ACC_IDX].get_public_address()); transaction tx = AUTO_VAL_INIT(tx); TMP_LOG_SILENT; - transfer_multisig(*w.get(), owner_keys, get_multisig_out_id(result_tx, multisig_index), std::vector({ de2 }), 0, TESTS_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::digit_split_strategy, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx); + transfer_multisig(*w.get(), owner_keys, get_multisig_out_id(result_tx, multisig_index), std::vector({ de2 }), 0, TESTS_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx); TMP_LOG_RESTORE; r = mine_next_pow_blocks_in_playtime(m_scratchpad_keeper, m_accounts[MINER_ACC_IDX].get_public_address(), c, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); @@ -416,7 +412,7 @@ bool multisig_wallet_heterogenous_dst::c1(currency::core& c, size_t ev_index, co // Send multisig tx transaction ms_tx = AUTO_VAL_INIT(ms_tx); miner_wlt->transfer(std::vector({ de1, de2, de3, de4, de5, de6 }), - 0, 0, TX_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::digit_split_strategy, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), ms_tx); + 0, 0, TX_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), ms_tx); // calculate multisig hashes for further usage crypto::hash ms1_hash = currency::get_multisig_out_id(ms_tx, get_tx_out_index_by_amount(ms_tx, de1.amount)); @@ -484,7 +480,7 @@ bool multisig_wallet_heterogenous_dst::c1(currency::core& c, size_t ev_index, co currency::transaction tx = AUTO_VAL_INIT(tx); transfer_multisig(*alice_wlt.get(), std::list({ m_accounts[BOB_ACC_IDX].get_keys() }), ms1_hash, std::vector({ tx_destination_entry(amount1, receiver_acc.get_public_address()) }), - 0, TX_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::digit_split_strategy, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx); + 0, TX_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect txs count in the pool"); r = mine_next_pow_block_in_playtime(m_scratchpad_keeper, m_accounts[MINER_ACC_IDX].get_public_address(), c); @@ -499,7 +495,7 @@ bool multisig_wallet_heterogenous_dst::c1(currency::core& c, size_t ev_index, co { // as 'false' means don't send to network. This should fail during preparation, not during sending/processing transfer_multisig(*bob_wlt.get(), std::list({ m_accounts[ALICE_ACC_IDX].get_keys() }), ms1_hash, std::vector({ tx_destination_entry(amount1, receiver_acc.get_public_address()) }), - 0, TX_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::digit_split_strategy, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx, 0, 0, true, false); + 0, TX_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx, 0, 0, true, false); } catch (tools::error::wallet_internal_error&) { @@ -518,7 +514,7 @@ bool multisig_wallet_heterogenous_dst::c1(currency::core& c, size_t ev_index, co tx = AUTO_VAL_INIT(tx); transfer_multisig(*carol_wlt.get(), std::list({ m_accounts[DAN_ACC_IDX].get_keys() }), ms2_hash, std::vector({ tx_destination_entry(amount2, receiver_acc.get_public_address()) }), - 0, TX_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::digit_split_strategy, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx); + 0, TX_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect txs count in the pool"); @@ -530,7 +526,7 @@ bool multisig_wallet_heterogenous_dst::c1(currency::core& c, size_t ev_index, co { // as 'false' means don't send to network. This should fail during preparation, not during sending/processing transfer_multisig(*dan_wlt.get(), std::list({ m_accounts[CAROL_ACC_IDX].get_keys() }), ms2_hash, std::vector({ tx_destination_entry(amount2, receiver_acc.get_public_address()) }), - 0, TX_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::digit_split_strategy, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx, 0, 0, true, false); + 0, TX_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx, 0, 0, true, false); } catch (tools::error::wallet_internal_error&) { @@ -546,7 +542,7 @@ bool multisig_wallet_heterogenous_dst::c1(currency::core& c, size_t ev_index, co // Re-try spending Carol-Dan multisig out on behalf of Dan. It should be OK now transfer_multisig(*dan_wlt.get(), std::list({ m_accounts[CAROL_ACC_IDX].get_keys() }), ms2_hash, std::vector({ tx_destination_entry(amount2, receiver_acc.get_public_address()) }), - 0, TX_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::digit_split_strategy, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx); + 0, TX_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect txs count in the pool"); r = mine_next_pow_block_in_playtime(m_scratchpad_keeper, m_accounts[MINER_ACC_IDX].get_public_address(), c); @@ -564,7 +560,7 @@ bool multisig_wallet_heterogenous_dst::c1(currency::core& c, size_t ev_index, co tx = AUTO_VAL_INIT(tx); transfer_multisig(*alice_wlt.get(), std::list({ m_accounts[DAN_ACC_IDX].get_keys() }), ms3_hash, std::vector({ tx_destination_entry(amount3, receiver_acc.get_public_address()) }), - 0, TX_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::digit_split_strategy, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx); + 0, TX_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect txs count in the pool"); @@ -575,7 +571,7 @@ bool multisig_wallet_heterogenous_dst::c1(currency::core& c, size_t ev_index, co { // last 'false' means don't send to network. This should fail during preparation, not during sending/processing transfer_multisig(*dan_wlt.get(), std::list({ m_accounts[ALICE_ACC_IDX].get_keys() }), ms3_hash, std::vector({ tx_destination_entry(amount3, receiver_acc.get_public_address()) }), - 0, TX_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::digit_split_strategy, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx, 0, 0, true, false); + 0, TX_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx, 0, 0, true, false); } catch (tools::error::wallet_internal_error&) { @@ -605,7 +601,7 @@ bool multisig_wallet_heterogenous_dst::c1(currency::core& c, size_t ev_index, co try { transfer_multisig(*dan_wlt.get(), std::list({ m_accounts[ALICE_ACC_IDX].get_keys() }), ms3_hash, std::vector({ tx_destination_entry(amount3, receiver_acc.get_public_address()) }), - 0, TX_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::digit_split_strategy, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx, 0, 0, true, false); + 0, TX_DEFAULT_FEE, std::vector(), std::vector(), tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx, 0, 0, true, false); } catch (tools::error::wallet_internal_error&) { @@ -704,7 +700,7 @@ bool multisig_wallet_same_dst_addr::c1(currency::core& c, size_t ev_index, const transaction tx = AUTO_VAL_INIT(tx); transfer_multisig(*alice_wlt.get(), std::list({ m_accounts[ALICE_ACC_IDX].get_keys() }), ms_hash, std::vector({ tx_destination_entry(amount, m_accounts[BOB_ACC_IDX].get_public_address()) }), - 0, TX_DEFAULT_FEE, empty_extra, empty_attachment, tools::detail::digit_split_strategy, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx); + 0, TX_DEFAULT_FEE, empty_extra, empty_attachment, tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect number of tx in the pool"); r = mine_next_pow_block_in_playtime(m_scratchpad_keeper, m_accounts[MINER_ACC_IDX].get_public_address(), c); @@ -798,7 +794,7 @@ bool multisig_wallet_ms_to_ms::c1(currency::core& c, size_t ev_index, const std: transaction ms_tx2 = AUTO_VAL_INIT(ms_tx2); transfer_multisig(*alice_wlt.get(), std::list({ m_accounts[BOB_ACC_IDX].get_keys() }), ms_hash, std::vector({ de }), - 0, TX_DEFAULT_FEE, empty_extra, empty_attachment, tools::detail::digit_split_strategy, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), ms_tx2); + 0, TX_DEFAULT_FEE, empty_extra, empty_attachment, tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), ms_tx2); // check the pool and mine a block CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect number of tx in the pool"); @@ -815,7 +811,7 @@ bool multisig_wallet_ms_to_ms::c1(currency::core& c, size_t ev_index, const std: transaction tx = AUTO_VAL_INIT(tx); transfer_multisig(*miner_wlt.get(), std::list({ m_accounts[BOB_ACC_IDX].get_keys() }), ms_hash2, std::vector({ tx_destination_entry(amount - TX_DEFAULT_FEE, m_accounts[BOB_ACC_IDX].get_public_address()) }), - 0, TX_DEFAULT_FEE, empty_extra, empty_attachment, tools::detail::digit_split_strategy, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx); + 0, TX_DEFAULT_FEE, empty_extra, empty_attachment, tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx); CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Incorrect number of tx in the pool"); r = mine_next_pow_block_in_playtime(m_scratchpad_keeper, m_accounts[MINER_ACC_IDX].get_public_address(), c); @@ -2488,7 +2484,7 @@ bool multisig_unconfirmed_transfer_and_multiple_scan_pool_calls::c1(currency::co dst.back().amount = ms_amount + TX_DEFAULT_FEE; dst.back().minimum_sigs = dst.back().addr.size(); transaction key_to_ms_tx = AUTO_VAL_INIT(key_to_ms_tx); - miner_wlt->transfer(dst, 0, 0, TX_DEFAULT_FEE, extra, attachments, tools::detail::digit_split_strategy, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), key_to_ms_tx); + miner_wlt->transfer(dst, 0, 0, TX_DEFAULT_FEE, extra, attachments, tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), key_to_ms_tx); bool r = mine_next_pow_blocks_in_playtime(m_scratchpad_keeper, miner_acc.get_public_address(), c, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_blocks_in_playtime failed"); @@ -2528,7 +2524,7 @@ bool multisig_unconfirmed_transfer_and_multiple_scan_pool_calls::c1(currency::co TX_DEFAULT_FEE, extra, attachments, - tools::detail::digit_split_strategy, + tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx); diff --git a/tests/core_tests/offers_test.cpp b/tests/core_tests/offers_test.cpp index 8a24e711..8d0c116c 100644 --- a/tests/core_tests/offers_test.cpp +++ b/tests/core_tests/offers_test.cpp @@ -925,7 +925,7 @@ bool offers_updating_hack::update_foreign_offer(currency::core& c, size_t ev_ind // make a tx with this attachment (note: tsa.security is signed using Bob's keys, can be resign later using Alice's public address) transaction tx = AUTO_VAL_INIT(tx); bob_wlt->transfer(std::vector({ tx_destination_entry(TESTS_DEFAULT_FEE * 3, m_accounts[BOB_ACC_IDX].get_public_address()) }), 0, 0, TESTS_DEFAULT_FEE * 81, empty_extra, attachments, - tools::detail::digit_split_strategy, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx, 0, true, 0, false); + tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx, 0, true, 0, false); // don't resign yet @@ -999,7 +999,7 @@ bool offers_updating_hack::delete_foreign_offer(currency::core& c, size_t ev_ind // make a tx with this attachment (note: tsa.security is signed using Bob's keys, can be resign later using Alice's public address) transaction tx_c = AUTO_VAL_INIT(tx_c); bob_wlt->transfer(std::vector({ tx_destination_entry(TESTS_DEFAULT_FEE * 3, m_accounts[BOB_ACC_IDX].get_public_address()) }), 0, 0, TESTS_DEFAULT_FEE * 19, empty_extra, attachments, - tools::detail::digit_split_strategy, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx_c, 0, true, 0, true); + tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx_c, 0, true, 0, true); LOG_PRINT_L0(ENDL << c.get_tx_pool().print_pool(true)); diff --git a/tests/core_tests/wallet_tests.cpp b/tests/core_tests/wallet_tests.cpp index 71427959..948e71f9 100644 --- a/tests/core_tests/wallet_tests.cpp +++ b/tests/core_tests/wallet_tests.cpp @@ -3082,7 +3082,7 @@ bool wallet_unconfirmed_tx_expiration::c1(currency::core& c, size_t ev_index, co transaction tx = AUTO_VAL_INIT(tx); try { - alice_wlt->transfer(destinations, 0, 0, TESTS_DEFAULT_FEE, extra, empty_attachment, tools::detail::digit_split_strategy, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx); + alice_wlt->transfer(destinations, 0, 0, TESTS_DEFAULT_FEE, extra, empty_attachment, tools::detail::ssi_digit, tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx); } catch (std::exception &e) { From c9404884b84e4651f7ddd3c66f188863a3782f77 Mon Sep 17 00:00:00 2001 From: sowle Date: Wed, 3 Apr 2019 19:18:34 +0300 Subject: [PATCH 03/13] wallet: build_escrow_release_templates fixed --- src/wallet/wallet2.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index bb648001..15f43d7d 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -2731,7 +2731,6 @@ void wallet2::build_escrow_release_templates(crypto::hash multisig_id, const bc_services::contract_private_details& ecrow_details) { construct_tx_param construct_params = AUTO_VAL_INIT(construct_params); - finalize_tx_param ftp = AUTO_VAL_INIT(ftp); construct_params.fee = fee; construct_params.multisig_id = multisig_id; construct_params.split_strategy_id = detail::ssi_digit; @@ -2755,8 +2754,11 @@ void wallet2::build_escrow_release_templates(crypto::hash multisig_id, tsa.service_id = BC_ESCROW_SERVICE_ID; tsa.instruction = BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_NORMAL; construct_params.extra.push_back(tsa); - prepare_transaction(construct_params, ftp); - finalize_transaction(ftp, tx_release_template, crypto::secret_key(), false); + { + finalize_tx_param ftp = AUTO_VAL_INIT(ftp); + prepare_transaction(construct_params, ftp); + finalize_transaction(ftp, tx_release_template, crypto::secret_key(), false); + } //generate burn escrow construct_params.dsts.resize(1); @@ -2767,8 +2769,11 @@ void wallet2::build_escrow_release_templates(crypto::hash multisig_id, construct_params.extra.clear(); tsa.instruction = BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_BURN; construct_params.extra.push_back(tsa); - prepare_transaction(construct_params, ftp); - finalize_transaction(ftp, tx_burn_template, crypto::secret_key(), false); + { + finalize_tx_param ftp = AUTO_VAL_INIT(ftp); + prepare_transaction(construct_params, ftp); + finalize_transaction(ftp, tx_burn_template, crypto::secret_key(), false); + } } //---------------------------------------------------------------------------------------------------- void wallet2::build_escrow_cancel_template(crypto::hash multisig_id, From b1a518732226a6389de8443a6c3579babb37d27c Mon Sep 17 00:00:00 2001 From: sowle Date: Fri, 5 Apr 2019 00:42:28 +0300 Subject: [PATCH 04/13] wallet: sign_transfer + submit_transfer implemented --- src/currency_core/currency_format_utils.h | 14 ++ src/wallet/wallet2.cpp | 152 ++++++++++++++++++-- src/wallet/wallet2.h | 23 +-- src/wallet/wallet_errors.h | 9 ++ src/wallet/wallet_rpc_server.cpp | 83 +++++++++++ src/wallet/wallet_rpc_server.h | 5 + src/wallet/wallet_rpc_server_commans_defs.h | 45 ++++++ src/wallet/wallet_rpc_server_error_codes.h | 1 + 8 files changed, 310 insertions(+), 22 deletions(-) diff --git a/src/currency_core/currency_format_utils.h b/src/currency_core/currency_format_utils.h index 3bbf8aa2..14fb9970 100644 --- a/src/currency_core/currency_format_utils.h +++ b/src/currency_core/currency_format_utils.h @@ -67,12 +67,26 @@ namespace currency crypto::public_key real_out_tx_key; //real output's transaction's public key size_t real_output_in_tx_index; //index in transaction outputs vector uint64_t amount; //money + uint64_t transfer_index; //money crypto::hash multisig_id; //if txin_multisig: multisig output id size_t ms_sigs_count; //if txin_multisig: must be equal to output's minimum_sigs size_t ms_keys_count; //if txin_multisig: must be equal to size of output's keys container bool separately_signed_tx_complete; //for separately signed tx only: denotes the last source entry in complete tx to explicitly mark the final step of tx creation bool is_multisig() const { return ms_sigs_count > 0; } + + BEGIN_SERIALIZE_OBJECT() + FIELD(outputs) + FIELD(real_output) + FIELD(real_out_tx_key) + FIELD(real_output_in_tx_index) + FIELD(amount) + FIELD(transfer_index) + FIELD(multisig_id) + FIELD(ms_sigs_count) + FIELD(ms_keys_count) + FIELD(separately_signed_tx_complete) + END_SERIALIZE() }; struct tx_destination_entry diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 15f43d7d..f79c9285 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -2093,7 +2093,7 @@ void wallet2::get_payments(const std::string& payment_id, std::list(out.target); + + crypto::public_key ephemeral_pub = AUTO_VAL_INIT(ephemeral_pub); + if (!crypto::derive_public_key(derivation, i, m_account.get_keys().m_account_address.m_spend_public_key, ephemeral_pub)) + { + WLT_LOG_ERROR("derive_public_key failed for tx " << get_transaction_hash(ft.tx) << ", out # " << i); + } + + if (otk.key == ephemeral_pub) + { + // this is the output to the given keys + // derive secret key and calculate key image + crypto::secret_key ephemeral_sec = AUTO_VAL_INIT(ephemeral_sec); + crypto::derive_secret_key(derivation, i, m_account.get_keys().m_spend_secret_key, ephemeral_sec); + crypto::key_image ki = AUTO_VAL_INIT(ki); + crypto::generate_key_image(ephemeral_pub, ephemeral_sec, ki); + + ft.outs_key_images.push_back(std::make_pair(static_cast(i), ki)); + } + } // serialize and encrypt the result - signed_tx_blob = t_serializable_object_to_blob(create_tx_result); - crypto::do_chacha_crypt(signed_tx_blob, m_account.get_keys().m_view_secret_key); + signed_tx_blob = t_serializable_object_to_blob(ft); + crypto::chacha_crypt(signed_tx_blob, m_account.get_keys().m_view_secret_key); } //---------------------------------------------------------------------------------------------------- void wallet2::sign_transfer_files(const std::string& tx_sources_file, const std::string& signed_tx_file, currency::transaction& tx) @@ -2132,7 +2160,105 @@ void wallet2::sign_transfer_files(const std::string& tx_sources_file, const std: 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); } -*/ +//---------------------------------------------------------------------------------------------------- +void wallet2::submit_transfer(const std::string& signed_tx_blob, currency::transaction& tx) +{ + // decrypt sources + std::string decrypted_src_blob = crypto::chacha_crypt(signed_tx_blob, m_account.get_keys().m_view_secret_key); + + // 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"); + + crypto::hash tx_hash = get_transaction_hash(ft.tx); + + // foolproof check to make sure create_tx_param and create_tx_result DO match each other + + try + { + send_transaction_to_network(ft.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(get_transaction_hash(ft.tx)) + " was unsuccessful"); + throw; + } + + add_sent_tx_detailed_info(ft.tx, ft.ftp.prepared_destinations, ft.ftp.selected_transfers); + m_tx_keys.insert(std::make_pair(get_transaction_hash(ft.tx), ft.one_time_key)); + + if (m_watch_only) + { + std::vector> pk_ki_to_be_added; + std::vector> tri_ki_to_be_added; + + for (auto& p : ft.outs_key_images) + { + THROW_IF_FALSE_WALLET_INT_ERR_EX(p.first < tx.vout.size(), "outs_key_images has invalid out index: " << p.first << ", tx.vout.size() = " << tx.vout.size()); + auto& out = tx.vout[p.first]; + THROW_IF_FALSE_WALLET_INT_ERR_EX(out.target.type() == typeid(txout_to_key), "outs_key_images has invalid out type, index: " << p.first); + const txout_to_key& otk = boost::get(out.target); + pk_ki_to_be_added.push_back(std::make_pair(otk.key, p.second)); + } + + THROW_IF_FALSE_WALLET_INT_ERR_EX(tx.vin.size() == ft.ftp.sources.size(), "tx.vin and ft.ftp.sources sizes missmatch"); + for (size_t i = 0; i < tx.vin.size(); ++i) + { + const txin_v& in = tx.vin[i]; + THROW_IF_FALSE_WALLET_CMN_ERR_EX(in.type() == typeid(txin_to_key), "tx " << tx_hash << " has a non txin_to_key input"); + const crypto::key_image& ki = boost::get(in).k_image; + + const auto& src = ft.ftp.sources[i]; + THROW_IF_FALSE_WALLET_INT_ERR_EX(src.real_output < src.outputs.size(), "src.real_output is out of bounds: " << src.real_output); + const crypto::public_key& out_key = src.outputs[src.real_output].second; + + tri_ki_to_be_added.push_back(std::make_pair(src.transfer_index, ki)); + pk_ki_to_be_added.push_back(std::make_pair(out_key, ki)); + } + + for (auto& p : pk_ki_to_be_added) + { + auto it = m_pending_key_images.find(p.first); + if (it != m_pending_key_images.end()) + { + LOG_PRINT_YELLOW("warning: for tx " << tx_hash << " out pub key " << p.first << " already exist in m_pending_key_images, ki: " << it->second << ", proposed new ki: " << p.second, LOG_LEVEL_0); + } + else + { + m_pending_key_images[p.first] = p.second; + m_pending_key_images_file_container.push_back(tools::out_key_to_ki(p.first, p.second)); + LOG_PRINT_L2("for tx " << tx_hash << " pending key image added (" << p.first << ", " << p.second << ")"); + } + } + + for (auto& p : tri_ki_to_be_added) + { + THROW_IF_FALSE_WALLET_INT_ERR_EX(p.first < m_transfers.size(), "incorrect transfer index: " << p.first); + auto& tr = m_transfers[p.first]; + if (tr.m_key_image != currency::null_ki) + { + LOG_PRINT_YELLOW("transfer #" << p.first << " has not null key image: " << tr.m_key_image << " will be replaced with ki " << p.second, LOG_LEVEL_0); + } + tr.m_key_image = p.second; + m_key_images[p.second] = p.first; + LOG_PRINT_L2("for tx " << tx_hash << " key image " << p.second << " was associated with transfer # " << p.first); + } + } + + // print inputs' key images + // print tx was sent +} +//---------------------------------------------------------------------------------------------------- +void wallet2::submit_transfer_files(const std::string& signed_tx_file, currency::transaction& tx) +{ + std::string signed_tx_blob; + bool r = epee::file_io_utils::load_file_to_string(signed_tx_file, signed_tx_blob); + THROW_IF_FALSE_WALLET_EX(r, error::wallet_common_error, std::string("failed to open file ") + signed_tx_file); + + submit_transfer(signed_tx_blob, tx); +} //---------------------------------------------------------------------------------------------------- uint64_t wallet2::get_recent_transfers_total_count() { @@ -3060,6 +3186,7 @@ bool wallet2::prepare_tx_sources(uint64_t needed_money, size_t fake_outputs_coun sources.push_back(AUTO_VAL_INIT(currency::tx_source_entry())); currency::tx_source_entry& src = sources.back(); transfer_details& td = *it; + src.transfer_index = it - m_transfers.begin(); src.amount = td.amount(); //paste mixin transaction if (daemon_resp.outs.size()) @@ -3809,6 +3936,7 @@ void wallet2::transfer(const std::vector& dsts, 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); crypto::chacha_crypt(bl, m_account.get_keys().m_view_secret_key); epee::file_io_utils::save_string_to_file("unsigned_zano_tx", bl); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 1599cdd1..d9ae6836 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -255,6 +255,8 @@ namespace tools 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) @@ -270,6 +272,7 @@ namespace tools FIELD(crypt_address) FIELD(tx_outs_attr) FIELD(shuffle) + FIELD(spend_pub_key) END_SERIALIZE() }; @@ -294,7 +297,6 @@ namespace tools std::vector selected_transfers; std::vector prepared_destinations; - BEGIN_SERIALIZE_OBJECT() FIELD(unlock_time) FIELD(extra) @@ -310,21 +312,20 @@ namespace tools END_SERIALIZE() }; - /*uct constructed_tx_data + struct finalized_tx { - //std::vector prepared_destinations; currency::transaction tx; - //std::vector selected_transfers; - crypto::secret_key one_time_key; + crypto::secret_key one_time_key; + finalize_tx_param ftp; + std::vector> outs_key_images; // pairs (out_index, key_image) for each change output BEGIN_SERIALIZE_OBJECT() - //FIELD(prepared_destinations) FIELD(tx) - //FIELD(selected_transfers) FIELD(one_time_key) + FIELD(ftp) + FIELD(outs_key_images) END_SERIALIZE() - };*/ - + }; class wallet2 { @@ -613,8 +614,10 @@ namespace tools bool is_watch_only() const { return m_watch_only; } void sign_transfer(const std::string& tx_sources_blob, std::string& signed_tx_blob, currency::transaction& tx); + void sign_transfer_files(const std::string& tx_sources_file, const std::string& signed_tx_file, currency::transaction& tx); + void submit_transfer(const std::string& signed_tx_blob, currency::transaction& tx); + void submit_transfer_files(const std::string& signed_tx_file, currency::transaction& tx); - bool get_transfer_address(const std::string& adr_str, currency::account_public_address& addr, std::string& payment_id); uint64_t get_blockchain_current_height() const { return m_blockchain.size(); } template diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index d5b2009f..0903f5b5 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -678,3 +678,12 @@ if (cond) #define THROW_IF_FALSE_WALLET_INT_ERR_EX(cond, mess) THROW_IF_TRUE_WALLET_INT_ERR_EX((!(cond)), mess) + +#define THROW_IF_FALSE_WALLET_CMN_ERR_EX(cond, mess) \ + if (!(cond)) \ + { \ + std::stringstream ss; \ + ss << mess; \ + LOG_ERROR(" (" << #cond << ") is FALSE. THROW EXCEPTION: wallet_common_error"); \ + tools::error::throw_wallet_ex(std::string(__FILE__ ":" STRINGIZE(__LINE__)), ss.str()); \ + } diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 047e7839..6c9f49e0 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -301,6 +301,89 @@ namespace tools res.payment_id = epee::string_tools::buff_to_hex_nodelimer(payment_id); return true; } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_sign_transfer(const wallet_rpc::COMMAND_SIGN_TRANSFER::request& req, wallet_rpc::COMMAND_SIGN_TRANSFER::response& res, epee::json_rpc::error& er, connection_context& cntx) + { + try + { + currency::transaction tx = AUTO_VAL_INIT(tx); + std::string tx_unsigned_blob; + if (!string_tools::parse_hexstr_to_binbuff(req.tx_unsigned_hex, tx_unsigned_blob)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_ARGUMENT; + er.message = "tx_unsigned_hex is invalid"; + return false; + } + std::string tx_signed_blob; + m_wallet.sign_transfer(tx_unsigned_blob, tx_signed_blob, tx); + + res.tx_signed_hex = epee::string_tools::buff_to_hex_nodelimer(tx_signed_blob); + res.tx_hash = epee::string_tools::pod_to_hex(currency::get_transaction_hash(tx)); + } + catch (const std::exception& e) + { + er.code = WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR; + er.message = e.what(); + return false; + } + catch (...) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR"; + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_submit_transfer(const wallet_rpc::COMMAND_SUBMIT_TRANSFER::request& req, wallet_rpc::COMMAND_SUBMIT_TRANSFER::response& res, epee::json_rpc::error& er, connection_context& cntx) + { + //std::string tx_unsigned_blob; + //if (!string_tools::parse_hexstr_to_binbuff(req.tx_unsigned_hex, tx_unsigned_blob)) + //{ + // er.code = WALLET_RPC_ERROR_CODE_WRONG_ARGUMENT; + // er.message = "tx_unsigned_hex is invalid"; + // return false; + //} + + std::string tx_signed_blob; + if (!string_tools::parse_hexstr_to_binbuff(req.tx_signed_hex, tx_signed_blob)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_ARGUMENT; + er.message = "tx_signed_hex is invalid"; + return false; + } + + try + { + currency::transaction tx = AUTO_VAL_INIT(tx); + // TODO m_wallet.submit_transfer(tx_unsigned_blob, tx_signed_blob, tx); + res.tx_hash = epee::string_tools::pod_to_hex(currency::get_transaction_hash(tx)); + } + catch (const std::exception& e) + { + er.code = WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR; + er.message = e.what(); + return false; + } + catch (...) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; + er.message = "WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR"; + return false; + } + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ + + + + + + + + + + //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_maketelepod(const wallet_rpc::COMMAND_RPC_MAKETELEPOD::request& req, wallet_rpc::COMMAND_RPC_MAKETELEPOD::response& res, epee::json_rpc::error& er, connection_context& cntx) { diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 980badf9..036de6b9 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -46,6 +46,8 @@ namespace tools MAP_JON_RPC_WE("get_bulk_payments", on_get_bulk_payments, wallet_rpc::COMMAND_RPC_GET_BULK_PAYMENTS) MAP_JON_RPC_WE("make_integrated_address", on_make_integrated_address, wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS) MAP_JON_RPC_WE("split_integrated_address", on_split_integrated_address, wallet_rpc::COMMAND_RPC_SPLIT_INTEGRATED_ADDRESS) + MAP_JON_RPC_WE("sign_transfer", on_sign_transfer, wallet_rpc::COMMAND_SIGN_TRANSFER) + MAP_JON_RPC_WE("submit_transfer", on_submit_transfer, wallet_rpc::COMMAND_SUBMIT_TRANSFER) // supernet api MAP_JON_RPC_WE("maketelepod", on_maketelepod, wallet_rpc::COMMAND_RPC_MAKETELEPOD) @@ -64,6 +66,9 @@ namespace tools bool on_get_bulk_payments(const wallet_rpc::COMMAND_RPC_GET_BULK_PAYMENTS::request& req, wallet_rpc::COMMAND_RPC_GET_BULK_PAYMENTS::response& res, epee::json_rpc::error& er, connection_context& cntx); bool on_make_integrated_address(const wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_MAKE_INTEGRATED_ADDRESS::response& res, epee::json_rpc::error& er, connection_context& cntx); bool on_split_integrated_address(const wallet_rpc::COMMAND_RPC_SPLIT_INTEGRATED_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_SPLIT_INTEGRATED_ADDRESS::response& res, epee::json_rpc::error& er, connection_context& cntx); + bool on_sign_transfer(const wallet_rpc::COMMAND_SIGN_TRANSFER::request& req, wallet_rpc::COMMAND_SIGN_TRANSFER::response& res, epee::json_rpc::error& er, connection_context& cntx); + bool on_submit_transfer(const wallet_rpc::COMMAND_SUBMIT_TRANSFER::request& req, wallet_rpc::COMMAND_SUBMIT_TRANSFER::response& res, epee::json_rpc::error& er, connection_context& cntx); + bool on_maketelepod(const wallet_rpc::COMMAND_RPC_MAKETELEPOD::request& req, wallet_rpc::COMMAND_RPC_MAKETELEPOD::response& res, epee::json_rpc::error& er, connection_context& cntx); bool on_clonetelepod(const wallet_rpc::COMMAND_RPC_CLONETELEPOD::request& req, wallet_rpc::COMMAND_RPC_CLONETELEPOD::response& res, epee::json_rpc::error& er, connection_context& cntx); bool on_telepodstatus(const wallet_rpc::COMMAND_RPC_TELEPODSTATUS::request& req, wallet_rpc::COMMAND_RPC_TELEPODSTATUS::response& res, epee::json_rpc::error& er, connection_context& cntx); diff --git a/src/wallet/wallet_rpc_server_commans_defs.h b/src/wallet/wallet_rpc_server_commans_defs.h index 11724c34..ac025b44 100644 --- a/src/wallet/wallet_rpc_server_commans_defs.h +++ b/src/wallet/wallet_rpc_server_commans_defs.h @@ -360,6 +360,51 @@ namespace wallet_rpc }; }; + struct COMMAND_SIGN_TRANSFER + { + struct request + { + std::string tx_unsigned_hex; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tx_unsigned_hex) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string tx_signed_hex; + std::string tx_hash; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tx_signed_hex) + KV_SERIALIZE(tx_hash) + END_KV_SERIALIZE_MAP() + }; + }; + + struct COMMAND_SUBMIT_TRANSFER + { + struct request + { + //std::string tx_unsigned_hex; + std::string tx_signed_hex; + + BEGIN_KV_SERIALIZE_MAP() + //KV_SERIALIZE(tx_unsigned_hex) + KV_SERIALIZE(tx_signed_hex) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string tx_hash; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tx_hash) + END_KV_SERIALIZE_MAP() + }; + }; /*stay-alone instance*/ struct telepod diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h index 3743cb1a..f9c0a2a0 100644 --- a/src/wallet/wallet_rpc_server_error_codes.h +++ b/src/wallet/wallet_rpc_server_error_codes.h @@ -12,3 +12,4 @@ #define WALLET_RPC_ERROR_CODE_DAEMON_IS_BUSY -3 #define WALLET_RPC_ERROR_CODE_GENERIC_TRANSFER_ERROR -4 #define WALLET_RPC_ERROR_CODE_WRONG_PAYMENT_ID -5 +#define WALLET_RPC_ERROR_CODE_WRONG_ARGUMENT -6 From 593a887d7744b7212058958f119e4a4fad6cb5b9 Mon Sep 17 00:00:00 2001 From: sowle Date: Fri, 5 Apr 2019 00:43:09 +0300 Subject: [PATCH 05/13] unit tests fixed --- tests/unit_tests/epee_levin_protocol_handler_async.cpp | 2 +- tests/unit_tests/pow_hash_test.cpp | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/unit_tests/epee_levin_protocol_handler_async.cpp b/tests/unit_tests/epee_levin_protocol_handler_async.cpp index b256b12c..996451a8 100644 --- a/tests/unit_tests/epee_levin_protocol_handler_async.cpp +++ b/tests/unit_tests/epee_levin_protocol_handler_async.cpp @@ -264,7 +264,7 @@ TEST_F(positive_test_connection_to_levin_protocol_handler_calls, handler_initial TEST_F(positive_test_connection_to_levin_protocol_handler_calls, concurent_handler_initialization_and_destruction_is_correct) { const size_t connection_count = 10000; - auto create_and_destroy_connections = [this]() + auto create_and_destroy_connections = [this, connection_count]() { std::vector connections(connection_count); for (size_t i = 0; i < connection_count; ++i) diff --git a/tests/unit_tests/pow_hash_test.cpp b/tests/unit_tests/pow_hash_test.cpp index 523aeed1..010ba069 100644 --- a/tests/unit_tests/pow_hash_test.cpp +++ b/tests/unit_tests/pow_hash_test.cpp @@ -2,11 +2,7 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. - - - #include "gtest/gtest.h" - #include "crypto/crypto.h" #include "currency_core/currency_basic.h" #include "profile_tools.h" @@ -17,6 +13,8 @@ using namespace currency; +/* + bool test_scratchpad_against_light_scratchpad() { crypto::hash seed = crypto::cn_fast_hash("sdssdsffdss", 10); @@ -49,3 +47,4 @@ TEST(pow_tests, test_full_against_light) ASSERT_TRUE(res); } +*/ From 25e9cfc5371a3b09ee1e55c8f60a9951f6f2ee77 Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 8 Apr 2019 14:16:11 +0300 Subject: [PATCH 06/13] wallet: watch-only & cold-signing --- src/currency_core/account.cpp | 7 +- src/currency_core/account.h | 2 + src/simplewallet/simplewallet.cpp | 26 ++ src/simplewallet/simplewallet.h | 1 + src/wallet/wallet2.cpp | 162 ++++++----- src/wallet/wallet2.h | 31 +- src/wallet/wallet_errors.h | 1 + src/wallet/wallet_rpc_server.cpp | 306 +------------------- src/wallet/wallet_rpc_server.h | 15 +- src/wallet/wallet_rpc_server_commans_defs.h | 2 + 10 files changed, 152 insertions(+), 401 deletions(-) diff --git a/src/currency_core/account.cpp b/src/currency_core/account.cpp index a291feb8..39f6675e 100644 --- a/src/currency_core/account.cpp +++ b/src/currency_core/account.cpp @@ -122,11 +122,16 @@ namespace currency return get_account_address_as_str(m_keys.m_account_address); } //----------------------------------------------------------------- + void account_base::make_account_watch_only() + { + m_keys.m_spend_secret_key = currency::null_skey; + } + //----------------------------------------------------------------- std::string transform_addr_to_str(const account_public_address& addr) { return get_account_address_as_str(addr); } - + //----------------------------------------------------------------- account_public_address transform_str_to_addr(const std::string& str) { account_public_address ad = AUTO_VAL_INIT(ad); diff --git a/src/currency_core/account.h b/src/currency_core/account.h index 656c497c..f39e1a61 100644 --- a/src/currency_core/account.h +++ b/src/currency_core/account.h @@ -63,6 +63,8 @@ namespace currency bool load(const std::string& file_path); bool store(const std::string& file_path); + void make_account_watch_only(); + template inline void serialize(t_archive &a, const unsigned int /*ver*/) { diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 83615919..f86dd2c6 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -206,6 +206,7 @@ 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 ."); } //---------------------------------------------------------------------------------------------------- @@ -1280,6 +1281,31 @@ void simple_wallet::set_offline_mode(bool offline_mode) m_offline_mode = offline_mode; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::save_watch_only(const std::vector &args) +{ + if (args.size() < 2) + { + fail_msg_writer() << "wrong parameters, expected filename and password"; + return true; + } + try + { + m_wallet->store(epee::string_encoding::convert_to_unicode(args[0]), args[1], true); + success_msg_writer() << "Watch-only wallet has been stored to " << args[0]; + } + 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 diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index dca21675..30c76618 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -74,6 +74,7 @@ namespace currency bool enable_concole_logger(const std::vector &args); bool integrated_address(const std::vector &args); bool get_tx_key(const std::vector &args_); + bool save_watch_only(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 f79c9285..cde719ec 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -272,29 +272,52 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t for (size_t i_in_outs = 0; i_in_outs != outs.size(); i_in_outs++) { size_t o = outs[i_in_outs]; - THROW_IF_TRUE_WALLET_EX(tx.vout.size() <= o, error::wallet_internal_error, "wrong out in transaction: internal index=" + - std::to_string(o) + ", total_outs=" + std::to_string(tx.vout.size())); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(o < tx.vout.size(), "wrong out in transaction: internal index=" << o << ", total_outs=" << tx.vout.size()); if (tx.vout[o].target.type() == typeid(txout_to_key)) { - currency::keypair in_ephemeral; + const currency::txout_to_key& otk = boost::get(tx.vout[o].target); + + // obtain key image for this output crypto::key_image ki = currency::null_ki; - currency::generate_key_image_helper(m_account.get_keys(), tx_pub_key, o, in_ephemeral, ki); - THROW_IF_TRUE_WALLET_EX(in_ephemeral.pub != boost::get(tx.vout[o].target).key, - error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); - - auto it = m_key_images.find(ki); - if (it != m_key_images.end()) + if (m_watch_only) { - THROW_IF_TRUE_WALLET_EX(it->second >= m_transfers.size(), error::wallet_internal_error, "m_key_images entry has wrong m_transfers index, it->second: " + epee::string_tools::num_to_string_fast(it->second) + ", m_transfers.size(): " + epee::string_tools::num_to_string_fast(m_transfers.size())); - const transfer_details& local_td = m_transfers[it->second]; - WLT_LOG_YELLOW("tx " << get_transaction_hash(tx) << " @ block " << height << " has output #" << o << " with key image " << ki << " that has already been seen in output #" << - local_td.m_internal_output_index << " in tx " << get_transaction_hash(local_td.m_ptx_wallet_info->m_tx) << " @ block " << local_td.m_spent_height << - ". This output can't ever be spent and will be skipped.", LOG_LEVEL_0); - THROW_IF_TRUE_WALLET_EX(tx_money_got_in_outs < tx.vout[o].amount, error::wallet_internal_error, "tx_money_got_in_outs: " + epee::string_tools::num_to_string_fast(tx_money_got_in_outs) + ", tx.vout[o].amount:" + print_money(tx.vout[o].amount)); - tx_money_got_in_outs -= tx.vout[o].amount; - continue; // skip the output + // don't have spend secret key, so we unable to calculate key image for an output + // look it up in special container instead + auto it = m_pending_key_images.find(otk.key); + if (it != m_pending_key_images.end()) + { + ki = it->second; + WLT_LOG_L1("pending key image " << ki << " was found by out pub key " << otk.key); + } + else + { + ki = currency::null_ki; + WLT_LOG_L1("can't find pending key image by out pub key: " << otk.key << ", key image temporarily set to null"); + } + } + else + { + // normal wallet, calculate and store key images for own outs + currency::keypair in_ephemeral; + currency::generate_key_image_helper(m_account.get_keys(), tx_pub_key, o, in_ephemeral, ki); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(in_ephemeral.pub == otk.key, "key_image generated ephemeral public key that does not match with output_key"); + } + + if (ki != currency::null_ki) + { + auto it = m_key_images.find(ki); + if (it != m_key_images.end()) + { + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it->second < m_transfers.size(), "m_key_images entry has wrong m_transfers index, it->second: " << it->second << ", m_transfers.size(): " << m_transfers.size()); + const transfer_details& local_td = m_transfers[it->second]; + WLT_LOG_YELLOW("tx " << get_transaction_hash(tx) << " @ block " << height << " has output #" << o << " with key image " << ki << " that has already been seen in output #" << + local_td.m_internal_output_index << " in tx " << get_transaction_hash(local_td.m_ptx_wallet_info->m_tx) << " @ block " << local_td.m_spent_height << + ". This output can't ever be spent and will be skipped.", LOG_LEVEL_0); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(tx_money_got_in_outs >= tx.vout[o].amount, "tx_money_got_in_outs: " << tx_money_got_in_outs << ", tx.vout[o].amount:" << tx.vout[o].amount); + tx_money_got_in_outs -= tx.vout[o].amount; + continue; // skip the output + } } - mtd.receive_indices.push_back(o); @@ -314,7 +337,8 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t } } size_t transfer_index = m_transfers.size()-1; - m_key_images[td.m_key_image] = transfer_index; + if (td.m_key_image != currency::null_ki) + m_key_images[td.m_key_image] = transfer_index; add_transfer_to_transfers_cache(tx.vout[o].amount, transfer_index); WLT_LOG_L0("Received money, transfer #" << transfer_index << ", amount: " << print_money(td.amount()) << ", with tx: " << get_transaction_hash(tx) << ", at height " << height); } @@ -1686,6 +1710,7 @@ bool wallet2::reset_all() m_blockchain.clear(); m_transfers.clear(); m_key_images.clear(); + // m_pending_key_images is not cleared intentionally m_unconfirmed_in_transfers.clear(); m_unconfirmed_txs.clear(); m_unconfirmed_multisig_transfers.clear(); @@ -1702,11 +1727,16 @@ bool wallet2::reset_all() return true; } //---------------------------------------------------------------------------------------------------- -bool wallet2::store_keys(std::string& buff, const std::string& password) +bool wallet2::store_keys(std::string& buff, const std::string& password, bool store_as_watch_only /* = false */) { + currency::account_base acc = m_account; + if (store_as_watch_only) + acc.make_account_watch_only(); + std::string account_data; - bool r = epee::serialization::store_t_to_binary(m_account, account_data); + bool r = epee::serialization::store_t_to_binary(acc, account_data); WLT_CHECK_AND_ASSERT_MES(r, false, "failed to serialize wallet keys"); + wallet2::keys_file_data keys_file_data = boost::value_initialized(); crypto::chacha8_key key; @@ -1857,8 +1887,11 @@ void wallet2::generate(const std::wstring& path, const std::string& pass) init_log_prefix(); boost::system::error_code ignored_ec; THROW_IF_TRUE_WALLET_EX(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, epee::string_encoding::convert_to_ansii(m_wallet_file)); - bool stub; - load_keys2ki(true, stub); + if (m_watch_only) + { + bool stub; + load_keys2ki(true, stub); + } store(); } //---------------------------------------------------------------------------------------------------- @@ -1930,25 +1963,22 @@ void wallet2::load(const std::wstring& wallet_, const std::string& password) //---------------------------------------------------------------------------------------------------- void wallet2::store() { - store(m_wallet_file); + store(m_wallet_file, m_password, false); } //---------------------------------------------------------------------------------------------------- -void wallet2::store(const std::wstring& path_to_save) +void wallet2::store(const std::wstring& path_to_save, const std::string& password, bool store_as_watch_only) { 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, m_password); + bool r = store_keys(keys_buff, password); CHECK_AND_ASSERT_THROW_MES(r, "failed to store_keys for wallet " << epee::string_encoding::convert_to_ansii(m_wallet_file)); wallet_file_binary_header wbh = AUTO_VAL_INIT(wbh); -// std::stringstream ss; -// r = tools::portble_serialize_obj_to_stream(*this, ss); -// CHECK_AND_ASSERT_THROW_MES(r, "failed to portble_serialize_obj_to_stream for wallet " << epee::string_encoding::convert_to_ansii(m_wallet_file)); -// std::string body_buff = ss.str(); - //store data wbh.m_signature = WALLET_FILE_SIGNATURE; @@ -1965,8 +1995,18 @@ void wallet2::store(const std::wstring& path_to_save) CHECK_AND_ASSERT_THROW_MES(!data_file.fail(), "failed to open binary wallet file for saving: " << epee::string_encoding::convert_to_ansii(m_wallet_file)); data_file << header_buff << keys_buff; WLT_LOG_L0("Storing to 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)); + + 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)); + } data_file.flush(); data_file.close(); @@ -2173,8 +2213,6 @@ void wallet2::submit_transfer(const std::string& signed_tx_blob, currency::trans crypto::hash tx_hash = get_transaction_hash(ft.tx); - // foolproof check to make sure create_tx_param and create_tx_result DO match each other - try { send_transaction_to_network(ft.tx); @@ -2182,12 +2220,12 @@ void wallet2::submit_transfer(const std::string& signed_tx_blob, currency::trans 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(get_transaction_hash(ft.tx)) + " was unsuccessful"); + 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"); throw; } add_sent_tx_detailed_info(ft.tx, ft.ftp.prepared_destinations, ft.ftp.selected_transfers); - m_tx_keys.insert(std::make_pair(get_transaction_hash(ft.tx), ft.one_time_key)); + m_tx_keys.insert(std::make_pair(tx_hash, ft.one_time_key)); if (m_watch_only) { @@ -2237,9 +2275,9 @@ void wallet2::submit_transfer(const std::string& signed_tx_blob, currency::trans { THROW_IF_FALSE_WALLET_INT_ERR_EX(p.first < m_transfers.size(), "incorrect transfer index: " << p.first); auto& tr = m_transfers[p.first]; - if (tr.m_key_image != currency::null_ki) + if (tr.m_key_image != currency::null_ki && tr.m_key_image != p.second) { - LOG_PRINT_YELLOW("transfer #" << p.first << " has not null key image: " << tr.m_key_image << " will be replaced with ki " << p.second, LOG_LEVEL_0); + LOG_PRINT_YELLOW("transfer #" << p.first << " already has not null key image " << tr.m_key_image << " and it will be replaced with ki " << p.second, LOG_LEVEL_0); } tr.m_key_image = p.second; m_key_images[p.second] = p.first; @@ -2247,8 +2285,8 @@ void wallet2::submit_transfer(const std::string& signed_tx_blob, currency::trans } } - // print inputs' key images - // print tx was sent + // TODO: print inputs' key images + print_tx_sent_message(ft.tx, "(from submit_transfer)"); } //---------------------------------------------------------------------------------------------------- void wallet2::submit_transfer_files(const std::string& signed_tx_file, currency::transaction& tx) @@ -3332,6 +3370,7 @@ void wallet2::exception_handler() //---------------------------------------------------------------------------------------------------- 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 mark_transfers_with_flag(selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, reason); } //---------------------------------------------------------------------------------------------------- @@ -3667,13 +3706,17 @@ void wallet2::set_genesis(const crypto::hash& genesis_hash) m_blockchain[0] = genesis_hash; } //---------------------------------------------------------------------------------------------------- -void wallet2::print_tx_sent_message(const currency::transaction& tx, const std::string& description, uint64_t fee) +void wallet2::print_tx_sent_message(const currency::transaction& tx, const std::string& description, uint64_t fee /* = UINT64_MAX */) { //uint64_t balance_unlocked = 0; //uint64_t balance_total = balance(balance_unlocked); + std::stringstream ss; + if (fee != UINT64_MAX) + ss << "Commission: " << std::setw(21) << std::right << print_money(fee) << ENDL; + WLT_LOG_CYAN("Transaction " << get_transaction_hash(tx) << " was successfully sent " << description << ENDL - << "Commission: " << std::setw(21) << std::right << print_money(fee) << ENDL + << ss.str() // << "Balance: " << std::setw(21) << print_money(balance_total) << ENDL // << "Unlocked: " << std::setw(21) << print_money(balance_unlocked) << ENDL << "Please, wait for confirmation for your balance to be unlocked.", @@ -3746,28 +3789,6 @@ void wallet2::prepare_tx_destinations(uint64_t needed_money, } } //---------------------------------------------------------------------------------------------------- -/*void wallet2::prepare_transaction(const std::vector& dsts, - size_t fake_outputs_count, - uint64_t unlock_time, - uint64_t fee, - const std::vector& extra, - const std::vector& attachments, - tools::detail::split_strategy_id_t destination_split_strategy_id, - const tx_dust_policy& dust_policy, - const currency::account_public_address& crypt_address, - currency::transaction &tx, - uint8_t tx_outs_attr, - bool shuffle, - bool mark_tx_as_complete, - uint8_t flags, - std::vector& selected_transfers, - currency::keypair& one_time_key, - std::vector& prepared_destinations, - crypto::hash multisig_id) -{ - // TODO -}*/ -//---------------------------------------------------------------------------------------------------- void wallet2::prepare_transaction(const construct_tx_param& ctp, finalize_tx_param& ftp, const currency::transaction& tx_for_mode_separate /* = currency::transaction() */) { TIME_MEASURE_START_MS(get_needed_money_time); @@ -3905,12 +3926,10 @@ void wallet2::transfer(const std::vector& dsts, uint8_t tx_outs_attr, bool shuffle, uint8_t flags, - bool send_to_network) + bool send_to_network, + std::string* p_signed_tx_blob_str) { TIME_MEASURE_START(precalculation_time); - //using namespace currency; - currency::keypair onetime_keys = AUTO_VAL_INIT(onetime_keys); - 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); @@ -3933,7 +3952,6 @@ void wallet2::transfer(const std::vector& dsts, prepare_transaction(tx_param, 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; @@ -3941,7 +3959,9 @@ void wallet2::transfer(const std::vector& dsts, crypto::chacha_crypt(bl, m_account.get_keys().m_view_secret_key); epee::file_io_utils::save_string_to_file("unsigned_zano_tx", bl); LOG_PRINT_L0("Transaction stored to unsigned_zano_tx. You need to sign this tx using a full-access wallet."); - //relay_blob = bl; // TODO: save encrypted tx param blob to relay_blob + + if (p_signed_tx_blob_str != nullptr) + *p_signed_tx_blob_str = bl; // unlock transfers at the very end TIME_MEASURE_START(mark_transfers_as_spent_time); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index d9ae6836..d33a393e 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -297,6 +297,8 @@ namespace tools std::vector selected_transfers; std::vector prepared_destinations; + crypto::public_key spend_pub_key; // only for validations + BEGIN_SERIALIZE_OBJECT() FIELD(unlock_time) FIELD(extra) @@ -309,6 +311,7 @@ namespace tools FIELD(sources) FIELD(selected_transfers) FIELD(prepared_destinations) + FIELD(spend_pub_key) END_SERIALIZE() }; @@ -468,7 +471,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); + void store(const std::wstring& path, const std::string& password, bool store_as_watch_only = false); + 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; } const currency::account_base& get_account() const { return m_account; } @@ -534,7 +538,8 @@ namespace tools uint8_t tx_outs_attr = CURRENCY_TO_KEY_OUT_RELAXED, bool shuffle = true, uint8_t flags = 0, - bool send_to_network = true); + bool send_to_network = true, + std::string* p_signed_tx_blob_str = nullptr); void transfer(const std::vector& dsts, size_t fake_outputs_count, @@ -698,25 +703,6 @@ namespace tools const std::list& get_expiration_entries() const { return m_money_expirations; }; bool get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key) const; - /*void prepare_transaction(const std::vector& dsts, - size_t fake_outputs_count, - uint64_t unlock_time, - uint64_t fee, - const std::vector& extra, - const std::vector& attachments, - detail::split_strategy_id_t destination_split_strategy_id, - const tx_dust_policy& dust_policy, - const currency::account_public_address& crypt_address, - OUT currency::transaction &tx, - uint8_t tx_outs_attr, - bool shuffle, - bool mark_tx_as_complete, - uint8_t flags, - OUT std::vector& selected_transfers, - OUT currency::keypair& one_time_key, - OUT std::vector& prepared_destinations, - crypto::hash multisig_id = currency::null_hash);*/ - void prepare_transaction(const construct_tx_param& ctp, finalize_tx_param& ftp, const currency::transaction& tx_for_mode_separate = currency::transaction()); void finalize_transaction(const finalize_tx_param& ftp, currency::transaction& tx, crypto::secret_key& tx_key, bool broadcast_tx); @@ -725,7 +711,6 @@ namespace tools private: void add_transfers_to_expiration_list(const std::vector& selected_transfers, uint64_t expiration, uint64_t change_amount, const crypto::hash& related_tx_id); void remove_transfer_from_expiration_list(uint64_t transfer_index); - bool store_keys(std::string& buff, const std::string& password); void load_keys(const std::string& keys_file_name, const std::string& password); void process_new_transaction(const currency::transaction& tx, uint64_t height, const currency::block& b); void detach_blockchain(uint64_t height); @@ -797,7 +782,7 @@ private: uint64_t get_tx_expiration_median() const; - void print_tx_sent_message(const currency::transaction& tx, const std::string& description, uint64_t fee); + void print_tx_sent_message(const currency::transaction& tx, const std::string& description, uint64_t fee = UINT64_MAX); // Validates escrow template tx in assumption it's related to wallet's account (wallet's account is either A or B party in escrow process) bool validate_escrow_proposal(const wallet_rpc::wallet_transfer_info& wti, const bc_services::proposal_body& prop, diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index 0903f5b5..2b7a94a4 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -676,6 +676,7 @@ if (cond) tools::error::throw_wallet_ex(std::string(__FILE__ ":" STRINGIZE(__LINE__)), ss.str()); \ } +#define THROW_IF_FALSE_WALLET_INT_ERR_EX_NO_HANDLER(cond, mess) THROW_IF_TRUE_WALLET_INT_ERR_EX_NO_HANDLER((!(cond)), mess) #define THROW_IF_FALSE_WALLET_INT_ERR_EX(cond, mess) THROW_IF_TRUE_WALLET_INT_ERR_EX((!(cond)), mess) diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 6c9f49e0..7dfc3f64 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -158,8 +158,17 @@ namespace tools currency::transaction tx; std::vector extra; - m_wallet.transfer(dsts, req.mixin, req.unlock_time, req.fee, extra, attachments, tx); - res.tx_hash = epee::string_tools::pod_to_hex(currency::get_transaction_hash(tx)); + std::string signed_tx_blob_str; + m_wallet.transfer(dsts, req.mixin, req.unlock_time, req.fee, extra, attachments, detail::ssi_digit, tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx, CURRENCY_TO_KEY_OUT_RELAXED, true, 0, true, &signed_tx_blob_str); + if (m_wallet.is_watch_only()) + { + res.tx_unsigned_hex = epee::string_tools::buff_to_hex_nodelimer(signed_tx_blob_str); // watch-only wallets can't sign and relay transactions + // leave res.tx_hash empty, because tx has will change after signing + } + else + { + res.tx_hash = epee::string_tools::pod_to_hex(currency::get_transaction_hash(tx)); + } return true; } catch (const tools::error::daemon_busy& e) @@ -337,14 +346,6 @@ namespace tools //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_submit_transfer(const wallet_rpc::COMMAND_SUBMIT_TRANSFER::request& req, wallet_rpc::COMMAND_SUBMIT_TRANSFER::response& res, epee::json_rpc::error& er, connection_context& cntx) { - //std::string tx_unsigned_blob; - //if (!string_tools::parse_hexstr_to_binbuff(req.tx_unsigned_hex, tx_unsigned_blob)) - //{ - // er.code = WALLET_RPC_ERROR_CODE_WRONG_ARGUMENT; - // er.message = "tx_unsigned_hex is invalid"; - // return false; - //} - std::string tx_signed_blob; if (!string_tools::parse_hexstr_to_binbuff(req.tx_signed_hex, tx_signed_blob)) { @@ -356,7 +357,7 @@ namespace tools try { currency::transaction tx = AUTO_VAL_INIT(tx); - // TODO m_wallet.submit_transfer(tx_unsigned_blob, tx_signed_blob, tx); + m_wallet.submit_transfer(tx_signed_blob, tx); res.tx_hash = epee::string_tools::pod_to_hex(currency::get_transaction_hash(tx)); } catch (const std::exception& e) @@ -371,290 +372,9 @@ namespace tools er.message = "WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR"; return false; } - return true; - } - //------------------------------------------------------------------------------------------------------------------------------ - - - - - - - - - - - //------------------------------------------------------------------------------------------------------------------------------ - bool wallet_rpc_server::on_maketelepod(const wallet_rpc::COMMAND_RPC_MAKETELEPOD::request& req, wallet_rpc::COMMAND_RPC_MAKETELEPOD::response& res, epee::json_rpc::error& er, connection_context& cntx) - { - //check available balance - if (m_wallet.unlocked_balance() <= req.amount) - { - res.status = "INSUFFICIENT_COINS"; - return true; - } - - currency::account_base acc; - acc.generate(); - std::vector dsts(1); - dsts.back().amount = req.amount; - dsts.back().addr.resize(1); - dsts.back().addr.back() = acc.get_keys().m_account_address; - currency::transaction tx = AUTO_VAL_INIT(tx); - try - { - std::vector extra; - std::vector attachments; - - m_wallet.transfer(dsts, 0, 0, m_wallet.get_core_runtime_config().tx_default_fee, extra, attachments, tx); - } - catch (const std::runtime_error& er) - { - LOG_ERROR("Failed to send transaction: " << er.what()); - res.status = "INTERNAL_ERROR"; - return true; - } - - res.tpd.basement_tx_id_hex = string_tools::pod_to_hex(currency::get_transaction_hash(tx)); - std::string buff = epee::serialization::store_t_to_binary(acc); - res.tpd.account_keys_hex = string_tools::buff_to_hex_nodelimer(buff); - - res.status = "OK"; - LOG_PRINT_GREEN("TELEPOD ISSUED [" << currency::print_money(req.amount) << "BBR, base_tx_id: ]" << currency::get_transaction_hash(tx), LOG_LEVEL_0); return true; } //------------------------------------------------------------------------------------------------------------------------------ - bool wallet_rpc_server::build_transaction_from_telepod(const wallet_rpc::telepod& tlp, const currency::account_public_address& acc2, currency::transaction& tx2, std::string& status) - { - //check if base transaction confirmed - currency::COMMAND_RPC_GET_TRANSACTIONS::request get_tx_req = AUTO_VAL_INIT(get_tx_req); - currency::COMMAND_RPC_GET_TRANSACTIONS::response get_tx_rsp = AUTO_VAL_INIT(get_tx_rsp); - get_tx_req.txs_hashes.push_back(tlp.basement_tx_id_hex); - if (!m_wallet.get_core_proxy()->call_COMMAND_RPC_GET_TRANSACTIONS(get_tx_req, get_tx_rsp) - || get_tx_rsp.status != CORE_RPC_STATUS_OK - || !get_tx_rsp.txs_as_hex.size()) - { - status = "UNCONFIRMED"; - return false; - } - - //extract account keys - std::string acc_buff; - currency::account_base acc = AUTO_VAL_INIT(acc); - if (!string_tools::parse_hexstr_to_binbuff(tlp.account_keys_hex, acc_buff)) - { - LOG_ERROR("Failed to parse_hexstr_to_binbuff(tlp.account_keys_hex, acc_buff)"); - status = "BAD"; - return false; - } - if (!epee::serialization::load_t_from_binary(acc, acc_buff)) - { - LOG_ERROR("Failed to load_t_from_binary(acc, acc_buff)"); - status = "BAD"; - return false; - } - - //extract transaction - currency::transaction tx = AUTO_VAL_INIT(tx); - std::string buff; - if (!string_tools::parse_hexstr_to_binbuff(get_tx_rsp.txs_as_hex.back(), buff)) - { - LOG_ERROR("Failed to parse_hexstr_to_binbuff(get_tx_rsp.txs_as_hex.back(), buff)"); - status = "INTERNAL_ERROR"; - return false; - } - if (!currency::parse_and_validate_tx_from_blob(buff, tx)) - { - LOG_ERROR("Failed to currency::parse_and_validate_tx_from_blob(buff, tx)"); - status = "INTERNAL_ERROR"; - return false; - } - - crypto::public_key tx_pub_key = currency::get_tx_pub_key_from_extra(tx); - if (tx_pub_key == currency::null_pkey) - { - LOG_ERROR("Failed to currency::get_tx_pub_key_from_extra(tx)"); - status = "BAD"; - return false; - - } - - //get transaction global output indices - currency::COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::request get_ind_req = AUTO_VAL_INIT(get_ind_req); - currency::COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::response get_ind_rsp = AUTO_VAL_INIT(get_ind_rsp); - get_ind_req.txid = currency::get_transaction_hash(tx); - if (!m_wallet.get_core_proxy()->call_COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES(get_ind_req, get_ind_rsp) - || get_ind_rsp.status != CORE_RPC_STATUS_OK - || get_ind_rsp.o_indexes.size() != tx.vout.size()) - { - LOG_ERROR("Problem with call_COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES(....) "); - status = "INTERNAL_ERROR"; - return false; - } - - //prepare inputs - std::vector sources; - size_t i = 0; - uint64_t amount = 0; - for (auto& o : get_ind_rsp.o_indexes) - { - //check if input is for telepod's address - if (currency::is_out_to_acc(acc.get_keys(), boost::get(tx.vout[i].target), tx_pub_key, i)) - { - //income output - amount += tx.vout[i].amount; - sources.resize(sources.size() + 1); - currency::tx_source_entry& tse = sources.back(); - tse.amount = tx.vout[i].amount; - tse.outputs.push_back(currency::tx_source_entry::output_entry(o, boost::get(tx.vout[i].target).key)); - tse.real_out_tx_key = tx_pub_key; - tse.real_output = 0; - tse.real_output_in_tx_index = i; - } - ++i; - } - - - //prepare outputs - std::vector dsts(1); - currency::tx_destination_entry& dst = dsts.back(); - dst.addr.push_back(acc2); - dst.amount = amount - m_wallet.get_core_runtime_config().tx_default_fee; - - //generate transaction - const std::vector extra; - const std::vector attachments; - crypto::secret_key sk; - bool r = currency::construct_tx(acc.get_keys(), sources, dsts, extra, attachments, tx2, sk, 0); - if (!r) - { - LOG_ERROR("Problem with construct_tx(....) "); - status = "INTERNAL_ERROR"; - return false; - } - if (CURRENCY_MAX_TRANSACTION_BLOB_SIZE <= get_object_blobsize(tx2)) - { - LOG_ERROR("Problem with construct_tx(....), blobl size os too big: " << get_object_blobsize(tx2)); - status = "INTERNAL_ERROR"; - return false; - } - - return true; - } - //------------------------------------------------------------------------------------------------------------------------------ - bool wallet_rpc_server::on_clonetelepod(const wallet_rpc::COMMAND_RPC_CLONETELEPOD::request& req, wallet_rpc::COMMAND_RPC_CLONETELEPOD::response& res, epee::json_rpc::error& er, connection_context& cntx) - { - currency::transaction tx2 = AUTO_VAL_INIT(tx2); - //new destination account - currency::account_base acc2 = AUTO_VAL_INIT(acc2); - acc2.generate(); - - if (!build_transaction_from_telepod(req.tpd, acc2.get_keys().m_account_address, tx2, res.status)) - { - LOG_ERROR("Failed to build_transaction_from_telepod(...)"); - return true; - } - - //send transaction to daemon - currency::COMMAND_RPC_SEND_RAW_TX::request req_send_raw; - req_send_raw.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(tx2)); - currency::COMMAND_RPC_SEND_RAW_TX::response rsp_send_raw; - bool r = m_wallet.get_core_proxy()->call_COMMAND_RPC_SEND_RAW_TX(req_send_raw, rsp_send_raw); - if (!r || rsp_send_raw.status != CORE_RPC_STATUS_OK) - { - LOG_ERROR("Problem with construct_tx(....), blobl size os too big: " << get_object_blobsize(tx2)); - res.status = "INTERNAL_ERROR"; - return true; - } - - res.tpd.basement_tx_id_hex = string_tools::pod_to_hex(currency::get_transaction_hash(tx2)); - std::string acc2_buff = epee::serialization::store_t_to_binary(acc2); - res.tpd.account_keys_hex = string_tools::buff_to_hex_nodelimer(acc2_buff); - - res.status = "OK"; - LOG_PRINT_GREEN("TELEPOD ISSUED [" << currency::print_money(currency::get_outs_money_amount(tx2)) << "BBR, base_tx_id: ]" << currency::get_transaction_hash(tx2), LOG_LEVEL_0); - - return true; - } - //------------------------------------------------------------------------------------------------------------------------------ - bool wallet_rpc_server::on_telepodstatus(const wallet_rpc::COMMAND_RPC_TELEPODSTATUS::request& req, wallet_rpc::COMMAND_RPC_TELEPODSTATUS::response& res, epee::json_rpc::error& er, connection_context& cntx) - { - currency::transaction tx2 = AUTO_VAL_INIT(tx2); - //new destination account - currency::account_base acc2 = AUTO_VAL_INIT(acc2); - acc2.generate(); - - if (!build_transaction_from_telepod(req.tpd, acc2.get_keys().m_account_address, tx2, res.status)) - { - return true; - } - //check if transaction is spent - currency::COMMAND_RPC_CHECK_KEYIMAGES::request req_ki = AUTO_VAL_INIT(req_ki); - currency::COMMAND_RPC_CHECK_KEYIMAGES::response rsp_ki = AUTO_VAL_INIT(rsp_ki); - for (auto& i : tx2.vin) - req_ki.images.push_back(boost::get(i).k_image); - - if (!m_wallet.get_core_proxy()->call_COMMAND_RPC_COMMAND_RPC_CHECK_KEYIMAGES(req_ki, rsp_ki) - || rsp_ki.status != CORE_RPC_STATUS_OK - || rsp_ki.images_stat.size() != req_ki.images.size()) - { - LOG_ERROR("Problem with call_COMMAND_RPC_COMMAND_RPC_CHECK_KEYIMAGES(....)"); - res.status = "INTERNAL_ERROR"; - return true; - } - - for (auto s : rsp_ki.images_stat) - { - if (!s) - { - res.status = "SPENT"; - return true; - } - } - - res.status = "OK"; - return true; - } - //------------------------------------------------------------------------------------------------------------------------------ - bool wallet_rpc_server::on_withdrawtelepod(const wallet_rpc::COMMAND_RPC_WITHDRAWTELEPOD::request& req, wallet_rpc::COMMAND_RPC_WITHDRAWTELEPOD::response& res, epee::json_rpc::error& er, connection_context& cntx) - { - currency::transaction tx2 = AUTO_VAL_INIT(tx2); - //parse destination add - currency::account_public_address acc_addr = AUTO_VAL_INIT(acc_addr); - if (!currency::get_account_address_from_str(acc_addr, req.addr)) - { - LOG_ERROR("Failed to build_transaction_from_telepod(...)"); - res.status = "BAD_ADDRESS"; - return true; - } - - - if (!build_transaction_from_telepod(req.tpd, acc_addr, tx2, res.status)) - { - LOG_ERROR("Failed to build_transaction_from_telepod(...)"); - return true; - } - - //send transaction to daemon - currency::COMMAND_RPC_SEND_RAW_TX::request req_send_raw; - req_send_raw.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(tx2)); - currency::COMMAND_RPC_SEND_RAW_TX::response rsp_send_raw; - bool r = m_wallet.get_core_proxy()->call_COMMAND_RPC_SEND_RAW_TX(req_send_raw, rsp_send_raw); - if (!r || rsp_send_raw.status != CORE_RPC_STATUS_OK) - { - LOG_ERROR("Problem with construct_tx(....), blobl size os too big: " << get_object_blobsize(tx2)); - res.status = "INTERNAL_ERROR"; - return true; - } - - res.status = "OK"; - LOG_PRINT_GREEN("TELEPOD WITHDRAWN [" << currency::print_money(currency::get_outs_money_amount(tx2)) << "BBR, tx_id: ]" << currency::get_transaction_hash(tx2), LOG_LEVEL_0); - - return true; - } - -} - - +} // namespace tools diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 036de6b9..d643129c 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -48,12 +48,6 @@ namespace tools MAP_JON_RPC_WE("split_integrated_address", on_split_integrated_address, wallet_rpc::COMMAND_RPC_SPLIT_INTEGRATED_ADDRESS) MAP_JON_RPC_WE("sign_transfer", on_sign_transfer, wallet_rpc::COMMAND_SIGN_TRANSFER) MAP_JON_RPC_WE("submit_transfer", on_submit_transfer, wallet_rpc::COMMAND_SUBMIT_TRANSFER) - - // supernet api - MAP_JON_RPC_WE("maketelepod", on_maketelepod, wallet_rpc::COMMAND_RPC_MAKETELEPOD) - MAP_JON_RPC_WE("clonetelepod", on_clonetelepod, wallet_rpc::COMMAND_RPC_CLONETELEPOD) - MAP_JON_RPC_WE("telepodstatus", on_telepodstatus, wallet_rpc::COMMAND_RPC_TELEPODSTATUS) - MAP_JON_RPC_WE("withdrawtelepod", on_withdrawtelepod, wallet_rpc::COMMAND_RPC_WITHDRAWTELEPOD) END_JSON_RPC_MAP() END_URI_MAP2() @@ -69,13 +63,7 @@ namespace tools bool on_sign_transfer(const wallet_rpc::COMMAND_SIGN_TRANSFER::request& req, wallet_rpc::COMMAND_SIGN_TRANSFER::response& res, epee::json_rpc::error& er, connection_context& cntx); bool on_submit_transfer(const wallet_rpc::COMMAND_SUBMIT_TRANSFER::request& req, wallet_rpc::COMMAND_SUBMIT_TRANSFER::response& res, epee::json_rpc::error& er, connection_context& cntx); - bool on_maketelepod(const wallet_rpc::COMMAND_RPC_MAKETELEPOD::request& req, wallet_rpc::COMMAND_RPC_MAKETELEPOD::response& res, epee::json_rpc::error& er, connection_context& cntx); - bool on_clonetelepod(const wallet_rpc::COMMAND_RPC_CLONETELEPOD::request& req, wallet_rpc::COMMAND_RPC_CLONETELEPOD::response& res, epee::json_rpc::error& er, connection_context& cntx); - bool on_telepodstatus(const wallet_rpc::COMMAND_RPC_TELEPODSTATUS::request& req, wallet_rpc::COMMAND_RPC_TELEPODSTATUS::response& res, epee::json_rpc::error& er, connection_context& cntx); - bool on_withdrawtelepod(const wallet_rpc::COMMAND_RPC_WITHDRAWTELEPOD::request& req, wallet_rpc::COMMAND_RPC_WITHDRAWTELEPOD::response& res, epee::json_rpc::error& er, connection_context& cntx); - bool handle_command_line(const boost::program_options::variables_map& vm); - bool build_transaction_from_telepod(const wallet_rpc::telepod& tlp, const currency::account_public_address& acc2, currency::transaction& tx2, std::string& status); private: wallet2& m_wallet; @@ -83,4 +71,5 @@ namespace tools std::string m_bind_ip; bool m_do_mint; }; -} + +} // namespace tools diff --git a/src/wallet/wallet_rpc_server_commans_defs.h b/src/wallet/wallet_rpc_server_commans_defs.h index ac025b44..2c4ef2ba 100644 --- a/src/wallet/wallet_rpc_server_commans_defs.h +++ b/src/wallet/wallet_rpc_server_commans_defs.h @@ -231,9 +231,11 @@ namespace wallet_rpc struct response { std::string tx_hash; + std::string tx_unsigned_hex; // for cold-signing process BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash) + KV_SERIALIZE(tx_unsigned_hex) END_KV_SERIALIZE_MAP() }; }; From daa01fd53308034a992fa81c847593c65147aaaf Mon Sep 17 00:00:00 2001 From: sowle Date: Fri, 12 Apr 2019 09:48:33 +0300 Subject: [PATCH 07/13] 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 From 6f682bf79de8e8b35e561bb4fa63f6201c9ede2d Mon Sep 17 00:00:00 2001 From: sowle Date: Fri, 12 Apr 2019 16:38:36 +0300 Subject: [PATCH 08/13] attempt to fix gcc compilation --- contrib/zlib/test/infcover.c | 2 +- contrib/zlib/test/minigzip.c | 2 +- src/wallet/wallet2.cpp | 18 ++++++++++++------ 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/contrib/zlib/test/infcover.c b/contrib/zlib/test/infcover.c index 2be01646..e45b6cbb 100644 --- a/contrib/zlib/test/infcover.c +++ b/contrib/zlib/test/infcover.c @@ -9,7 +9,7 @@ #include #include #include -#include "zlib.h" +#include "../zlib.h" /* get definition of internal structure so we can mess with it (see pull()), and so we can call inflate_trees() (see cover5()) */ diff --git a/contrib/zlib/test/minigzip.c b/contrib/zlib/test/minigzip.c index e22fb08c..93de87ba 100644 --- a/contrib/zlib/test/minigzip.c +++ b/contrib/zlib/test/minigzip.c @@ -15,7 +15,7 @@ /* @(#) $Id$ */ -#include "zlib.h" +#include "../zlib.h" #include #ifdef STDC diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 49cd5241..7ecfe64e 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -651,7 +651,8 @@ void wallet2::request_cancel_contract(const crypto::hash& contract_id, uint64_t finalize_tx_param ftp = AUTO_VAL_INIT(ftp); prepare_transaction(construct_param, ftp); currency::transaction tx = AUTO_VAL_INIT(tx); - finalize_transaction(ftp, tx, crypto::secret_key(), true); + crypto::secret_key sk = AUTO_VAL_INIT(sk); + finalize_transaction(ftp, tx, sk, true); mark_transfers_as_spent(ftp.selected_transfers, std::string("contract <") + epee::string_tools::pod_to_hex(contract_id) + "> has been requested for cancellaton with tx <" + epee::string_tools::pod_to_hex(get_transaction_hash(tx)) + ">"); print_tx_sent_message(tx, "(transport for cancel proposal)", fee); @@ -2992,7 +2993,8 @@ void wallet2::build_escrow_release_templates(crypto::hash multisig_id, { finalize_tx_param ftp = AUTO_VAL_INIT(ftp); prepare_transaction(construct_params, ftp); - finalize_transaction(ftp, tx_release_template, crypto::secret_key(), false); + crypto::secret_key sk = AUTO_VAL_INIT(sk); + finalize_transaction(ftp, tx_release_template, sk, false); } //generate burn escrow @@ -3007,7 +3009,8 @@ void wallet2::build_escrow_release_templates(crypto::hash multisig_id, { finalize_tx_param ftp = AUTO_VAL_INIT(ftp); prepare_transaction(construct_params, ftp); - finalize_transaction(ftp, tx_burn_template, crypto::secret_key(), false); + crypto::secret_key sk = AUTO_VAL_INIT(sk); + finalize_transaction(ftp, tx_burn_template, sk, false); } } //---------------------------------------------------------------------------------------------------- @@ -3047,7 +3050,8 @@ void wallet2::build_escrow_cancel_template(crypto::hash multisig_id, construct_params.extra.push_back(expir); prepare_transaction(construct_params, ftp); - finalize_transaction(ftp, tx_cancel_template, crypto::secret_key(), false); + crypto::secret_key sk = AUTO_VAL_INIT(sk); + finalize_transaction(ftp, tx_cancel_template, sk, false); } //---------------------------------------------------------------------------------------------------- @@ -3228,7 +3232,8 @@ void wallet2::send_escrow_proposal(const bc_services::contract_private_details& try { prepare_transaction(ctp, ftp); - finalize_transaction(ftp, tx, crypto::secret_key(), false); + crypto::secret_key sk = AUTO_VAL_INIT(sk); + finalize_transaction(ftp, tx, sk, false); } catch (...) { @@ -4042,7 +4047,8 @@ void wallet2::transfer(const std::vector& dsts, } TIME_MEASURE_START(finalize_transaction_time); - finalize_transaction(ftp, tx, crypto::secret_key(), send_to_network); + crypto::secret_key sk = AUTO_VAL_INIT(sk); + finalize_transaction(ftp, tx, sk, send_to_network); TIME_MEASURE_FINISH(finalize_transaction_time); // unlock transfers at the very end From e253876a932f5a9e750781fda1eb027eb99ea66a Mon Sep 17 00:00:00 2001 From: sowle Date: Sat, 13 Apr 2019 01:10:29 +0300 Subject: [PATCH 09/13] gui build fixed --- src/wallet/wallet2.cpp | 5 +++++ src/wallet/wallet2.h | 1 + 2 files changed, 6 insertions(+) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 7ecfe64e..3f5c26ba 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -1981,6 +1981,11 @@ void wallet2::store() store(m_wallet_file, m_password); } //---------------------------------------------------------------------------------------------------- +void wallet2::store(const std::wstring& path) +{ + store(path, m_password); +} +//---------------------------------------------------------------------------------------------------- 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() << ")"); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 0664f08c..783aeb39 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -446,6 +446,7 @@ 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); 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); From 4fbca90553a97139f383fe399f982b237fedf21d Mon Sep 17 00:00:00 2001 From: sowle Date: Tue, 16 Apr 2019 09:37:27 +0300 Subject: [PATCH 10/13] serialization: std::pair compilation fix for gcc --- src/serialization/stl_containers.h | 37 ++++++++++++++++-------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/serialization/stl_containers.h b/src/serialization/stl_containers.h index ae4d22ac..c3bb800e 100644 --- a/src/serialization/stl_containers.h +++ b/src/serialization/stl_containers.h @@ -9,28 +9,12 @@ #include #include -//#include "serialization.h" -template