From d2d1bf5d7ac727d857473813a1a0986e7de1fe4f Mon Sep 17 00:00:00 2001 From: sowle Date: Wed, 3 Apr 2019 12:48:09 +0300 Subject: [PATCH] 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