diff --git a/src/currency_core/account.cpp b/src/currency_core/account.cpp index a291feb8..39f6675e 100644 --- a/src/currency_core/account.cpp +++ b/src/currency_core/account.cpp @@ -122,11 +122,16 @@ namespace currency return get_account_address_as_str(m_keys.m_account_address); } //----------------------------------------------------------------- + void account_base::make_account_watch_only() + { + m_keys.m_spend_secret_key = currency::null_skey; + } + //----------------------------------------------------------------- std::string transform_addr_to_str(const account_public_address& addr) { return get_account_address_as_str(addr); } - + //----------------------------------------------------------------- account_public_address transform_str_to_addr(const std::string& str) { account_public_address ad = AUTO_VAL_INIT(ad); diff --git a/src/currency_core/account.h b/src/currency_core/account.h index 656c497c..f39e1a61 100644 --- a/src/currency_core/account.h +++ b/src/currency_core/account.h @@ -63,6 +63,8 @@ namespace currency bool load(const std::string& file_path); bool store(const std::string& file_path); + void make_account_watch_only(); + template inline void serialize(t_archive &a, const unsigned int /*ver*/) { diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 83615919..f86dd2c6 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -206,6 +206,7 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("integrated_address", boost::bind(&simple_wallet::integrated_address, this, _1), "integrated_address [|"); + m_cmd_binder.set_handler("save_watch_only", boost::bind(&simple_wallet::save_watch_only, this, _1), "Save a watch-only keys file ."); } //---------------------------------------------------------------------------------------------------- @@ -1280,6 +1281,31 @@ void simple_wallet::set_offline_mode(bool offline_mode) m_offline_mode = offline_mode; } //---------------------------------------------------------------------------------------------------- +bool simple_wallet::save_watch_only(const std::vector &args) +{ + if (args.size() < 2) + { + fail_msg_writer() << "wrong parameters, expected filename and password"; + return true; + } + try + { + m_wallet->store(epee::string_encoding::convert_to_unicode(args[0]), args[1], true); + success_msg_writer() << "Watch-only wallet has been stored to " << args[0]; + } + catch (const std::exception& e) + { + LOG_ERROR("unexpected error: " << e.what()); + fail_msg_writer() << "unexpected error: " << e.what(); + } + catch (...) + { + LOG_ERROR("Unknown error"); + fail_msg_writer() << "unknown error"; + } + return true; +} +//---------------------------------------------------------------------------------------------------- int main(int argc, char* argv[]) { #ifdef WIN32 diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index dca21675..30c76618 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -74,6 +74,7 @@ namespace currency bool enable_concole_logger(const std::vector &args); bool integrated_address(const std::vector &args); bool get_tx_key(const std::vector &args_); + bool save_watch_only(const std::vector &args); bool get_alias_from_daemon(const std::string& alias_name, currency::extra_alias_entry_base& ai); bool get_transfer_address(const std::string& adr_str, currency::account_public_address& addr); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index f79c9285..cde719ec 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -272,29 +272,52 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t for (size_t i_in_outs = 0; i_in_outs != outs.size(); i_in_outs++) { size_t o = outs[i_in_outs]; - THROW_IF_TRUE_WALLET_EX(tx.vout.size() <= o, error::wallet_internal_error, "wrong out in transaction: internal index=" + - std::to_string(o) + ", total_outs=" + std::to_string(tx.vout.size())); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(o < tx.vout.size(), "wrong out in transaction: internal index=" << o << ", total_outs=" << tx.vout.size()); if (tx.vout[o].target.type() == typeid(txout_to_key)) { - currency::keypair in_ephemeral; + const currency::txout_to_key& otk = boost::get(tx.vout[o].target); + + // obtain key image for this output crypto::key_image ki = currency::null_ki; - currency::generate_key_image_helper(m_account.get_keys(), tx_pub_key, o, in_ephemeral, ki); - THROW_IF_TRUE_WALLET_EX(in_ephemeral.pub != boost::get(tx.vout[o].target).key, - error::wallet_internal_error, "key_image generated ephemeral public key not matched with output_key"); - - auto it = m_key_images.find(ki); - if (it != m_key_images.end()) + if (m_watch_only) { - THROW_IF_TRUE_WALLET_EX(it->second >= m_transfers.size(), error::wallet_internal_error, "m_key_images entry has wrong m_transfers index, it->second: " + epee::string_tools::num_to_string_fast(it->second) + ", m_transfers.size(): " + epee::string_tools::num_to_string_fast(m_transfers.size())); - const transfer_details& local_td = m_transfers[it->second]; - WLT_LOG_YELLOW("tx " << get_transaction_hash(tx) << " @ block " << height << " has output #" << o << " with key image " << ki << " that has already been seen in output #" << - local_td.m_internal_output_index << " in tx " << get_transaction_hash(local_td.m_ptx_wallet_info->m_tx) << " @ block " << local_td.m_spent_height << - ". This output can't ever be spent and will be skipped.", LOG_LEVEL_0); - THROW_IF_TRUE_WALLET_EX(tx_money_got_in_outs < tx.vout[o].amount, error::wallet_internal_error, "tx_money_got_in_outs: " + epee::string_tools::num_to_string_fast(tx_money_got_in_outs) + ", tx.vout[o].amount:" + print_money(tx.vout[o].amount)); - tx_money_got_in_outs -= tx.vout[o].amount; - continue; // skip the output + // don't have spend secret key, so we unable to calculate key image for an output + // look it up in special container instead + auto it = m_pending_key_images.find(otk.key); + if (it != m_pending_key_images.end()) + { + ki = it->second; + WLT_LOG_L1("pending key image " << ki << " was found by out pub key " << otk.key); + } + else + { + ki = currency::null_ki; + WLT_LOG_L1("can't find pending key image by out pub key: " << otk.key << ", key image temporarily set to null"); + } + } + else + { + // normal wallet, calculate and store key images for own outs + currency::keypair in_ephemeral; + currency::generate_key_image_helper(m_account.get_keys(), tx_pub_key, o, in_ephemeral, ki); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(in_ephemeral.pub == otk.key, "key_image generated ephemeral public key that does not match with output_key"); + } + + if (ki != currency::null_ki) + { + auto it = m_key_images.find(ki); + if (it != m_key_images.end()) + { + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it->second < m_transfers.size(), "m_key_images entry has wrong m_transfers index, it->second: " << it->second << ", m_transfers.size(): " << m_transfers.size()); + const transfer_details& local_td = m_transfers[it->second]; + WLT_LOG_YELLOW("tx " << get_transaction_hash(tx) << " @ block " << height << " has output #" << o << " with key image " << ki << " that has already been seen in output #" << + local_td.m_internal_output_index << " in tx " << get_transaction_hash(local_td.m_ptx_wallet_info->m_tx) << " @ block " << local_td.m_spent_height << + ". This output can't ever be spent and will be skipped.", LOG_LEVEL_0); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(tx_money_got_in_outs >= tx.vout[o].amount, "tx_money_got_in_outs: " << tx_money_got_in_outs << ", tx.vout[o].amount:" << tx.vout[o].amount); + tx_money_got_in_outs -= tx.vout[o].amount; + continue; // skip the output + } } - mtd.receive_indices.push_back(o); @@ -314,7 +337,8 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t } } size_t transfer_index = m_transfers.size()-1; - m_key_images[td.m_key_image] = transfer_index; + if (td.m_key_image != currency::null_ki) + m_key_images[td.m_key_image] = transfer_index; add_transfer_to_transfers_cache(tx.vout[o].amount, transfer_index); WLT_LOG_L0("Received money, transfer #" << transfer_index << ", amount: " << print_money(td.amount()) << ", with tx: " << get_transaction_hash(tx) << ", at height " << height); } @@ -1686,6 +1710,7 @@ bool wallet2::reset_all() m_blockchain.clear(); m_transfers.clear(); m_key_images.clear(); + // m_pending_key_images is not cleared intentionally m_unconfirmed_in_transfers.clear(); m_unconfirmed_txs.clear(); m_unconfirmed_multisig_transfers.clear(); @@ -1702,11 +1727,16 @@ bool wallet2::reset_all() return true; } //---------------------------------------------------------------------------------------------------- -bool wallet2::store_keys(std::string& buff, const std::string& password) +bool wallet2::store_keys(std::string& buff, const std::string& password, bool store_as_watch_only /* = false */) { + currency::account_base acc = m_account; + if (store_as_watch_only) + acc.make_account_watch_only(); + std::string account_data; - bool r = epee::serialization::store_t_to_binary(m_account, account_data); + bool r = epee::serialization::store_t_to_binary(acc, account_data); WLT_CHECK_AND_ASSERT_MES(r, false, "failed to serialize wallet keys"); + wallet2::keys_file_data keys_file_data = boost::value_initialized(); crypto::chacha8_key key; @@ -1857,8 +1887,11 @@ void wallet2::generate(const std::wstring& path, const std::string& pass) init_log_prefix(); boost::system::error_code ignored_ec; THROW_IF_TRUE_WALLET_EX(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, epee::string_encoding::convert_to_ansii(m_wallet_file)); - bool stub; - load_keys2ki(true, stub); + if (m_watch_only) + { + bool stub; + load_keys2ki(true, stub); + } store(); } //---------------------------------------------------------------------------------------------------- @@ -1930,25 +1963,22 @@ void wallet2::load(const std::wstring& wallet_, const std::string& password) //---------------------------------------------------------------------------------------------------- void wallet2::store() { - store(m_wallet_file); + store(m_wallet_file, m_password, false); } //---------------------------------------------------------------------------------------------------- -void wallet2::store(const std::wstring& path_to_save) +void wallet2::store(const std::wstring& path_to_save, const std::string& password, bool store_as_watch_only) { LOG_PRINT_L0("(before storing: pending_key_images: " << m_pending_key_images.size() << ", pki file elements: " << m_pending_key_images_file_container.size() << ", tx_keys: " << m_tx_keys.size() << ")"); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(!(store_as_watch_only && path_to_save == m_wallet_file), "ttrying to save watch-only wallet to the same wallet file!"); + //prepare data std::string keys_buff; - bool r = store_keys(keys_buff, m_password); + bool r = store_keys(keys_buff, password); CHECK_AND_ASSERT_THROW_MES(r, "failed to store_keys for wallet " << epee::string_encoding::convert_to_ansii(m_wallet_file)); wallet_file_binary_header wbh = AUTO_VAL_INIT(wbh); -// std::stringstream ss; -// r = tools::portble_serialize_obj_to_stream(*this, ss); -// CHECK_AND_ASSERT_THROW_MES(r, "failed to portble_serialize_obj_to_stream for wallet " << epee::string_encoding::convert_to_ansii(m_wallet_file)); -// std::string body_buff = ss.str(); - //store data wbh.m_signature = WALLET_FILE_SIGNATURE; @@ -1965,8 +1995,18 @@ void wallet2::store(const std::wstring& path_to_save) CHECK_AND_ASSERT_THROW_MES(!data_file.fail(), "failed to open binary wallet file for saving: " << epee::string_encoding::convert_to_ansii(m_wallet_file)); data_file << header_buff << keys_buff; WLT_LOG_L0("Storing to file..."); - r = tools::portble_serialize_obj_to_stream(*this, data_file); - CHECK_AND_ASSERT_THROW_MES(r, "failed to portble_serialize_obj_to_stream for wallet " << epee::string_encoding::convert_to_ansii(m_wallet_file)); + + if (store_as_watch_only) + { + // TODO + r = tools::portble_serialize_obj_to_stream(*this, data_file); + CHECK_AND_ASSERT_THROW_MES(r, "failed to portble_serialize_obj_to_stream for wallet " << epee::string_encoding::convert_to_ansii(m_wallet_file)); + } + else + { + r = tools::portble_serialize_obj_to_stream(*this, data_file); + CHECK_AND_ASSERT_THROW_MES(r, "failed to portble_serialize_obj_to_stream for wallet " << epee::string_encoding::convert_to_ansii(m_wallet_file)); + } data_file.flush(); data_file.close(); @@ -2173,8 +2213,6 @@ void wallet2::submit_transfer(const std::string& signed_tx_blob, currency::trans crypto::hash tx_hash = get_transaction_hash(ft.tx); - // foolproof check to make sure create_tx_param and create_tx_result DO match each other - try { send_transaction_to_network(ft.tx); @@ -2182,12 +2220,12 @@ void wallet2::submit_transfer(const std::string& signed_tx_blob, currency::trans catch (...) { // clear spent transfers if smth went wrong - clear_transfers_from_flag(ft.ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, "broadcasting tx " + epee::string_tools::pod_to_hex(get_transaction_hash(ft.tx)) + " was unsuccessful"); + clear_transfers_from_flag(ft.ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, "broadcasting tx " + epee::string_tools::pod_to_hex(tx_hash) + " was unsuccessful"); throw; } add_sent_tx_detailed_info(ft.tx, ft.ftp.prepared_destinations, ft.ftp.selected_transfers); - m_tx_keys.insert(std::make_pair(get_transaction_hash(ft.tx), ft.one_time_key)); + m_tx_keys.insert(std::make_pair(tx_hash, ft.one_time_key)); if (m_watch_only) { @@ -2237,9 +2275,9 @@ void wallet2::submit_transfer(const std::string& signed_tx_blob, currency::trans { THROW_IF_FALSE_WALLET_INT_ERR_EX(p.first < m_transfers.size(), "incorrect transfer index: " << p.first); auto& tr = m_transfers[p.first]; - if (tr.m_key_image != currency::null_ki) + if (tr.m_key_image != currency::null_ki && tr.m_key_image != p.second) { - LOG_PRINT_YELLOW("transfer #" << p.first << " has not null key image: " << tr.m_key_image << " will be replaced with ki " << p.second, LOG_LEVEL_0); + LOG_PRINT_YELLOW("transfer #" << p.first << " already has not null key image " << tr.m_key_image << " and it will be replaced with ki " << p.second, LOG_LEVEL_0); } tr.m_key_image = p.second; m_key_images[p.second] = p.first; @@ -2247,8 +2285,8 @@ void wallet2::submit_transfer(const std::string& signed_tx_blob, currency::trans } } - // print inputs' key images - // print tx was sent + // TODO: print inputs' key images + print_tx_sent_message(ft.tx, "(from submit_transfer)"); } //---------------------------------------------------------------------------------------------------- void wallet2::submit_transfer_files(const std::string& signed_tx_file, currency::transaction& tx) @@ -3332,6 +3370,7 @@ void wallet2::exception_handler() //---------------------------------------------------------------------------------------------------- void wallet2::mark_transfers_as_spent(const std::vector& selected_transfers, const std::string& reason /* = empty_string */) { + // TODO: design a safe undo for this operation mark_transfers_with_flag(selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, reason); } //---------------------------------------------------------------------------------------------------- @@ -3667,13 +3706,17 @@ void wallet2::set_genesis(const crypto::hash& genesis_hash) m_blockchain[0] = genesis_hash; } //---------------------------------------------------------------------------------------------------- -void wallet2::print_tx_sent_message(const currency::transaction& tx, const std::string& description, uint64_t fee) +void wallet2::print_tx_sent_message(const currency::transaction& tx, const std::string& description, uint64_t fee /* = UINT64_MAX */) { //uint64_t balance_unlocked = 0; //uint64_t balance_total = balance(balance_unlocked); + std::stringstream ss; + if (fee != UINT64_MAX) + ss << "Commission: " << std::setw(21) << std::right << print_money(fee) << ENDL; + WLT_LOG_CYAN("Transaction " << get_transaction_hash(tx) << " was successfully sent " << description << ENDL - << "Commission: " << std::setw(21) << std::right << print_money(fee) << ENDL + << ss.str() // << "Balance: " << std::setw(21) << print_money(balance_total) << ENDL // << "Unlocked: " << std::setw(21) << print_money(balance_unlocked) << ENDL << "Please, wait for confirmation for your balance to be unlocked.", @@ -3746,28 +3789,6 @@ void wallet2::prepare_tx_destinations(uint64_t needed_money, } } //---------------------------------------------------------------------------------------------------- -/*void wallet2::prepare_transaction(const std::vector& dsts, - size_t fake_outputs_count, - uint64_t unlock_time, - uint64_t fee, - const std::vector& extra, - const std::vector& attachments, - tools::detail::split_strategy_id_t destination_split_strategy_id, - const tx_dust_policy& dust_policy, - const currency::account_public_address& crypt_address, - currency::transaction &tx, - uint8_t tx_outs_attr, - bool shuffle, - bool mark_tx_as_complete, - uint8_t flags, - std::vector& selected_transfers, - currency::keypair& one_time_key, - std::vector& prepared_destinations, - crypto::hash multisig_id) -{ - // TODO -}*/ -//---------------------------------------------------------------------------------------------------- void wallet2::prepare_transaction(const construct_tx_param& ctp, finalize_tx_param& ftp, const currency::transaction& tx_for_mode_separate /* = currency::transaction() */) { TIME_MEASURE_START_MS(get_needed_money_time); @@ -3905,12 +3926,10 @@ void wallet2::transfer(const std::vector& dsts, uint8_t tx_outs_attr, bool shuffle, uint8_t flags, - bool send_to_network) + bool send_to_network, + std::string* p_signed_tx_blob_str) { TIME_MEASURE_START(precalculation_time); - //using namespace currency; - currency::keypair onetime_keys = AUTO_VAL_INIT(onetime_keys); - construct_tx_param tx_param = AUTO_VAL_INIT(tx_param); tx_param.attachments = attachments; tx_param.crypt_address = currency::get_crypt_address_from_destinations(m_account.get_keys(), dsts); @@ -3933,7 +3952,6 @@ void wallet2::transfer(const std::vector& dsts, prepare_transaction(tx_param, ftp); TIME_MEASURE_FINISH(prepare_transaction_time); - if (m_watch_only) { tx_param.spend_pub_key = m_account.get_public_address().m_spend_public_key; @@ -3941,7 +3959,9 @@ void wallet2::transfer(const std::vector& dsts, crypto::chacha_crypt(bl, m_account.get_keys().m_view_secret_key); epee::file_io_utils::save_string_to_file("unsigned_zano_tx", bl); LOG_PRINT_L0("Transaction stored to unsigned_zano_tx. You need to sign this tx using a full-access wallet."); - //relay_blob = bl; // TODO: save encrypted tx param blob to relay_blob + + if (p_signed_tx_blob_str != nullptr) + *p_signed_tx_blob_str = bl; // unlock transfers at the very end TIME_MEASURE_START(mark_transfers_as_spent_time); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index d9ae6836..d33a393e 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -297,6 +297,8 @@ namespace tools std::vector selected_transfers; std::vector prepared_destinations; + crypto::public_key spend_pub_key; // only for validations + BEGIN_SERIALIZE_OBJECT() FIELD(unlock_time) FIELD(extra) @@ -309,6 +311,7 @@ namespace tools FIELD(sources) FIELD(selected_transfers) FIELD(prepared_destinations) + FIELD(spend_pub_key) END_SERIALIZE() }; @@ -468,7 +471,8 @@ namespace tools void restore(const std::wstring& path, const std::string& pass, const std::string& restore_key); void load(const std::wstring& wallet, const std::string& password); void store(); - void store(const std::wstring& path); + void store(const std::wstring& path, const std::string& password, bool store_as_watch_only = false); + bool store_keys(std::string& buff, const std::string& password, bool store_as_watch_only = false); std::wstring get_wallet_path(){ return m_wallet_file; } currency::account_base& get_account() { return m_account; } const currency::account_base& get_account() const { return m_account; } @@ -534,7 +538,8 @@ namespace tools uint8_t tx_outs_attr = CURRENCY_TO_KEY_OUT_RELAXED, bool shuffle = true, uint8_t flags = 0, - bool send_to_network = true); + bool send_to_network = true, + std::string* p_signed_tx_blob_str = nullptr); void transfer(const std::vector& dsts, size_t fake_outputs_count, @@ -698,25 +703,6 @@ namespace tools const std::list& get_expiration_entries() const { return m_money_expirations; }; bool get_tx_key(const crypto::hash &txid, crypto::secret_key &tx_key) const; - /*void prepare_transaction(const std::vector& dsts, - size_t fake_outputs_count, - uint64_t unlock_time, - uint64_t fee, - const std::vector& extra, - const std::vector& attachments, - detail::split_strategy_id_t destination_split_strategy_id, - const tx_dust_policy& dust_policy, - const currency::account_public_address& crypt_address, - OUT currency::transaction &tx, - uint8_t tx_outs_attr, - bool shuffle, - bool mark_tx_as_complete, - uint8_t flags, - OUT std::vector& selected_transfers, - OUT currency::keypair& one_time_key, - OUT std::vector& prepared_destinations, - crypto::hash multisig_id = currency::null_hash);*/ - void prepare_transaction(const construct_tx_param& ctp, finalize_tx_param& ftp, const currency::transaction& tx_for_mode_separate = currency::transaction()); void finalize_transaction(const finalize_tx_param& ftp, currency::transaction& tx, crypto::secret_key& tx_key, bool broadcast_tx); @@ -725,7 +711,6 @@ namespace tools private: void add_transfers_to_expiration_list(const std::vector& selected_transfers, uint64_t expiration, uint64_t change_amount, const crypto::hash& related_tx_id); void remove_transfer_from_expiration_list(uint64_t transfer_index); - bool store_keys(std::string& buff, const std::string& password); void load_keys(const std::string& keys_file_name, const std::string& password); void process_new_transaction(const currency::transaction& tx, uint64_t height, const currency::block& b); void detach_blockchain(uint64_t height); @@ -797,7 +782,7 @@ private: uint64_t get_tx_expiration_median() const; - void print_tx_sent_message(const currency::transaction& tx, const std::string& description, uint64_t fee); + void print_tx_sent_message(const currency::transaction& tx, const std::string& description, uint64_t fee = UINT64_MAX); // Validates escrow template tx in assumption it's related to wallet's account (wallet's account is either A or B party in escrow process) bool validate_escrow_proposal(const wallet_rpc::wallet_transfer_info& wti, const bc_services::proposal_body& prop, diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index 0903f5b5..2b7a94a4 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -676,6 +676,7 @@ if (cond) tools::error::throw_wallet_ex(std::string(__FILE__ ":" STRINGIZE(__LINE__)), ss.str()); \ } +#define THROW_IF_FALSE_WALLET_INT_ERR_EX_NO_HANDLER(cond, mess) THROW_IF_TRUE_WALLET_INT_ERR_EX_NO_HANDLER((!(cond)), mess) #define THROW_IF_FALSE_WALLET_INT_ERR_EX(cond, mess) THROW_IF_TRUE_WALLET_INT_ERR_EX((!(cond)), mess) diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 6c9f49e0..7dfc3f64 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -158,8 +158,17 @@ namespace tools currency::transaction tx; std::vector extra; - m_wallet.transfer(dsts, req.mixin, req.unlock_time, req.fee, extra, attachments, tx); - res.tx_hash = epee::string_tools::pod_to_hex(currency::get_transaction_hash(tx)); + std::string signed_tx_blob_str; + m_wallet.transfer(dsts, req.mixin, req.unlock_time, req.fee, extra, attachments, detail::ssi_digit, tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx, CURRENCY_TO_KEY_OUT_RELAXED, true, 0, true, &signed_tx_blob_str); + if (m_wallet.is_watch_only()) + { + res.tx_unsigned_hex = epee::string_tools::buff_to_hex_nodelimer(signed_tx_blob_str); // watch-only wallets can't sign and relay transactions + // leave res.tx_hash empty, because tx has will change after signing + } + else + { + res.tx_hash = epee::string_tools::pod_to_hex(currency::get_transaction_hash(tx)); + } return true; } catch (const tools::error::daemon_busy& e) @@ -337,14 +346,6 @@ namespace tools //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::on_submit_transfer(const wallet_rpc::COMMAND_SUBMIT_TRANSFER::request& req, wallet_rpc::COMMAND_SUBMIT_TRANSFER::response& res, epee::json_rpc::error& er, connection_context& cntx) { - //std::string tx_unsigned_blob; - //if (!string_tools::parse_hexstr_to_binbuff(req.tx_unsigned_hex, tx_unsigned_blob)) - //{ - // er.code = WALLET_RPC_ERROR_CODE_WRONG_ARGUMENT; - // er.message = "tx_unsigned_hex is invalid"; - // return false; - //} - std::string tx_signed_blob; if (!string_tools::parse_hexstr_to_binbuff(req.tx_signed_hex, tx_signed_blob)) { @@ -356,7 +357,7 @@ namespace tools try { currency::transaction tx = AUTO_VAL_INIT(tx); - // TODO m_wallet.submit_transfer(tx_unsigned_blob, tx_signed_blob, tx); + m_wallet.submit_transfer(tx_signed_blob, tx); res.tx_hash = epee::string_tools::pod_to_hex(currency::get_transaction_hash(tx)); } catch (const std::exception& e) @@ -371,290 +372,9 @@ namespace tools er.message = "WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR"; return false; } - return true; - } - //------------------------------------------------------------------------------------------------------------------------------ - - - - - - - - - - - //------------------------------------------------------------------------------------------------------------------------------ - bool wallet_rpc_server::on_maketelepod(const wallet_rpc::COMMAND_RPC_MAKETELEPOD::request& req, wallet_rpc::COMMAND_RPC_MAKETELEPOD::response& res, epee::json_rpc::error& er, connection_context& cntx) - { - //check available balance - if (m_wallet.unlocked_balance() <= req.amount) - { - res.status = "INSUFFICIENT_COINS"; - return true; - } - - currency::account_base acc; - acc.generate(); - std::vector dsts(1); - dsts.back().amount = req.amount; - dsts.back().addr.resize(1); - dsts.back().addr.back() = acc.get_keys().m_account_address; - currency::transaction tx = AUTO_VAL_INIT(tx); - try - { - std::vector extra; - std::vector attachments; - - m_wallet.transfer(dsts, 0, 0, m_wallet.get_core_runtime_config().tx_default_fee, extra, attachments, tx); - } - catch (const std::runtime_error& er) - { - LOG_ERROR("Failed to send transaction: " << er.what()); - res.status = "INTERNAL_ERROR"; - return true; - } - - res.tpd.basement_tx_id_hex = string_tools::pod_to_hex(currency::get_transaction_hash(tx)); - std::string buff = epee::serialization::store_t_to_binary(acc); - res.tpd.account_keys_hex = string_tools::buff_to_hex_nodelimer(buff); - - res.status = "OK"; - LOG_PRINT_GREEN("TELEPOD ISSUED [" << currency::print_money(req.amount) << "BBR, base_tx_id: ]" << currency::get_transaction_hash(tx), LOG_LEVEL_0); return true; } //------------------------------------------------------------------------------------------------------------------------------ - bool wallet_rpc_server::build_transaction_from_telepod(const wallet_rpc::telepod& tlp, const currency::account_public_address& acc2, currency::transaction& tx2, std::string& status) - { - //check if base transaction confirmed - currency::COMMAND_RPC_GET_TRANSACTIONS::request get_tx_req = AUTO_VAL_INIT(get_tx_req); - currency::COMMAND_RPC_GET_TRANSACTIONS::response get_tx_rsp = AUTO_VAL_INIT(get_tx_rsp); - get_tx_req.txs_hashes.push_back(tlp.basement_tx_id_hex); - if (!m_wallet.get_core_proxy()->call_COMMAND_RPC_GET_TRANSACTIONS(get_tx_req, get_tx_rsp) - || get_tx_rsp.status != CORE_RPC_STATUS_OK - || !get_tx_rsp.txs_as_hex.size()) - { - status = "UNCONFIRMED"; - return false; - } - - //extract account keys - std::string acc_buff; - currency::account_base acc = AUTO_VAL_INIT(acc); - if (!string_tools::parse_hexstr_to_binbuff(tlp.account_keys_hex, acc_buff)) - { - LOG_ERROR("Failed to parse_hexstr_to_binbuff(tlp.account_keys_hex, acc_buff)"); - status = "BAD"; - return false; - } - if (!epee::serialization::load_t_from_binary(acc, acc_buff)) - { - LOG_ERROR("Failed to load_t_from_binary(acc, acc_buff)"); - status = "BAD"; - return false; - } - - //extract transaction - currency::transaction tx = AUTO_VAL_INIT(tx); - std::string buff; - if (!string_tools::parse_hexstr_to_binbuff(get_tx_rsp.txs_as_hex.back(), buff)) - { - LOG_ERROR("Failed to parse_hexstr_to_binbuff(get_tx_rsp.txs_as_hex.back(), buff)"); - status = "INTERNAL_ERROR"; - return false; - } - if (!currency::parse_and_validate_tx_from_blob(buff, tx)) - { - LOG_ERROR("Failed to currency::parse_and_validate_tx_from_blob(buff, tx)"); - status = "INTERNAL_ERROR"; - return false; - } - - crypto::public_key tx_pub_key = currency::get_tx_pub_key_from_extra(tx); - if (tx_pub_key == currency::null_pkey) - { - LOG_ERROR("Failed to currency::get_tx_pub_key_from_extra(tx)"); - status = "BAD"; - return false; - - } - - //get transaction global output indices - currency::COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::request get_ind_req = AUTO_VAL_INIT(get_ind_req); - currency::COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::response get_ind_rsp = AUTO_VAL_INIT(get_ind_rsp); - get_ind_req.txid = currency::get_transaction_hash(tx); - if (!m_wallet.get_core_proxy()->call_COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES(get_ind_req, get_ind_rsp) - || get_ind_rsp.status != CORE_RPC_STATUS_OK - || get_ind_rsp.o_indexes.size() != tx.vout.size()) - { - LOG_ERROR("Problem with call_COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES(....) "); - status = "INTERNAL_ERROR"; - return false; - } - - //prepare inputs - std::vector sources; - size_t i = 0; - uint64_t amount = 0; - for (auto& o : get_ind_rsp.o_indexes) - { - //check if input is for telepod's address - if (currency::is_out_to_acc(acc.get_keys(), boost::get(tx.vout[i].target), tx_pub_key, i)) - { - //income output - amount += tx.vout[i].amount; - sources.resize(sources.size() + 1); - currency::tx_source_entry& tse = sources.back(); - tse.amount = tx.vout[i].amount; - tse.outputs.push_back(currency::tx_source_entry::output_entry(o, boost::get(tx.vout[i].target).key)); - tse.real_out_tx_key = tx_pub_key; - tse.real_output = 0; - tse.real_output_in_tx_index = i; - } - ++i; - } - - - //prepare outputs - std::vector dsts(1); - currency::tx_destination_entry& dst = dsts.back(); - dst.addr.push_back(acc2); - dst.amount = amount - m_wallet.get_core_runtime_config().tx_default_fee; - - //generate transaction - const std::vector extra; - const std::vector attachments; - crypto::secret_key sk; - bool r = currency::construct_tx(acc.get_keys(), sources, dsts, extra, attachments, tx2, sk, 0); - if (!r) - { - LOG_ERROR("Problem with construct_tx(....) "); - status = "INTERNAL_ERROR"; - return false; - } - if (CURRENCY_MAX_TRANSACTION_BLOB_SIZE <= get_object_blobsize(tx2)) - { - LOG_ERROR("Problem with construct_tx(....), blobl size os too big: " << get_object_blobsize(tx2)); - status = "INTERNAL_ERROR"; - return false; - } - - return true; - } - //------------------------------------------------------------------------------------------------------------------------------ - bool wallet_rpc_server::on_clonetelepod(const wallet_rpc::COMMAND_RPC_CLONETELEPOD::request& req, wallet_rpc::COMMAND_RPC_CLONETELEPOD::response& res, epee::json_rpc::error& er, connection_context& cntx) - { - currency::transaction tx2 = AUTO_VAL_INIT(tx2); - //new destination account - currency::account_base acc2 = AUTO_VAL_INIT(acc2); - acc2.generate(); - - if (!build_transaction_from_telepod(req.tpd, acc2.get_keys().m_account_address, tx2, res.status)) - { - LOG_ERROR("Failed to build_transaction_from_telepod(...)"); - return true; - } - - //send transaction to daemon - currency::COMMAND_RPC_SEND_RAW_TX::request req_send_raw; - req_send_raw.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(tx2)); - currency::COMMAND_RPC_SEND_RAW_TX::response rsp_send_raw; - bool r = m_wallet.get_core_proxy()->call_COMMAND_RPC_SEND_RAW_TX(req_send_raw, rsp_send_raw); - if (!r || rsp_send_raw.status != CORE_RPC_STATUS_OK) - { - LOG_ERROR("Problem with construct_tx(....), blobl size os too big: " << get_object_blobsize(tx2)); - res.status = "INTERNAL_ERROR"; - return true; - } - - res.tpd.basement_tx_id_hex = string_tools::pod_to_hex(currency::get_transaction_hash(tx2)); - std::string acc2_buff = epee::serialization::store_t_to_binary(acc2); - res.tpd.account_keys_hex = string_tools::buff_to_hex_nodelimer(acc2_buff); - - res.status = "OK"; - LOG_PRINT_GREEN("TELEPOD ISSUED [" << currency::print_money(currency::get_outs_money_amount(tx2)) << "BBR, base_tx_id: ]" << currency::get_transaction_hash(tx2), LOG_LEVEL_0); - - return true; - } - //------------------------------------------------------------------------------------------------------------------------------ - bool wallet_rpc_server::on_telepodstatus(const wallet_rpc::COMMAND_RPC_TELEPODSTATUS::request& req, wallet_rpc::COMMAND_RPC_TELEPODSTATUS::response& res, epee::json_rpc::error& er, connection_context& cntx) - { - currency::transaction tx2 = AUTO_VAL_INIT(tx2); - //new destination account - currency::account_base acc2 = AUTO_VAL_INIT(acc2); - acc2.generate(); - - if (!build_transaction_from_telepod(req.tpd, acc2.get_keys().m_account_address, tx2, res.status)) - { - return true; - } - //check if transaction is spent - currency::COMMAND_RPC_CHECK_KEYIMAGES::request req_ki = AUTO_VAL_INIT(req_ki); - currency::COMMAND_RPC_CHECK_KEYIMAGES::response rsp_ki = AUTO_VAL_INIT(rsp_ki); - for (auto& i : tx2.vin) - req_ki.images.push_back(boost::get(i).k_image); - - if (!m_wallet.get_core_proxy()->call_COMMAND_RPC_COMMAND_RPC_CHECK_KEYIMAGES(req_ki, rsp_ki) - || rsp_ki.status != CORE_RPC_STATUS_OK - || rsp_ki.images_stat.size() != req_ki.images.size()) - { - LOG_ERROR("Problem with call_COMMAND_RPC_COMMAND_RPC_CHECK_KEYIMAGES(....)"); - res.status = "INTERNAL_ERROR"; - return true; - } - - for (auto s : rsp_ki.images_stat) - { - if (!s) - { - res.status = "SPENT"; - return true; - } - } - - res.status = "OK"; - return true; - } - //------------------------------------------------------------------------------------------------------------------------------ - bool wallet_rpc_server::on_withdrawtelepod(const wallet_rpc::COMMAND_RPC_WITHDRAWTELEPOD::request& req, wallet_rpc::COMMAND_RPC_WITHDRAWTELEPOD::response& res, epee::json_rpc::error& er, connection_context& cntx) - { - currency::transaction tx2 = AUTO_VAL_INIT(tx2); - //parse destination add - currency::account_public_address acc_addr = AUTO_VAL_INIT(acc_addr); - if (!currency::get_account_address_from_str(acc_addr, req.addr)) - { - LOG_ERROR("Failed to build_transaction_from_telepod(...)"); - res.status = "BAD_ADDRESS"; - return true; - } - - - if (!build_transaction_from_telepod(req.tpd, acc_addr, tx2, res.status)) - { - LOG_ERROR("Failed to build_transaction_from_telepod(...)"); - return true; - } - - //send transaction to daemon - currency::COMMAND_RPC_SEND_RAW_TX::request req_send_raw; - req_send_raw.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(tx2)); - currency::COMMAND_RPC_SEND_RAW_TX::response rsp_send_raw; - bool r = m_wallet.get_core_proxy()->call_COMMAND_RPC_SEND_RAW_TX(req_send_raw, rsp_send_raw); - if (!r || rsp_send_raw.status != CORE_RPC_STATUS_OK) - { - LOG_ERROR("Problem with construct_tx(....), blobl size os too big: " << get_object_blobsize(tx2)); - res.status = "INTERNAL_ERROR"; - return true; - } - - res.status = "OK"; - LOG_PRINT_GREEN("TELEPOD WITHDRAWN [" << currency::print_money(currency::get_outs_money_amount(tx2)) << "BBR, tx_id: ]" << currency::get_transaction_hash(tx2), LOG_LEVEL_0); - - return true; - } - -} - - +} // namespace tools diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 036de6b9..d643129c 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -48,12 +48,6 @@ namespace tools MAP_JON_RPC_WE("split_integrated_address", on_split_integrated_address, wallet_rpc::COMMAND_RPC_SPLIT_INTEGRATED_ADDRESS) MAP_JON_RPC_WE("sign_transfer", on_sign_transfer, wallet_rpc::COMMAND_SIGN_TRANSFER) MAP_JON_RPC_WE("submit_transfer", on_submit_transfer, wallet_rpc::COMMAND_SUBMIT_TRANSFER) - - // supernet api - MAP_JON_RPC_WE("maketelepod", on_maketelepod, wallet_rpc::COMMAND_RPC_MAKETELEPOD) - MAP_JON_RPC_WE("clonetelepod", on_clonetelepod, wallet_rpc::COMMAND_RPC_CLONETELEPOD) - MAP_JON_RPC_WE("telepodstatus", on_telepodstatus, wallet_rpc::COMMAND_RPC_TELEPODSTATUS) - MAP_JON_RPC_WE("withdrawtelepod", on_withdrawtelepod, wallet_rpc::COMMAND_RPC_WITHDRAWTELEPOD) END_JSON_RPC_MAP() END_URI_MAP2() @@ -69,13 +63,7 @@ namespace tools bool on_sign_transfer(const wallet_rpc::COMMAND_SIGN_TRANSFER::request& req, wallet_rpc::COMMAND_SIGN_TRANSFER::response& res, epee::json_rpc::error& er, connection_context& cntx); bool on_submit_transfer(const wallet_rpc::COMMAND_SUBMIT_TRANSFER::request& req, wallet_rpc::COMMAND_SUBMIT_TRANSFER::response& res, epee::json_rpc::error& er, connection_context& cntx); - bool on_maketelepod(const wallet_rpc::COMMAND_RPC_MAKETELEPOD::request& req, wallet_rpc::COMMAND_RPC_MAKETELEPOD::response& res, epee::json_rpc::error& er, connection_context& cntx); - bool on_clonetelepod(const wallet_rpc::COMMAND_RPC_CLONETELEPOD::request& req, wallet_rpc::COMMAND_RPC_CLONETELEPOD::response& res, epee::json_rpc::error& er, connection_context& cntx); - bool on_telepodstatus(const wallet_rpc::COMMAND_RPC_TELEPODSTATUS::request& req, wallet_rpc::COMMAND_RPC_TELEPODSTATUS::response& res, epee::json_rpc::error& er, connection_context& cntx); - bool on_withdrawtelepod(const wallet_rpc::COMMAND_RPC_WITHDRAWTELEPOD::request& req, wallet_rpc::COMMAND_RPC_WITHDRAWTELEPOD::response& res, epee::json_rpc::error& er, connection_context& cntx); - bool handle_command_line(const boost::program_options::variables_map& vm); - bool build_transaction_from_telepod(const wallet_rpc::telepod& tlp, const currency::account_public_address& acc2, currency::transaction& tx2, std::string& status); private: wallet2& m_wallet; @@ -83,4 +71,5 @@ namespace tools std::string m_bind_ip; bool m_do_mint; }; -} + +} // namespace tools diff --git a/src/wallet/wallet_rpc_server_commans_defs.h b/src/wallet/wallet_rpc_server_commans_defs.h index ac025b44..2c4ef2ba 100644 --- a/src/wallet/wallet_rpc_server_commans_defs.h +++ b/src/wallet/wallet_rpc_server_commans_defs.h @@ -231,9 +231,11 @@ namespace wallet_rpc struct response { std::string tx_hash; + std::string tx_unsigned_hex; // for cold-signing process BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(tx_hash) + KV_SERIALIZE(tx_unsigned_hex) END_KV_SERIALIZE_MAP() }; };