diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 580a918d..991cebcd 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -4312,6 +4312,39 @@ bool wallet2::get_utxo_distribution(std::map& distribution) return false; } //---------------------------------------------------------------------------------------------------- +void wallet2::submit_externally_signed_asset_tx(const finalized_tx& ft, const crypto::eth_signature& eth_sig, bool unlock_transfers_on_fail, currency::transaction& result_tx, bool& transfers_unlocked) +{ + transaction tx = ft.tx; + + currency::asset_operation_ownership_proof_eth aoop_eth{}; + aoop_eth.eth_sig = eth_sig; + tx.proofs.push_back(std::move(aoop_eth)); + + // foolproof + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(ft.ftp.spend_pub_key == m_account.get_keys().account_address.spend_public_key, "The given tx was created in a different wallet, keys missmatch, tx hash: " << ft.tx_id); + + try + { + send_transaction_to_network(tx); + } + catch (...) + { + // clear transfers flags if smth went wrong and it was requested + if (unlock_transfers_on_fail) + { + uint32_t flag = WALLET_TRANSFER_DETAIL_FLAG_SPENT | WALLET_TRANSFER_DETAIL_FLAG_ASSET_OP_RESERVATION; + clear_transfers_from_flag(ft.ftp.selected_transfers, flag, "broadcasting tx " + epee::string_tools::pod_to_hex(ft.tx_id) + " was unsuccessful"); + transfers_unlocked = true; + } + throw; + } + + m_tx_keys.insert(std::make_pair(ft.tx_id, ft.one_time_key)); + add_sent_tx_detailed_info(tx, ft.ftp.attachments, ft.ftp.prepared_destinations, ft.ftp.selected_transfers); + + print_tx_sent_message(tx, "from submit_externally_signed_asset_tx", true, get_tx_fee(tx)); +} +//---------------------------------------------------------------------------------------------------- void wallet2::submit_transfer(const std::string& signed_tx_blob, currency::transaction& tx) { // decrypt sources diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 30f3d84e..c454480a 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -595,6 +595,7 @@ namespace tools void sign_transfer_files(const std::string& tx_sources_file, const std::string& signed_tx_file, currency::transaction& tx); void submit_transfer(const std::string& signed_tx_blob, currency::transaction& tx); void submit_transfer_files(const std::string& signed_tx_file, currency::transaction& tx); + void submit_externally_signed_asset_tx(const currency::finalized_tx& ft, const crypto::eth_signature& eth_sig, bool unlock_transfers_on_fail, currency::transaction& result_tx, bool& transfers_unlocked); void sweep_below(size_t fake_outs_count, const currency::account_public_address& destination_addr, uint64_t threshold_amount, const currency::payment_id_t& payment_id, uint64_t fee, size_t& outs_total, uint64_t& amount_total, size_t& outs_swept, uint64_t& amount_swept, currency::transaction* p_result_tx = nullptr, std::string* p_filename_or_unsigned_tx_blob_str = nullptr); diff --git a/src/wallet/wallet_public_structs_defs.h b/src/wallet/wallet_public_structs_defs.h index 3c6eb174..af6548c5 100644 --- a/src/wallet/wallet_public_structs_defs.h +++ b/src/wallet/wallet_public_structs_defs.h @@ -13,7 +13,7 @@ #include "currency_core/offers_service_basics.h" #include "currency_core/bc_escrow_service.h" #include "rpc/core_rpc_server_commands_defs.h" - +#include "currency_protocol/blobdatatype.h" const uint64_t WALLET_GLOBAL_OUTPUT_INDEX_UNDEFINED = std::numeric_limits::max(); @@ -2083,5 +2083,39 @@ namespace wallet_public }; }; + struct COMMAND_ASSET_SEND_EXT_SIGNED_TX + { + DOC_COMMAND("Inserts externally made asset ownership signature into the given transaction and broadcasts it."); + + struct request + { + currency::blobdata finalized_tx; + currency::blobdata unsigned_tx; + crypto::eth_signature eth_sig; + crypto::hash expected_tx_id; + bool unlock_transfers_on_fail = false; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_BLOB_AS_BASE64_STRING(finalized_tx)DOC_DSCR("Base64-encoded finalized_tx data structure, which was received from emit_asset call.") DOC_EXMP("ewogICJ2ZXJzaW9uIjogMSwgC....iAgInZpbiI6IFsgewogICAgIC") DOC_END + KV_SERIALIZE_BLOB_AS_BASE64_STRING(unsigned_tx) DOC_DSCR("Base64-encoded unsigned transaction blob, which was received from emit_asset call.") DOC_EXMP("083737bcfd826a973f74bb56a52b4fa562e6579ccaadd2697463498a66de4f1760b2cd40f11c3a00a7a80000") DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(eth_sig) DOC_DSCR("HEX-encoded ETH signature (64 bytes)") DOC_EXMP("674bb56a5b4fa562e679ccacc4e69455e63f4a581257382191de6856c2156630b3fba0db4bdd73ffcfb36b6add697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(expected_tx_id) DOC_DSCR("The expected transaction id. Tx won't be sent if the calculated one doesn't match this one. Consider using 'verified_tx_id' returned by 'decrypt_tx_details' call.") DOC_EXMP("40fa6db923728b38962718c61b4dc3af1acaa1967479c73703e260dc3609c58d") DOC_END + KV_SERIALIZE(unlock_transfers_on_fail) DOC_DSCR("If true, all locked wallet transfers, corresponding to the transaction, will be unlocked on sending failure. False by default.") DOC_EXMP(false) DOC_END + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string status; + bool transfers_were_unlocked; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) DOC_DSCR("Status of the call") DOC_EXMP("OK") DOC_END + KV_SERIALIZE(transfers_were_unlocked) DOC_DSCR("If true, all input transfers that were locked when preparing this transaction, are now unlocked and may be spent. Can be true only upon sending failure and if requested.") DOC_EXMP(false) DOC_END + END_KV_SERIALIZE_MAP() + }; + }; + + } // namespace wallet_rpc } // namespace tools diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 39c75e27..e4d0b4ee 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2018 Zano Project +// Copyright (c) 2014-2024 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Copyright (c) 2012-2013 The Cryptonote developers // Distributed under the MIT/X11 software license, see the accompanying @@ -1334,6 +1334,46 @@ namespace tools currency::transaction result_tx; w.get_wallet()->burn_asset(req.asset_id, req.burn_amount, result_tx); res.result_tx = currency::get_transaction_hash(result_tx); + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_asset_send_ext_signed_tx(const wallet_public::COMMAND_ASSET_SEND_EXT_SIGNED_TX::request& req, wallet_public::COMMAND_ASSET_SEND_EXT_SIGNED_TX::response& res, epee::json_rpc::error& er, connection_context& cntx) + { + WALLET_RPC_BEGIN_TRY_ENTRY(); + + currency::finalized_tx ft{}; + if (!t_unserializable_object_from_blob(ft, req.finalized_tx)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_ARGUMENT; + er.message = "finalized_tx couldn't be deserialized"; + return false; + } + + if (t_serializable_object_to_blob(ft.tx) != req.unsigned_tx) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_ARGUMENT; + er.message = "unsigned_tx doesn't match finalized_tx"; + return false; + } + + crypto::hash tx_id = currency::get_transaction_hash(ft.tx); + if (req.expected_tx_id != tx_id) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_ARGUMENT; + er.message = std::string("expected_tx_id mismatch, real tx id is ") + epee::string_tools::pod_to_hex(tx_id); + return false; + } + + try + { + currency::transaction result_tx{}; + w.get_wallet()->submit_externally_signed_asset_tx(ft, req.eth_sig, req.unlock_transfers_on_fail, result_tx, res.transfers_were_unlocked); + } + catch(std::exception& e) + { + // doing this to be able to return 'transfers_were_unlocked' to the caller even in the case of exception + res.status = e.what(); + return true; + } + return true; WALLET_RPC_CATCH_TRY_ENTRY(); } diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 4eac9211..6016d8b5 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -151,6 +151,7 @@ namespace tools MAP_JON_RPC_WE("update_asset", on_assets_update, wallet_public::COMMAND_ASSETS_UPDATE) MAP_JON_RPC_WE("burn_asset", on_assets_burn, wallet_public::COMMAND_ASSETS_BURN) + MAP_JON_RPC_WE("send_ext_signed_asset_tx", on_asset_send_ext_signed_tx, wallet_public::COMMAND_ASSET_SEND_EXT_SIGNED_TX) //MULTIWALLET APIs MAP_JON_RPC_WE("mw_get_wallets", on_mw_get_wallets, wallet_public::COMMAND_MW_GET_WALLETS) @@ -222,6 +223,7 @@ namespace tools bool on_assets_emit(const wallet_public::COMMAND_ASSETS_EMIT::request& req, wallet_public::COMMAND_ASSETS_EMIT::response& res, epee::json_rpc::error& er, connection_context& cntx); bool on_assets_update(const wallet_public::COMMAND_ASSETS_UPDATE::request& req, wallet_public::COMMAND_ASSETS_UPDATE::response& res, epee::json_rpc::error& er, connection_context& cntx); bool on_assets_burn(const wallet_public::COMMAND_ASSETS_BURN::request& req, wallet_public::COMMAND_ASSETS_BURN::response& res, epee::json_rpc::error& er, connection_context& cntx); + bool on_asset_send_ext_signed_tx(const wallet_public::COMMAND_ASSET_SEND_EXT_SIGNED_TX::request& req, wallet_public::COMMAND_ASSET_SEND_EXT_SIGNED_TX::response& res, epee::json_rpc::error& er, connection_context& cntx); bool on_mw_get_wallets(const wallet_public::COMMAND_MW_GET_WALLETS::request& req, wallet_public::COMMAND_MW_GET_WALLETS::response& res, epee::json_rpc::error& er, connection_context& cntx); bool on_mw_select_wallet(const wallet_public::COMMAND_MW_SELECT_WALLET::request& req, wallet_public::COMMAND_MW_SELECT_WALLET::response& res, epee::json_rpc::error& er, connection_context& cntx);