From 4b9a457e642f344b96e4bb74b0a690bb9bbd707f Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sat, 31 Aug 2024 22:38:23 +0400 Subject: [PATCH 001/106] initial changes on wallet file size/memory consumption improvements --- src/currency_core/currency_config.h | 2 +- src/wallet/wallet2.cpp | 14938 +++++++++++++------------- src/wallet/wallet2.h | 44 +- src/wallet/wallet2_base.h | 2 +- 4 files changed, 7529 insertions(+), 7457 deletions(-) diff --git a/src/currency_core/currency_config.h b/src/currency_core/currency_config.h index d2bc4fa7..4c0ebd56 100644 --- a/src/currency_core/currency_config.h +++ b/src/currency_core/currency_config.h @@ -253,7 +253,7 @@ #define BC_OFFERS_CURRENCY_MARKET_FILENAME "market.bin" -#define WALLET_FILE_SERIALIZATION_VERSION 167 +#define WALLET_FILE_SERIALIZATION_VERSION 168 #define WALLET_FILE_LAST_SUPPORTED_VERSION 165 #define CURRENT_MEMPOOL_ARCHIVE_VER (CURRENCY_FORMATION_VERSION+31) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index da73a1f1..bf04aacc 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -38,7 +38,7 @@ using namespace epee; #endif #ifndef DISABLE_TOR - #include "common/tor_helper.h" +#include "common/tor_helper.h" #endif #include "storages/levin_abstract_invoke2.h" @@ -59,6 +59,8 @@ using namespace currency; #define WALLET_TX_MAX_ALLOWED_FEE (COIN * 100) #define WALLET_FETCH_RANDOM_OUTS_SIZE 200 +#define WALLET_CONCISE_MODE_MAX_REORG_BLOCKS CURRENCY_BLOCKS_PER_DAY * 7 //week +#define WALLET_CONCISE_MODE_MAX_HISTORY_SIZE 500 @@ -126,651 +128,673 @@ namespace tools } return max_unlock_time; } -//---------------------------------------------------------------------------------------------------- -std::string wallet2::transfer_flags_to_str(uint32_t flags) -{ - std::string result(5, ' '); - if (flags & WALLET_TRANSFER_DETAIL_FLAG_SPENT) - result[0] = 's'; - if (flags & WALLET_TRANSFER_DETAIL_FLAG_BLOCKED) - result[1] = 'b'; - if (flags & WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION) - result[2] = 'e'; - if (flags & WALLET_TRANSFER_DETAIL_FLAG_MINED_TRANSFER) - result[3] = 'm'; - if (flags & WALLET_TRANSFER_DETAIL_FLAG_COLD_SIG_RESERVATION) - result[4] = 'c'; - return result; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::init(const std::string& daemon_address) -{ - //m_miner_text_info = PROJECT_VERSION_LONG; - m_core_proxy->set_connection_addr(daemon_address); - bool connected = m_core_proxy->check_connection(); - if (!daemon_address.empty()) + //---------------------------------------------------------------------------------------------------- + std::string wallet2::transfer_flags_to_str(uint32_t flags) { - WLT_LOG_L0("daemon address: " << daemon_address); - WLT_LOG_L1((connected ? "" : "not ") << "connected to daemon"); + std::string result(5, ' '); + if (flags & WALLET_TRANSFER_DETAIL_FLAG_SPENT) + result[0] = 's'; + if (flags & WALLET_TRANSFER_DETAIL_FLAG_BLOCKED) + result[1] = 'b'; + if (flags & WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION) + result[2] = 'e'; + if (flags & WALLET_TRANSFER_DETAIL_FLAG_MINED_TRANSFER) + result[3] = 'm'; + if (flags & WALLET_TRANSFER_DETAIL_FLAG_COLD_SIG_RESERVATION) + result[4] = 'c'; + return result; } - - std::stringstream ss; - const tools::wallet_public::wallet_vote_config& votes = this->get_current_votes(); - if (votes.entries.size()) + //---------------------------------------------------------------------------------------------------- + void wallet2::init(const std::string& daemon_address) { - ss << "VOTING SET LOADED:"; - for (const auto& e : votes.entries) + //m_miner_text_info = PROJECT_VERSION_LONG; + m_core_proxy->set_connection_addr(daemon_address); + bool connected = m_core_proxy->check_connection(); + if (!daemon_address.empty()) { - ss << "\t\t" << e.proposal_id << "\t\t" << (e.vote ? "1" : "0") << "\t\t(" << e.h_start << " - " << e.h_end << ")"; + WLT_LOG_L0("daemon address: " << daemon_address); + WLT_LOG_L1((connected ? "" : "not ") << "connected to daemon"); } - WLT_LOG_L0(ss.str()); - } -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::set_core_proxy(const std::shared_ptr& proxy) -{ - THROW_IF_TRUE_WALLET_EX(!proxy, error::wallet_internal_error, "Trying to set null core proxy."); - m_core_proxy = proxy; - return true; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::set_defragmentation_tx_settings(bool enabled, uint64_t min_outs, uint64_t max_outs, uint64_t max_allowed_amount, size_t decoys_count) -{ - m_defragmentation_tx_enabled = enabled; - m_min_utxo_count_for_defragmentation_tx = min_outs; - m_max_utxo_count_for_defragmentation_tx = max_outs; - m_max_allowed_output_amount_for_defragmentation_tx = max_allowed_amount; - m_decoys_count_for_defragmentation_tx = decoys_count; - if (enabled) - { - WLT_LOG_L0("Defragmentation tx creation is enabled, settings: min outs: " << min_outs << ", max outs: " << max_outs << ", max amount: " << print_money_brief(max_allowed_amount) << - ", decoys: " << (decoys_count != SIZE_MAX ? epee::string_tools::num_to_string_fast(decoys_count) : std::string("default"))); - } - else - { - WLT_LOG_L0("Defragmentation tx creation is disabled"); - } -} -//---------------------------------------------------------------------------------------------------- -std::shared_ptr wallet2::get_core_proxy() -{ - return m_core_proxy; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::get_transfer_info_by_key_image(const crypto::key_image& ki, transfer_details& td, size_t& i) -{ - auto it = m_key_images.find(ki); - if (it == m_key_images.end()) - { - return false; - } - THROW_IF_FALSE_WALLET_EX(it->second < m_transfers.size(), error::wallet_internal_error, "wrong out in transaction: internal index"); - td = m_transfers[it->second]; - i = it->second; - return true; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::get_transfer_info_by_index(size_t i, transfer_details& td) -{ - WLT_CHECK_AND_ASSERT_MES(i < m_transfers.size(), false, "wrong out in transaction: internal index, m_transfers.size()=" << m_transfers.size()); - td = m_transfers[i]; - return true; -} -//---------------------------------------------------------------------------------------------------- -size_t wallet2::scan_for_collisions(std::unordered_map >& key_images) -{ - size_t count = 0; - for (size_t i = 0; i != m_transfers.size(); i++) - { - key_images[m_transfers[i].m_key_image].push_back(i); - if (key_images[m_transfers[i].m_key_image].size() > 1) - count++; - } - return count; -} -//---------------------------------------------------------------------------------------------------- -size_t wallet2::fix_collisions() -{ - std::unordered_map > key_images; - scan_for_collisions(key_images); - size_t count = 0; - for (auto &coll_entry : key_images) - { - if(coll_entry.second.size()<2) - continue; - currency::COMMAND_RPC_CHECK_KEYIMAGES::request req_ki = AUTO_VAL_INIT(req_ki); - req_ki.images.push_back(coll_entry.first); - currency::COMMAND_RPC_CHECK_KEYIMAGES::response rsp_ki = AUTO_VAL_INIT(rsp_ki); - bool r = m_core_proxy->call_COMMAND_RPC_COMMAND_RPC_CHECK_KEYIMAGES(req_ki, rsp_ki); - THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "unable to get spent key image info for keyimage: " << coll_entry.first); - THROW_IF_FALSE_WALLET_INT_ERR_EX(rsp_ki.images_stat.size() == 1, "unable to get spent key image info for keyimage: " << coll_entry.first << "keyimages size()=" << rsp_ki.images_stat.size()); - THROW_IF_FALSE_WALLET_INT_ERR_EX(*rsp_ki.images_stat.begin() != 0, "unable to get spent key image info for keyimage: " << coll_entry.first << "keyimages [0]=0"); - - - for (auto it = coll_entry.second.begin(); it!= coll_entry.second.end(); it++) + std::stringstream ss; + const tools::wallet_public::wallet_vote_config& votes = this->get_current_votes(); + if (votes.entries.size()) { - m_transfers[*it].m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; - m_transfers[*it].m_spent_height = *rsp_ki.images_stat.begin(); - WLT_LOG_L0("Fixed collision for key image " << coll_entry.first << " transfer " << count); - count++; + ss << "VOTING SET LOADED:"; + for (const auto& e : votes.entries) + { + ss << "\t\t" << e.proposal_id << "\t\t" << (e.vote ? "1" : "0") << "\t\t(" << e.h_start << " - " << e.h_end << ")"; + } + WLT_LOG_L0(ss.str()); } } - - return count; -} -//---------------------------------------------------------------------------------------------------- -size_t wallet2::scan_for_transaction_entries(const crypto::hash& tx_id, const crypto::key_image& ki, std::list& details) -{ - bool check_ki = ki != currency::null_ki; - bool check_tx_id = tx_id != currency::null_hash; - - for (auto it = m_transfers.begin(); it != m_transfers.end(); it++) + //---------------------------------------------------------------------------------------------------- + bool wallet2::set_core_proxy(const std::shared_ptr& proxy) { - if (check_ki && it->m_key_image == ki) + THROW_IF_TRUE_WALLET_EX(!proxy, error::wallet_internal_error, "Trying to set null core proxy."); + m_core_proxy = proxy; + return true; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::set_defragmentation_tx_settings(bool enabled, uint64_t min_outs, uint64_t max_outs, uint64_t max_allowed_amount, size_t decoys_count) + { + m_defragmentation_tx_enabled = enabled; + m_min_utxo_count_for_defragmentation_tx = min_outs; + m_max_utxo_count_for_defragmentation_tx = max_outs; + m_max_allowed_output_amount_for_defragmentation_tx = max_allowed_amount; + m_decoys_count_for_defragmentation_tx = decoys_count; + if (enabled) { - details.push_back(*it); - } - if (check_tx_id && get_transaction_hash(it->m_ptx_wallet_info->m_tx) == tx_id) - { - details.push_back(*it); - } - } - return details.size(); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::fetch_tx_global_indixes(const currency::transaction& tx, std::vector& goutputs_indexes) -{ - std::list> txs; - txs.push_back(tx); - std::vector > res; - fetch_tx_global_indixes(txs, res); - THROW_IF_FALSE_WALLET_INT_ERR_EX(res.size() == 1, "fetch_tx_global_indixes for single entry returned wrong result: res.size()=" << res.size()); - goutputs_indexes = res[0]; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::fetch_tx_global_indixes(const std::list>& txs, std::vector>& goutputs_indexes) -{ - currency::COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::request req = AUTO_VAL_INIT(req); - currency::COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::response res = AUTO_VAL_INIT(res); - for (auto& tx : txs) - { - req.txids.push_back(get_transaction_hash(tx)); - } - - bool r = m_core_proxy->call_COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES(req, res); - THROW_IF_TRUE_WALLET_EX(!r, error::no_connection_to_daemon, "get_o_indexes.bin"); - THROW_IF_TRUE_WALLET_EX(res.status == API_RETURN_CODE_BUSY, error::daemon_busy, "get_o_indexes.bin"); - THROW_IF_TRUE_WALLET_EX(res.status != API_RETURN_CODE_OK, error::get_out_indices_error, res.status); - THROW_IF_FALSE_WALLET_INT_ERR_EX(res.tx_global_outs.size() == txs.size(), "res.tx_global_outs.size()(" << res.tx_global_outs.size() - << ") == txs.size()(" << txs.size() << ")"); - goutputs_indexes.resize(txs.size()); - auto it_resp = res.tx_global_outs.begin(); - auto it_txs = txs.begin(); - size_t i = 0; - for (; it_resp != res.tx_global_outs.end();) - { - THROW_IF_FALSE_WALLET_INT_ERR_EX(it_resp->v.size() == it_txs->get().vout.size(), - "transactions outputs size=" << it_txs->get().vout.size() << - " not match with COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES response[i] size=" << it_resp->v.size()); - - goutputs_indexes[i] = it_resp->v; - it_resp++; it_txs++; i++; - } -} - -bool out_is_to_key(const currency::tx_out_v& out_t) -{ - if (out_t.type() == typeid(currency::tx_out_bare)) - { - return boost::get(out_t).target.type() == typeid(currency::txout_to_key); - } - return false; -} - -bool out_is_multisig(const currency::tx_out_v& out_t) -{ - if (out_t.type() == typeid(currency::tx_out_bare)) - { - return boost::get(out_t).target.type() == typeid(currency::txout_multisig); - } - return false; -} - -bool out_is_to_htlc(const currency::tx_out_v& out_t) -{ - if (out_t.type() == typeid(currency::tx_out_bare)) - { - return boost::get(out_t).target.type() == typeid(currency::txout_htlc); - } - return false; -} - -bool out_is_zc(const currency::tx_out_v& out_t) -{ - return out_t.type() == typeid(currency::tx_out_zarcanum); -} - -const currency::txout_htlc& out_get_htlc(const currency::tx_out_v& out_t) -{ - return boost::get(boost::get(out_t).target); -} - -const crypto::public_key& wallet2::out_get_pub_key(const currency::tx_out_v& out_t, std::list& htlc_info_list) -{ - if (out_t.type() == typeid(tx_out_bare)) - { - const currency::tx_out_bare& out = boost::get(out_t); - if (out.target.type() == typeid(currency::txout_to_key)) - { - return boost::get(out.target).key; + WLT_LOG_L0("Defragmentation tx creation is enabled, settings: min outs: " << min_outs << ", max outs: " << max_outs << ", max amount: " << print_money_brief(max_allowed_amount) << + ", decoys: " << (decoys_count != SIZE_MAX ? epee::string_tools::num_to_string_fast(decoys_count) : std::string("default"))); } else { - THROW_IF_FALSE_WALLET_INT_ERR_EX(out.target.type() == typeid(currency::txout_htlc), "Unexpected out type in target wallet: " << out.target.type().name()); - THROW_IF_FALSE_WALLET_INT_ERR_EX(htlc_info_list.size() > 0, "Found txout_htlc out but htlc_info_list is empty"); - bool hltc_our_out_is_before_expiration = htlc_info_list.front().hltc_our_out_is_before_expiration; - htlc_info_list.pop_front(); - if (hltc_our_out_is_before_expiration) + WLT_LOG_L0("Defragmentation tx creation is disabled"); + } + } + //---------------------------------------------------------------------------------------------------- + std::shared_ptr wallet2::get_core_proxy() + { + return m_core_proxy; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::get_transfer_info_by_key_image(const crypto::key_image& ki, transfer_details& td, size_t& i) + { + auto it = m_key_images.find(ki); + if (it == m_key_images.end()) + { + return false; + } + //THROW_IF_FALSE_WALLET_EX(it->second < m_transfers.size(), error::wallet_internal_error, "wrong out in transaction: internal index"); + td = m_transfers.at(it->second); + i = it->second; + return true; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::get_transfer_info_by_index(size_t i, transfer_details& td) + { + //WLT_CHECK_AND_ASSERT_MES(i < m_transfers.size(), false, "wrong out in transaction: internal index, m_transfers.size()=" << m_transfers.size()); + td = m_transfers.at(i); + return true; + } + //---------------------------------------------------------------------------------------------------- + size_t wallet2::scan_for_collisions(std::unordered_map >& key_images) + { + size_t count = 0; + for (const auto& tr : m_transfers) + { + key_images[tr.second.m_key_image].push_back(tr.first); + if (key_images[tr.second.m_key_image].size() > 1) + count++; + } + return count; + } + //---------------------------------------------------------------------------------------------------- + size_t wallet2::fix_collisions() + { + std::unordered_map > key_images; + scan_for_collisions(key_images); + size_t count = 0; + for (auto& coll_entry : key_images) + { + if (coll_entry.second.size() < 2) + continue; + + currency::COMMAND_RPC_CHECK_KEYIMAGES::request req_ki = AUTO_VAL_INIT(req_ki); + req_ki.images.push_back(coll_entry.first); + currency::COMMAND_RPC_CHECK_KEYIMAGES::response rsp_ki = AUTO_VAL_INIT(rsp_ki); + bool r = m_core_proxy->call_COMMAND_RPC_COMMAND_RPC_CHECK_KEYIMAGES(req_ki, rsp_ki); + THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "unable to get spent key image info for keyimage: " << coll_entry.first); + THROW_IF_FALSE_WALLET_INT_ERR_EX(rsp_ki.images_stat.size() == 1, "unable to get spent key image info for keyimage: " << coll_entry.first << "keyimages size()=" << rsp_ki.images_stat.size()); + THROW_IF_FALSE_WALLET_INT_ERR_EX(*rsp_ki.images_stat.begin() != 0, "unable to get spent key image info for keyimage: " << coll_entry.first << "keyimages [0]=0"); + + + for (auto it = coll_entry.second.begin(); it != coll_entry.second.end(); it++) { - return boost::get(out.target).pkey_redeem; + m_transfers.at(*it).m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; + m_transfers.at(*it).m_spent_height = *rsp_ki.images_stat.begin(); + WLT_LOG_L0("Fixed collision for key image " << coll_entry.first << " transfer " << count); + count++; + } + } + + return count; + } + //---------------------------------------------------------------------------------------------------- + size_t wallet2::scan_for_transaction_entries(const crypto::hash& tx_id, const crypto::key_image& ki, std::list& details) + { + bool check_ki = ki != currency::null_ki; + bool check_tx_id = tx_id != currency::null_hash; + + for (auto it = m_transfers.begin(); it != m_transfers.end(); it++) + { + if (check_ki && it->second.m_key_image == ki) + { + details.push_back(it->second); + } + if (check_tx_id && get_transaction_hash(it->second.m_ptx_wallet_info->m_tx) == tx_id) + { + details.push_back(it->second); + } + } + return details.size(); + } + //---------------------------------------------------------------------------------------------------- + void wallet2::fetch_tx_global_indixes(const currency::transaction& tx, std::vector& goutputs_indexes) + { + std::list> txs; + txs.push_back(tx); + std::vector > res; + fetch_tx_global_indixes(txs, res); + THROW_IF_FALSE_WALLET_INT_ERR_EX(res.size() == 1, "fetch_tx_global_indixes for single entry returned wrong result: res.size()=" << res.size()); + goutputs_indexes = res[0]; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::fetch_tx_global_indixes(const std::list>& txs, std::vector>& goutputs_indexes) + { + currency::COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::request req = AUTO_VAL_INIT(req); + currency::COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::response res = AUTO_VAL_INIT(res); + for (auto& tx : txs) + { + req.txids.push_back(get_transaction_hash(tx)); + } + + bool r = m_core_proxy->call_COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES(req, res); + THROW_IF_TRUE_WALLET_EX(!r, error::no_connection_to_daemon, "get_o_indexes.bin"); + THROW_IF_TRUE_WALLET_EX(res.status == API_RETURN_CODE_BUSY, error::daemon_busy, "get_o_indexes.bin"); + THROW_IF_TRUE_WALLET_EX(res.status != API_RETURN_CODE_OK, error::get_out_indices_error, res.status); + THROW_IF_FALSE_WALLET_INT_ERR_EX(res.tx_global_outs.size() == txs.size(), "res.tx_global_outs.size()(" << res.tx_global_outs.size() + << ") == txs.size()(" << txs.size() << ")"); + goutputs_indexes.resize(txs.size()); + auto it_resp = res.tx_global_outs.begin(); + auto it_txs = txs.begin(); + size_t i = 0; + for (; it_resp != res.tx_global_outs.end();) + { + THROW_IF_FALSE_WALLET_INT_ERR_EX(it_resp->v.size() == it_txs->get().vout.size(), + "transactions outputs size=" << it_txs->get().vout.size() << + " not match with COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES response[i] size=" << it_resp->v.size()); + + goutputs_indexes[i] = it_resp->v; + it_resp++; it_txs++; i++; + } + } + + bool out_is_to_key(const currency::tx_out_v& out_t) + { + if (out_t.type() == typeid(currency::tx_out_bare)) + { + return boost::get(out_t).target.type() == typeid(currency::txout_to_key); + } + return false; + } + + bool out_is_multisig(const currency::tx_out_v& out_t) + { + if (out_t.type() == typeid(currency::tx_out_bare)) + { + return boost::get(out_t).target.type() == typeid(currency::txout_multisig); + } + return false; + } + + bool out_is_to_htlc(const currency::tx_out_v& out_t) + { + if (out_t.type() == typeid(currency::tx_out_bare)) + { + return boost::get(out_t).target.type() == typeid(currency::txout_htlc); + } + return false; + } + + bool out_is_zc(const currency::tx_out_v& out_t) + { + return out_t.type() == typeid(currency::tx_out_zarcanum); + } + + const currency::txout_htlc& out_get_htlc(const currency::tx_out_v& out_t) + { + return boost::get(boost::get(out_t).target); + } + + const crypto::public_key& wallet2::out_get_pub_key(const currency::tx_out_v& out_t, std::list& htlc_info_list) + { + if (out_t.type() == typeid(tx_out_bare)) + { + const currency::tx_out_bare& out = boost::get(out_t); + if (out.target.type() == typeid(currency::txout_to_key)) + { + return boost::get(out.target).key; } else { - return boost::get(out.target).pkey_refund; - } - } - } - else - { - THROW_IF_FALSE_WALLET_INT_ERR_EX(out_t.type() == typeid(currency::tx_out_zarcanum), "Unexpected out type im wallet: " << out_t.type().name()); - return boost::get(out_t).stealth_address; - } -} -//---------------------------------------------------------------------------------------------------- -void wallet2::process_ado_in_new_transaction(const currency::asset_descriptor_operation& ado, process_transaction_context& ptc) -{ - do - { - crypto::public_key asset_id{}; - if (ado.operation_type != ASSET_DESCRIPTOR_OPERATION_UNDEFINED) - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(get_or_calculate_asset_id(ado, nullptr, &asset_id), "get_or_calculate_asset_id failed"); - - if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER) - { - if (ado.descriptor.owner != m_account.get_public_address().spend_public_key) - break; - - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(m_own_asset_descriptors.count(asset_id) == 0, "asset with asset_id " << asset_id << " has already been registered in the wallet as own asset"); - wallet_own_asset_context& asset_context = m_own_asset_descriptors[asset_id]; - epee::misc_utils::cast_assign_a_to_b(asset_context, ado.descriptor); - //*static_cast(&asset_context) = ado.descriptor; - - std::stringstream ss; - ss << "New Asset Registered:" - << ENDL << "asset id: " << asset_id - << ENDL << "Name: " << asset_context.full_name - << ENDL << "Ticker: " << asset_context.ticker - << ENDL << "Total Max Supply: " << print_asset_money(asset_context.total_max_supply, asset_context.decimal_point) - << ENDL << "Current Supply: " << print_asset_money(asset_context.current_supply, asset_context.decimal_point) - << ENDL << "Decimal Point: " << (int)asset_context.decimal_point; - - - add_rollback_event(ptc.height, asset_register_event{ asset_id }); - WLT_LOG_MAGENTA(ss.str(), LOG_LEVEL_0); - if (m_wcallback) - m_wcallback->on_message(i_wallet2_callback::ms_yellow, ss.str()); - } - else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT || ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN) - { - auto it = m_own_asset_descriptors.find(asset_id); - if (it == m_own_asset_descriptors.end()) - break; - //asset had been updated - add_rollback_event(ptc.height, asset_update_event{ it->first, it->second }); - epee::misc_utils::cast_assign_a_to_b(it->second, ado.descriptor); - - } - else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE ) - { - auto it = m_own_asset_descriptors.find(asset_id); - if (it == m_own_asset_descriptors.end()) - { - if (ado.descriptor.owner == m_account.get_public_address().spend_public_key) + THROW_IF_FALSE_WALLET_INT_ERR_EX(out.target.type() == typeid(currency::txout_htlc), "Unexpected out type in target wallet: " << out.target.type().name()); + THROW_IF_FALSE_WALLET_INT_ERR_EX(htlc_info_list.size() > 0, "Found txout_htlc out but htlc_info_list is empty"); + bool hltc_our_out_is_before_expiration = htlc_info_list.front().hltc_our_out_is_before_expiration; + htlc_info_list.pop_front(); + if (hltc_our_out_is_before_expiration) { - // ownership of the asset acquired - - wallet_own_asset_context& asset_context = m_own_asset_descriptors[asset_id]; - epee::misc_utils::cast_assign_a_to_b(asset_context, ado.descriptor); - - std::stringstream ss; - ss << "Asset ownership acquired:" - << ENDL << "asset id: " << asset_id - << ENDL << "Name: " << ado.descriptor.full_name - << ENDL << "Ticker: " << ado.descriptor.ticker - << ENDL << "Total Max Supply: " << print_asset_money(ado.descriptor.total_max_supply, ado.descriptor.decimal_point) - << ENDL << "Current Supply: " << print_asset_money(ado.descriptor.current_supply, ado.descriptor.decimal_point) - << ENDL << "Decimal Point: " << (int)ado.descriptor.decimal_point; - - - add_rollback_event(ptc.height, asset_register_event{ asset_id }); - WLT_LOG_MAGENTA(ss.str(), LOG_LEVEL_0); - if (m_wcallback) - m_wcallback->on_message(i_wallet2_callback::ms_yellow, ss.str()); + return boost::get(out.target).pkey_redeem; } else { - // update event of the asset that we not control, skip + return boost::get(out.target).pkey_refund; + } + } + } + else + { + THROW_IF_FALSE_WALLET_INT_ERR_EX(out_t.type() == typeid(currency::tx_out_zarcanum), "Unexpected out type im wallet: " << out_t.type().name()); + return boost::get(out_t).stealth_address; + } + } + //---------------------------------------------------------------------------------------------------- + void wallet2::process_ado_in_new_transaction(const currency::asset_descriptor_operation& ado, process_transaction_context& ptc) + { + do + { + crypto::public_key asset_id{}; + if (ado.operation_type != ASSET_DESCRIPTOR_OPERATION_UNDEFINED) + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(get_or_calculate_asset_id(ado, nullptr, &asset_id), "get_or_calculate_asset_id failed"); + + if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER) + { + if (ado.descriptor.owner != m_account.get_public_address().spend_public_key) break; - } + + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(m_own_asset_descriptors.count(asset_id) == 0, "asset with asset_id " << asset_id << " has already been registered in the wallet as own asset"); + wallet_own_asset_context& asset_context = m_own_asset_descriptors[asset_id]; + epee::misc_utils::cast_assign_a_to_b(asset_context, ado.descriptor); + //*static_cast(&asset_context) = ado.descriptor; + + std::stringstream ss; + ss << "New Asset Registered:" + << ENDL << "asset id: " << asset_id + << ENDL << "Name: " << asset_context.full_name + << ENDL << "Ticker: " << asset_context.ticker + << ENDL << "Total Max Supply: " << print_asset_money(asset_context.total_max_supply, asset_context.decimal_point) + << ENDL << "Current Supply: " << print_asset_money(asset_context.current_supply, asset_context.decimal_point) + << ENDL << "Decimal Point: " << (int)asset_context.decimal_point; + + + add_rollback_event(ptc.height, asset_register_event{ asset_id }); + WLT_LOG_MAGENTA(ss.str(), LOG_LEVEL_0); + if (m_wcallback) + m_wcallback->on_message(i_wallet2_callback::ms_yellow, ss.str()); } - else + else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT || ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN) { - //update event for asset that we control, check if ownership is still ours - if (ado.descriptor.owner != m_account.get_public_address().spend_public_key && !it->second.thirdparty_custody) + auto it = m_own_asset_descriptors.find(asset_id); + if (it == m_own_asset_descriptors.end()) + break; + //asset had been updated + add_rollback_event(ptc.height, asset_update_event{ it->first, it->second }); + epee::misc_utils::cast_assign_a_to_b(it->second, ado.descriptor); + + } + else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE) + { + auto it = m_own_asset_descriptors.find(asset_id); + if (it == m_own_asset_descriptors.end()) { - //ownership of the asset had been transfered - add_rollback_event(ptc.height, asset_unown_event{ it->first, it->second }); - m_own_asset_descriptors.erase(it); + if (ado.descriptor.owner == m_account.get_public_address().spend_public_key) + { + // ownership of the asset acquired - std::stringstream ss; - ss << "Asset ownership lost:" - << ENDL << "asset id: " << asset_id - << ENDL << "New owner: " << ado.descriptor.owner - << ENDL << "Name: " << ado.descriptor.full_name - << ENDL << "Ticker: " << ado.descriptor.ticker - << ENDL << "Total Max Supply: " << print_asset_money(ado.descriptor.total_max_supply, ado.descriptor.decimal_point) - << ENDL << "Current Supply: " << print_asset_money(ado.descriptor.current_supply, ado.descriptor.decimal_point) - << ENDL << "Decimal Point: " << (int)ado.descriptor.decimal_point; + wallet_own_asset_context& asset_context = m_own_asset_descriptors[asset_id]; + epee::misc_utils::cast_assign_a_to_b(asset_context, ado.descriptor); - add_rollback_event(ptc.height, asset_register_event{ asset_id }); - WLT_LOG_MAGENTA(ss.str(), LOG_LEVEL_0); - if (m_wcallback) - m_wcallback->on_message(i_wallet2_callback::ms_yellow, ss.str()); + std::stringstream ss; + ss << "Asset ownership acquired:" + << ENDL << "asset id: " << asset_id + << ENDL << "Name: " << ado.descriptor.full_name + << ENDL << "Ticker: " << ado.descriptor.ticker + << ENDL << "Total Max Supply: " << print_asset_money(ado.descriptor.total_max_supply, ado.descriptor.decimal_point) + << ENDL << "Current Supply: " << print_asset_money(ado.descriptor.current_supply, ado.descriptor.decimal_point) + << ENDL << "Decimal Point: " << (int)ado.descriptor.decimal_point; + + + add_rollback_event(ptc.height, asset_register_event{ asset_id }); + WLT_LOG_MAGENTA(ss.str(), LOG_LEVEL_0); + if (m_wcallback) + m_wcallback->on_message(i_wallet2_callback::ms_yellow, ss.str()); + } + else + { + // update event of the asset that we not control, skip + break; + } } else { - //just an update of the asset - add_rollback_event(ptc.height, asset_update_event{ it->first, it->second }); - epee::misc_utils::cast_assign_a_to_b(it->second, ado.descriptor); - + //update event for asset that we control, check if ownership is still ours + if (ado.descriptor.owner != m_account.get_public_address().spend_public_key && !it->second.thirdparty_custody) + { + //ownership of the asset had been transfered + add_rollback_event(ptc.height, asset_unown_event{ it->first, it->second }); + m_own_asset_descriptors.erase(it); + + std::stringstream ss; + ss << "Asset ownership lost:" + << ENDL << "asset id: " << asset_id + << ENDL << "New owner: " << ado.descriptor.owner + << ENDL << "Name: " << ado.descriptor.full_name + << ENDL << "Ticker: " << ado.descriptor.ticker + << ENDL << "Total Max Supply: " << print_asset_money(ado.descriptor.total_max_supply, ado.descriptor.decimal_point) + << ENDL << "Current Supply: " << print_asset_money(ado.descriptor.current_supply, ado.descriptor.decimal_point) + << ENDL << "Decimal Point: " << (int)ado.descriptor.decimal_point; + + add_rollback_event(ptc.height, asset_register_event{ asset_id }); + WLT_LOG_MAGENTA(ss.str(), LOG_LEVEL_0); + if (m_wcallback) + m_wcallback->on_message(i_wallet2_callback::ms_yellow, ss.str()); + } + else + { + //just an update of the asset + add_rollback_event(ptc.height, asset_update_event{ it->first, it->second }); + epee::misc_utils::cast_assign_a_to_b(it->second, ado.descriptor); + + } } } - } - } while (false); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::add_rollback_event(uint64_t h, const wallet_event_t& ev) -{ - m_rollback_events.emplace_back(h, ev); -} -//---------------------------------------------------------------------------------------------------- -#define M_LAST_ZC_GLOBAL_INDEXS_MAX_SIZE 30 -void wallet2::add_to_last_zc_global_indexs(uint64_t h, uint64_t last_zc_output_index) -{ - if (m_last_zc_global_indexs.size()) + } while (false); + } + //---------------------------------------------------------------------------------------------------- + void wallet2::add_rollback_event(uint64_t h, const wallet_event_t& ev) { - if (h > m_last_zc_global_indexs.begin()->first) + m_rollback_events.emplace_back(h, ev); + } + //---------------------------------------------------------------------------------------------------- +#define M_LAST_ZC_GLOBAL_INDEXS_MAX_SIZE 30 + void wallet2::add_to_last_zc_global_indexs(uint64_t h, uint64_t last_zc_output_index) + { + if (m_last_zc_global_indexs.size()) + { + if (h > m_last_zc_global_indexs.begin()->first) + { + //new block added on top of last one, simply add new record + m_last_zc_global_indexs.push_front(std::make_pair(h, last_zc_output_index)); + } + else if (h < m_last_zc_global_indexs.begin()->first) + { + //looks like reorganize, pop all records before + while (m_last_zc_global_indexs.size() && m_last_zc_global_indexs.begin()->first >= h) + { + m_last_zc_global_indexs.erase(m_last_zc_global_indexs.begin()); + } + m_last_zc_global_indexs.push_front(std::make_pair(h, last_zc_output_index)); + } + else + { + //@#@ +#ifdef _DEBUG + if (m_last_zc_global_indexs.begin()->second > last_zc_output_index) + { + LOG_ERROR("!!!!!!!!!!!!!!!!!"); + } +#endif + //equals, same h but new last_zc_output_index, just update it, should be always bigger then prev + WLT_THROW_IF_FALSE_WITH_CODE(m_last_zc_global_indexs.begin()->second <= last_zc_output_index, + "condition m_last_zc_global_indexs.begin()->second " << m_last_zc_global_indexs.begin()->second << " <= last_zc_output_index " << last_zc_output_index << " failed", API_RETURN_CODE_INTERNAL_ERROR); + m_last_zc_global_indexs.begin()->second = last_zc_output_index; + } + } + else { //new block added on top of last one, simply add new record m_last_zc_global_indexs.push_front(std::make_pair(h, last_zc_output_index)); } - else if (h < m_last_zc_global_indexs.begin()->first) - { - //looks like reorganize, pop all records before - while (m_last_zc_global_indexs.size() && m_last_zc_global_indexs.begin()->first >= h) - { - m_last_zc_global_indexs.erase(m_last_zc_global_indexs.begin()); - } - m_last_zc_global_indexs.push_front(std::make_pair(h, last_zc_output_index)); - } - else - { - //@#@ -#ifdef _DEBUG - if (m_last_zc_global_indexs.begin()->second > last_zc_output_index) - { - LOG_ERROR("!!!!!!!!!!!!!!!!!"); - } -#endif - //equals, same h but new last_zc_output_index, just update it, should be always bigger then prev - WLT_THROW_IF_FALSE_WITH_CODE(m_last_zc_global_indexs.begin()->second <= last_zc_output_index, - "condition m_last_zc_global_indexs.begin()->second " << m_last_zc_global_indexs.begin()->second << " <= last_zc_output_index " << last_zc_output_index << " failed", API_RETURN_CODE_INTERNAL_ERROR); - m_last_zc_global_indexs.begin()->second = last_zc_output_index; - } + + if (m_last_zc_global_indexs.size() > M_LAST_ZC_GLOBAL_INDEXS_MAX_SIZE) + m_last_zc_global_indexs.pop_back(); } - else + //---------------------------------------------------------------------------------------------------- + uint64_t wallet2::get_actual_zc_global_index() { - //new block added on top of last one, simply add new record - m_last_zc_global_indexs.push_front(std::make_pair(h, last_zc_output_index)); + WLT_THROW_IF_FALSE_WITH_CODE(m_last_zc_global_indexs.size(), "m_last_zc_global_indexs is empty", API_RETURN_CODE_INTERNAL_ERROR); + for (auto it = m_last_zc_global_indexs.begin(); it != m_last_zc_global_indexs.end(); it++) + { + if (it->first <= m_last_known_daemon_height - WALLET_DEFAULT_TX_SPENDABLE_AGE) + { + return it->second; + } + } + WLT_THROW_IF_FALSE_WITH_CODE(false, "doesn't have anything that match expected height = " << m_last_known_daemon_height - WALLET_DEFAULT_TX_SPENDABLE_AGE, API_RETURN_CODE_INTERNAL_ERROR); + throw std::runtime_error(""); //mostly to suppress compiler warning } - - if (m_last_zc_global_indexs.size() > M_LAST_ZC_GLOBAL_INDEXS_MAX_SIZE) - m_last_zc_global_indexs.pop_back(); -} -//---------------------------------------------------------------------------------------------------- -uint64_t wallet2::get_actual_zc_global_index() -{ - WLT_THROW_IF_FALSE_WITH_CODE(m_last_zc_global_indexs.size(), "m_last_zc_global_indexs is empty", API_RETURN_CODE_INTERNAL_ERROR); - for (auto it = m_last_zc_global_indexs.begin(); it != m_last_zc_global_indexs.end(); it++) + //---------------------------------------------------------------------------------------------------- + void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t height, const currency::block& b, const std::vector* pglobal_indexes) { - if (it->first <= m_last_known_daemon_height - WALLET_DEFAULT_TX_SPENDABLE_AGE) - { - return it->second; - } - } - WLT_THROW_IF_FALSE_WITH_CODE(false, "doesn't have anything that match expected height = " << m_last_known_daemon_height - WALLET_DEFAULT_TX_SPENDABLE_AGE, API_RETURN_CODE_INTERNAL_ERROR); - throw std::runtime_error(""); //mostly to suppress compiler warning -} -//---------------------------------------------------------------------------------------------------- -void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t height, const currency::block& b, const std::vector* pglobal_indexes) -{ - //check for transaction spends - process_transaction_context ptc(tx); + //check for transaction spends + process_transaction_context ptc(tx); - process_unconfirmed(tx, ptc.recipients, ptc.remote_aliases); + process_unconfirmed(tx, ptc.recipients, ptc.remote_aliases); - // check all outputs for spending (compare key images) - ptc.coin_base_tx = is_coinbase(tx, ptc.is_pos_coinbase); - //PoW block don't have change, so all outs supposed to be marked as "mined" - ptc.is_derived_from_coinbase = !ptc.is_pos_coinbase; - ptc.height = height; - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(pglobal_indexes, "pglobal_indexes not set"); - if (this->is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM)) - { - if (pglobal_indexes->size()) + // check all outputs for spending (compare key images) + ptc.coin_base_tx = is_coinbase(tx, ptc.is_pos_coinbase); + //PoW block don't have change, so all outs supposed to be marked as "mined" + ptc.is_derived_from_coinbase = !ptc.is_pos_coinbase; + ptc.height = height; + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(pglobal_indexes, "pglobal_indexes not set"); + if (this->is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM)) { - //@#@ - WLT_LOG_L2("add_to_last_zc_global_indexs: h: " << height << ", b: " << currency::get_block_hash(b) << " , tx: " << currency::get_transaction_hash(tx) << ", last_zc_output_index: " << pglobal_indexes->back()); - add_to_last_zc_global_indexs(ptc.height, pglobal_indexes->back()); - } - } - - for(auto& in : tx.vin) - { - ptc.sub_i = 0; - VARIANT_SWITCH_BEGIN(in); - VARIANT_CASE_CONST(currency::txin_to_key, intk) - { - process_input_t(intk, ptc, tx); - } - VARIANT_CASE_CONST(currency::txin_zc_input, in_zc) - { - process_input_t(in_zc, ptc, tx); - //ptc.sub_i++; - } - VARIANT_CASE_CONST(currency::txin_multisig, inms) - { - crypto::hash multisig_id = inms.multisig_out_id; - auto it = m_multisig_transfers.find(multisig_id); - if (it != m_multisig_transfers.end()) + if (pglobal_indexes->size()) { - it->second.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; - it->second.m_spent_height = height; - WLT_LOG_L0("Spent multisig out: " << multisig_id << ", amount: " << print_money(currency::get_amount_from_variant(in)) << ", with tx: " << ptc.tx_hash() << ", at height " << height); - ptc.employed_entries.spent.push_back(wallet_public::employed_tx_entry{ ptc.i }); + //@#@ + WLT_LOG_L2("add_to_last_zc_global_indexs: h: " << height << ", b: " << currency::get_block_hash(b) << " , tx: " << currency::get_transaction_hash(tx) << ", last_zc_output_index: " << pglobal_indexes->back()); + add_to_last_zc_global_indexs(ptc.height, pglobal_indexes->back()); } } - VARIANT_CASE_CONST(currency::txin_htlc, in_htlc) + + for (auto& in : tx.vin) { - if (in_htlc.key_offsets.size() != 1) + ptc.sub_i = 0; + VARIANT_SWITCH_BEGIN(in); + VARIANT_CASE_CONST(currency::txin_to_key, intk) { - LOG_ERROR("in_htlc.key_offsets.size() != 1, skip inout"); - continue; + process_input_t(intk, ptc, tx); } - - if (in_htlc.key_offsets[0].type() != typeid(uint64_t)) + VARIANT_CASE_CONST(currency::txin_zc_input, in_zc) { - LOG_ERROR("HTLC with ref_by_id is not supported by wallet yet"); - continue; + process_input_t(in_zc, ptc, tx); + //ptc.sub_i++; } - - auto it = m_active_htlcs.find(std::make_pair(in_htlc.amount, boost::get(in_htlc.key_offsets[0]))); - if (it != m_active_htlcs.end()) + VARIANT_CASE_CONST(currency::txin_multisig, inms) { - transfer_details& td = m_transfers[it->second]; - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.m_ptx_wallet_info->m_tx.vout.size() > td.m_internal_output_index, "Internal error: wrong td.m_internal_output_index: " << td.m_internal_output_index); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].type() == typeid(tx_out_bare), "Internal error: wrong output type: " << td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].type().name()); - const boost::typeindex::type_info& ti = boost::get(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]).target.type(); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(ti == typeid(txout_htlc), "Internal error: wrong type of output's target: " << ti.name()); - //input spend active htlc - m_transfers[it->second].m_spent_height = height; - transfer_details_extra_option_htlc_info& tdeohi = get_or_add_field_to_variant_vector(td.varian_options); - tdeohi.origin = in_htlc.hltc_origin; - tdeohi.redeem_tx_id = ptc.tx_hash(); - } - } - VARIANT_SWITCH_END(); - ptc.i++; - } - - /* - collect unlock_time from every output that transfered coins to this account and use maximum of - all values m_payments entry, use this strict policy is required to protect exchanges from being feeded with - useless outputs - */ - ptc.max_out_unlock_time = 0; - - std::vector outs; - //uint64_t sum_of_native_outs = 0; // TODO: @#@# correctly calculate tx_money_got_in_outs for post-HF4 - ptc.tx_pub_key = null_pkey; - bool r = parse_and_validate_tx_extra(tx, ptc.tx_pub_key); - THROW_IF_TRUE_WALLET_EX(!r, error::tx_extra_parse_error, tx); - - //check for transaction income - crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); - std::list htlc_info_list; - r = lookup_acc_outs(m_account.get_keys(), tx, ptc.tx_pub_key, outs, derivation, htlc_info_list); - THROW_IF_TRUE_WALLET_EX(!r, error::acc_outs_lookup_error, tx, ptc.tx_pub_key, m_account.get_keys()); - - if (!outs.empty()) - { - //good news - got money! take care about it - //usually we have only one transfer for user in transaction - - //create once instance of tx for all entries - std::shared_ptr pwallet_info(new transaction_wallet_info()); - pwallet_info->m_tx = tx; - pwallet_info->m_block_height = height; - pwallet_info->m_block_timestamp = b.timestamp; - - if (is_auditable()) - { - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(pglobal_indexes && pglobal_indexes->size() == tx.vout.size(), "wrong pglobal_indexes = " << pglobal_indexes << ""); - } - std::vector outputs_index_local; - - if (!pglobal_indexes || (pglobal_indexes->size() == 0 && tx.vout.size() != 0)) - { - //if tx contain htlc_out, then we would need global_indexes anyway, to be able later detect redeem of htlc - if (m_use_deffered_global_outputs && htlc_info_list.size() == 0) - { - pglobal_indexes = nullptr; - } - else - { - fetch_tx_global_indixes(tx, outputs_index_local); - pglobal_indexes = &outputs_index_local; - } - } - - for (size_t i_in_outs = 0; i_in_outs != outs.size(); i_in_outs++) - { - const wallet_out_info& out = outs[i_in_outs]; - size_t o = out.index; - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(o < tx.vout.size(), "wrong out in transaction: internal index: " << o << ", tx.vout.size(): " << tx.vout.size()); - { - const currency::tx_out_v& out_v = tx.vout[o]; - bool out_type_zc = out_is_zc(out_v); - bool out_type_to_key = out_is_to_key(out_v); - bool out_type_htlc = out_is_to_htlc(out_v); - bool out_type_multisig = out_is_multisig(out_v); - - if (out_type_zc || out_type_to_key || out_type_htlc) + crypto::hash multisig_id = inms.multisig_out_id; + auto it = m_multisig_transfers.find(multisig_id); + if (it != m_multisig_transfers.end()) { - crypto::public_key out_key = out_get_pub_key(out_v, htlc_info_list); // htlc_info_list contains information about which one, redeem or refund key is ours for an htlc output + it->second.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; + it->second.m_spent_height = height; + WLT_LOG_L0("Spent multisig out: " << multisig_id << ", amount: " << print_money(currency::get_amount_from_variant(in)) << ", with tx: " << ptc.tx_hash() << ", at height " << height); + ptc.employed_entries.spent.push_back(wallet_public::employed_tx_entry{ ptc.i }); + } + } + VARIANT_CASE_CONST(currency::txin_htlc, in_htlc) + { + if (in_htlc.key_offsets.size() != 1) + { + LOG_ERROR("in_htlc.key_offsets.size() != 1, skip inout"); + continue; + } - // obtain key image for this output - crypto::key_image ki = currency::null_ki; - if (m_watch_only) + if (in_htlc.key_offsets[0].type() != typeid(uint64_t)) + { + LOG_ERROR("HTLC with ref_by_id is not supported by wallet yet"); + continue; + } + + auto it = m_active_htlcs.find(std::make_pair(in_htlc.amount, boost::get(in_htlc.key_offsets[0]))); + if (it != m_active_htlcs.end()) + { + transfer_details& td = m_transfers.at(it->second); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.m_ptx_wallet_info->m_tx.vout.size() > td.m_internal_output_index, "Internal error: wrong td.m_internal_output_index: " << td.m_internal_output_index); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].type() == typeid(tx_out_bare), "Internal error: wrong output type: " << td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].type().name()); + const boost::typeindex::type_info& ti = boost::get(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]).target.type(); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(ti == typeid(txout_htlc), "Internal error: wrong type of output's target: " << ti.name()); + //input spend active htlc + m_transfers.at(it->second).m_spent_height = height; + transfer_details_extra_option_htlc_info& tdeohi = get_or_add_field_to_variant_vector(td.varian_options); + tdeohi.origin = in_htlc.hltc_origin; + tdeohi.redeem_tx_id = ptc.tx_hash(); + } + } + VARIANT_SWITCH_END(); + ptc.i++; + } + + /* + collect unlock_time from every output that transfered coins to this account and use maximum of + all values m_payments entry, use this strict policy is required to protect exchanges from being feeded with + useless outputs + */ + ptc.max_out_unlock_time = 0; + + std::vector outs; + //uint64_t sum_of_native_outs = 0; // TODO: @#@# correctly calculate tx_money_got_in_outs for post-HF4 + ptc.tx_pub_key = null_pkey; + bool r = parse_and_validate_tx_extra(tx, ptc.tx_pub_key); + THROW_IF_TRUE_WALLET_EX(!r, error::tx_extra_parse_error, tx); + + //check for transaction income + crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); + std::list htlc_info_list; + r = lookup_acc_outs(m_account.get_keys(), tx, ptc.tx_pub_key, outs, derivation, htlc_info_list); + THROW_IF_TRUE_WALLET_EX(!r, error::acc_outs_lookup_error, tx, ptc.tx_pub_key, m_account.get_keys()); + + if (!outs.empty()) + { + //good news - got money! take care about it + //usually we have only one transfer for user in transaction + + //create once instance of tx for all entries + std::shared_ptr pwallet_info(new transaction_wallet_info()); + pwallet_info->m_tx = tx; + pwallet_info->m_block_height = height; + pwallet_info->m_block_timestamp = b.timestamp; + + if (is_auditable()) + { + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(pglobal_indexes && pglobal_indexes->size() == tx.vout.size(), "wrong pglobal_indexes = " << pglobal_indexes << ""); + } + std::vector outputs_index_local; + + if (!pglobal_indexes || (pglobal_indexes->size() == 0 && tx.vout.size() != 0)) + { + //if tx contain htlc_out, then we would need global_indexes anyway, to be able later detect redeem of htlc + if (m_use_deffered_global_outputs && htlc_info_list.size() == 0) + { + pglobal_indexes = nullptr; + } + else + { + fetch_tx_global_indixes(tx, outputs_index_local); + pglobal_indexes = &outputs_index_local; + } + } + + for (size_t i_in_outs = 0; i_in_outs != outs.size(); i_in_outs++) + { + const wallet_out_info& out = outs[i_in_outs]; + size_t o = out.index; + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(o < tx.vout.size(), "wrong out in transaction: internal index: " << o << ", tx.vout.size(): " << tx.vout.size()); + { + const currency::tx_out_v& out_v = tx.vout[o]; + bool out_type_zc = out_is_zc(out_v); + bool out_type_to_key = out_is_to_key(out_v); + bool out_type_htlc = out_is_to_htlc(out_v); + bool out_type_multisig = out_is_multisig(out_v); + + if (out_type_zc || out_type_to_key || out_type_htlc) { - if (!is_auditable()) + crypto::public_key out_key = out_get_pub_key(out_v, htlc_info_list); // htlc_info_list contains information about which one, redeem or refund key is ours for an htlc output + + // obtain key image for this output + crypto::key_image ki = currency::null_ki; + if (m_watch_only) { - // 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(out_key); - if (it != m_pending_key_images.end()) + if (!is_auditable()) { - ki = it->second; - WLT_LOG_L1("pending key image " << ki << " was found by out pub key " << out_key); - } - else - { - ki = currency::null_ki; - WLT_LOG_L1("can't find pending key image by out pub key: " << out_key << ", key image temporarily set to null"); + // 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(out_key); + if (it != m_pending_key_images.end()) + { + ki = it->second; + WLT_LOG_L1("pending key image " << ki << " was found by out pub key " << out_key); + } + else + { + ki = currency::null_ki; + WLT_LOG_L1("can't find pending key image by out pub key: " << out_key << ", key image temporarily set to null"); + } } } - } - else - { - // normal wallet, calculate and store key images for own outs - currency::keypair in_ephemeral = AUTO_VAL_INIT(in_ephemeral); - currency::generate_key_image_helper(m_account.get_keys(), ptc.tx_pub_key, o, in_ephemeral, ki); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(in_ephemeral.pub == out_key, "key_image generated ephemeral public key that does not match with output_key"); - } - - if (ki != currency::null_ki) - { - // make sure calculated key image for this own output has not been seen before - auto it = m_key_images.find(ki); - if (it != m_key_images.end()) + else { - // We encountered an output with a key image already seen. This implies only one can be spent in the future (assuming the first isn't spent yet). - // To address this, we disregard such outputs and log a warning. - // - // It was later revealed that auditable wallets could still be vulnerable: an attacker might quickly broadcast a transaction - // using the same output's ephemeral keys + the same tx pub key. If the malicious transaction (potentially for a lesser amount) - // arrives first, the recipient would be unable to spend the funds from the second, real transaction. - // This attack vector was highlighted by Luke Parker (twitter: @kayabaNerve), who suggested selecting the output with the largest amount. - // Sadly, this fix only applies to classic RingCT transactions and is incompatible with our use of Confidential Assets. - // Consequently, we adopted a solution suggested by @crypto_zoidberg: verifying in zero knowledge that the sender possesses the transaction's - // secret key. This verification is integrated with the balance proof (double Schnorr proof). - // - // However, we continue to omit outputs with duplicate key images since they could originate from the same source (albeit impractically). - // -- sowle + // normal wallet, calculate and store key images for own outs + currency::keypair in_ephemeral = AUTO_VAL_INIT(in_ephemeral); + currency::generate_key_image_helper(m_account.get_keys(), ptc.tx_pub_key, o, in_ephemeral, ki); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(in_ephemeral.pub == out_key, "key_image generated ephemeral public key that does not match with output_key"); + } - 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]; + if (ki != currency::null_ki) + { + // make sure calculated key image for this own output has not been seen before + auto it = m_key_images.find(ki); + if (it != m_key_images.end()) + { + // We encountered an output with a key image already seen. This implies only one can be spent in the future (assuming the first isn't spent yet). + // To address this, we disregard such outputs and log a warning. + // + // It was later revealed that auditable wallets could still be vulnerable: an attacker might quickly broadcast a transaction + // using the same output's ephemeral keys + the same tx pub key. If the malicious transaction (potentially for a lesser amount) + // arrives first, the recipient would be unable to spend the funds from the second, real transaction. + // This attack vector was highlighted by Luke Parker (twitter: @kayabaNerve), who suggested selecting the output with the largest amount. + // Sadly, this fix only applies to classic RingCT transactions and is incompatible with our use of Confidential Assets. + // Consequently, we adopted a solution suggested by @crypto_zoidberg: verifying in zero knowledge that the sender possesses the transaction's + // secret key. This verification is integrated with the balance proof (double Schnorr proof). + // + // However, we continue to omit outputs with duplicate key images since they could originate from the same source (albeit impractically). + // -- sowle + //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.at(it->second); + + std::stringstream ss; + ss << "tx " << ptc.tx_hash() << " @ block " << height << " has output #" << o << " with amount " << out.amount; + if (!out.is_native_coin()) + ss << "(asset_id: " << out.asset_id << ") "; + ss << "and 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."; + WLT_LOG_YELLOW(ss.str(), LOG_LEVEL_0); + if (m_wcallback) + m_wcallback->on_message(i_wallet2_callback::ms_yellow, ss.str()); + //if (out.is_native_coin()) + //{ + //WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(sum_of_native_outs >= out.amount, "sum_of_native_outs: " << sum_of_native_outs << ", out.amount:" << out.amount); + //sum_of_native_outs -= out.amount; + //} + continue; // skip the output + } + } + + uint8_t mix_attr = CURRENCY_TO_KEY_OUT_RELAXED; + [[maybe_unused]] bool mix_attr_r = get_mix_attr_from_tx_out_v(out_v, mix_attr); + if (is_auditable() && (out_type_to_key || out_type_zc) && mix_attr != CURRENCY_TO_KEY_OUT_FORCED_NO_MIX) + { std::stringstream ss; - ss << "tx " << ptc.tx_hash() << " @ block " << height << " has output #" << o << " with amount " << out.amount; + ss << "output #" << o << " from tx " << ptc.tx_hash(); if (!out.is_native_coin()) - ss << "(asset_id: " << out.asset_id << ") "; - ss << "and 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."; + ss << " asset_id: " << out.asset_id; + ss << " with amount " << print_money_brief(out.amount) + << " is targeted to this auditable wallet and has INCORRECT mix_attr = " << (uint64_t)mix_attr << ". Output is IGNORED."; WLT_LOG_YELLOW(ss.str(), LOG_LEVEL_0); if (m_wcallback) - m_wcallback->on_message(i_wallet2_callback::ms_yellow, ss.str()); + m_wcallback->on_message(i_wallet2_callback::ms_red, ss.str()); //if (out.is_native_coin()) //{ //WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(sum_of_native_outs >= out.amount, "sum_of_native_outs: " << sum_of_native_outs << ", out.amount:" << out.amount); @@ -778,1647 +802,1628 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t //} continue; // skip the output } - } - uint8_t mix_attr = CURRENCY_TO_KEY_OUT_RELAXED; - [[maybe_unused]] bool mix_attr_r = get_mix_attr_from_tx_out_v(out_v, mix_attr); - if (is_auditable() && (out_type_to_key || out_type_zc) && mix_attr != CURRENCY_TO_KEY_OUT_FORCED_NO_MIX) - { - std::stringstream ss; - ss << "output #" << o << " from tx " << ptc.tx_hash(); - if (!out.is_native_coin()) - ss << " asset_id: " << out.asset_id; - ss << " with amount " << print_money_brief(out.amount) - << " is targeted to this auditable wallet and has INCORRECT mix_attr = " << (uint64_t)mix_attr << ". Output is IGNORED."; - WLT_LOG_YELLOW(ss.str(), LOG_LEVEL_0); - if (m_wcallback) - m_wcallback->on_message(i_wallet2_callback::ms_red, ss.str()); - //if (out.is_native_coin()) - //{ - //WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(sum_of_native_outs >= out.amount, "sum_of_native_outs: " << sum_of_native_outs << ", out.amount:" << out.amount); - //sum_of_native_outs -= out.amount; - //} - continue; // skip the output - } - - ptc.employed_entries.receive.push_back(wallet_public::employed_tx_entry{ o , out.amount , out.asset_id}); - - m_transfers.push_back(boost::value_initialized()); - transfer_details& td = m_transfers.back(); - td.m_ptx_wallet_info = pwallet_info; - td.m_internal_output_index = o; - td.m_key_image = ki; - td.m_amount = out.amount; - if (m_use_deffered_global_outputs) - { - if (pglobal_indexes && pglobal_indexes->size() > o) - td.m_global_output_index = (*pglobal_indexes)[o]; + ptc.employed_entries.receive.push_back(wallet_public::employed_tx_entry{ o , out.amount , out.asset_id }); + uint64_t new_index = m_transfers.empty() ? 0 : (--m_transfers.end())->first; + auto rsp = m_transfers.insert(std::make_pair(new_index, boost::value_initialized())); + transfer_details& td = rsp.first->second; + td.m_ptx_wallet_info = pwallet_info; + td.m_internal_output_index = o; + td.m_key_image = ki; + td.m_amount = out.amount; + if (m_use_deffered_global_outputs) + { + if (pglobal_indexes && pglobal_indexes->size() > o) + td.m_global_output_index = (*pglobal_indexes)[o]; + else + td.m_global_output_index = WALLET_GLOBAL_OUTPUT_INDEX_UNDEFINED; + } else - td.m_global_output_index = WALLET_GLOBAL_OUTPUT_INDEX_UNDEFINED; - } - else - { - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(pglobal_indexes, "pglobal_indexes IS NULL in non mobile wallet"); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(pglobal_indexes->size() > o, "pglobal_indexes size()(" << pglobal_indexes->size() << ") <= o " << o); - td.m_global_output_index = (*pglobal_indexes)[o]; - } - if (ptc.coin_base_tx) - { - //last out in coinbase tx supposed to be change from coinstake - //for genesis block we'll count every input as WALLET_TRANSFER_DETAIL_FLAG_MINED_TRANSFER - if (td.m_ptx_wallet_info->m_block_height == 0 || !(o == tx.vout.size() - 1 && !ptc.is_derived_from_coinbase)) // TODO: @#@# reconsider this condition { - td.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_MINED_TRANSFER; + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(pglobal_indexes, "pglobal_indexes IS NULL in non mobile wallet"); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(pglobal_indexes->size() > o, "pglobal_indexes size()(" << pglobal_indexes->size() << ") <= o " << o); + td.m_global_output_index = (*pglobal_indexes)[o]; } - } - - if (out_type_zc) - { - td.m_zc_info_ptr.reset(new transfer_details_base::ZC_out_info(out.amount_blinding_mask, out.asset_id_blinding_mask, out.asset_id)); - } - - size_t transfer_index = m_transfers.size() - 1; - if (out_type_htlc) - { - const currency::txout_htlc& hltc = out_get_htlc(out_v); - //mark this as spent - td.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; - //create entry for htlc input - htlc_expiration_trigger het = AUTO_VAL_INIT(het); - het.is_wallet_owns_redeem = (out_key == hltc.pkey_redeem) ? true : false; - het.transfer_index = transfer_index; - uint64_t expired_if_more_then = td.m_ptx_wallet_info->m_block_height + hltc.expiration; - m_htlcs.insert(std::make_pair(expired_if_more_then, het)); - - if (het.is_wallet_owns_redeem) + if (ptc.coin_base_tx) { - td.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_HTLC_REDEEM; - } - - //active htlc - auto amount_gindex_pair = std::make_pair(td.m_amount, td.m_global_output_index); - m_active_htlcs[amount_gindex_pair] = transfer_index; - m_active_htlcs_txid[ptc.tx_hash()] = transfer_index; - //add payer to extra options - currency::tx_payer payer = AUTO_VAL_INIT(payer); - if (het.is_wallet_owns_redeem) - { - if (currency::get_type_in_variant_container(tx.extra, payer)) + //last out in coinbase tx supposed to be change from coinstake + //for genesis block we'll count every input as WALLET_TRANSFER_DETAIL_FLAG_MINED_TRANSFER + if (td.m_ptx_wallet_info->m_block_height == 0 || !(o == tx.vout.size() - 1 && !ptc.is_derived_from_coinbase)) // TODO: @#@# reconsider this condition { - crypto::chacha_crypt(payer.acc_addr, derivation); - td.varian_options.push_back(payer); + td.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_MINED_TRANSFER; } } - else + + if (out_type_zc) { - //since this is refund-mode htlc out, then sender is this wallet itself - payer.acc_addr = m_account.get_public_address(); - td.varian_options.push_back(payer); + td.m_zc_info_ptr.reset(new transfer_details_base::ZC_out_info(out.amount_blinding_mask, out.asset_id_blinding_mask, out.asset_id)); } + size_t transfer_index = new_index; + if (out_type_htlc) + { + const currency::txout_htlc& hltc = out_get_htlc(out_v); + //mark this as spent + td.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; + //create entry for htlc input + htlc_expiration_trigger het = AUTO_VAL_INIT(het); + het.is_wallet_owns_redeem = (out_key == hltc.pkey_redeem) ? true : false; + het.transfer_index = transfer_index; + uint64_t expired_if_more_then = td.m_ptx_wallet_info->m_block_height + hltc.expiration; + m_htlcs.insert(std::make_pair(expired_if_more_then, het)); + + if (het.is_wallet_owns_redeem) + { + td.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_HTLC_REDEEM; + } + + //active htlc + auto amount_gindex_pair = std::make_pair(td.m_amount, td.m_global_output_index); + m_active_htlcs[amount_gindex_pair] = transfer_index; + m_active_htlcs_txid[ptc.tx_hash()] = transfer_index; + //add payer to extra options + currency::tx_payer payer = AUTO_VAL_INIT(payer); + if (het.is_wallet_owns_redeem) + { + if (currency::get_type_in_variant_container(tx.extra, payer)) + { + crypto::chacha_crypt(payer.acc_addr, derivation); + td.varian_options.push_back(payer); + } + } + else + { + //since this is refund-mode htlc out, then sender is this wallet itself + payer.acc_addr = m_account.get_public_address(); + td.varian_options.push_back(payer); + } + + } + else + { + ptc.total_balance_change[td.get_asset_id()] += td.amount(); + add_transfer_to_transfers_cache(td.m_amount, transfer_index, td.get_asset_id()); + } + + if (td.m_key_image != currency::null_ki) + m_key_images[td.m_key_image] = transfer_index; + + if (is_watch_only() && is_auditable()) + { + WLT_CHECK_AND_ASSERT_MES_NO_RET(td.m_global_output_index != WALLET_GLOBAL_OUTPUT_INDEX_UNDEFINED, "td.m_global_output_index != WALLET_GLOBAL_OUTPUT_INDEX_UNDEFINED validation failed"); + auto amount_gindex_pair = std::make_pair(td.amount_for_global_output_index(), td.m_global_output_index); + WLT_CHECK_AND_ASSERT_MES_NO_RET(m_amount_gindex_to_transfer_id.count(amount_gindex_pair) == 0, "update m_amount_gindex_to_transfer_id: amount_for_global_output_index: " << td.amount_for_global_output_index() << ", gindex: " << td.m_global_output_index << " already exists"); + m_amount_gindex_to_transfer_id[amount_gindex_pair] = transfer_index; + } + + if (ptc.max_out_unlock_time < get_tx_unlock_time(tx, o)) + ptc.max_out_unlock_time = get_tx_unlock_time(tx, o); + + if (out_type_to_key || out_type_zc) + { + if (td.is_native_coin()) + { + WLT_LOG_L0("Received native coins, transfer #" << transfer_index << ", amount: " << print_money_brief(td.amount()) << (out_type_zc ? " (hidden)" : "") << ", with tx: " << ptc.tx_hash() << ", at height " << height); + } + else + { + // TODO @#@# output asset's ticker/name + WLT_LOG_L0("Received asset " << print16(td.get_asset_id()) << ", transfer #" << transfer_index << ", amount: " << print_money_brief(td.amount()) << (out_type_zc ? " (hidden)" : "") << ", with tx: " << ptc.tx_hash() << ", at height " << height); + } + } + else if (out_type_htlc) + { + WLT_LOG_L0("Detected HTLC[" << (td.m_flags & WALLET_TRANSFER_DETAIL_FLAG_HTLC_REDEEM ? "REDEEM" : "REFUND") << "], transfer #" << transfer_index << ", amount: " << print_money(td.amount()) << ", with tx: " << ptc.tx_hash() << ", at height " << height); + } + } + else if (out_type_multisig) + { + crypto::hash multisig_id = currency::get_multisig_out_id(tx, o); + WLT_CHECK_AND_ASSERT_MES_NO_RET(m_multisig_transfers.count(multisig_id) == 0, "multisig_id = " << multisig_id << " already in multisig container"); + transfer_details_base& tdb = m_multisig_transfers[multisig_id]; + tdb.m_ptx_wallet_info = pwallet_info; + tdb.m_internal_output_index = o; + tdb.m_amount = outs[i_in_outs].amount; + WLT_LOG_L0("Received multisig, multisig out id: " << multisig_id << ", amount: " << tdb.amount() << ", with tx: " << ptc.tx_hash()); } else { - ptc.total_balance_change[td.get_asset_id()] += td.amount(); - add_transfer_to_transfers_cache(td.m_amount, transfer_index, td.get_asset_id()); - } - - if (td.m_key_image != currency::null_ki) - m_key_images[td.m_key_image] = transfer_index; - - if (is_watch_only() && is_auditable()) - { - WLT_CHECK_AND_ASSERT_MES_NO_RET(td.m_global_output_index != WALLET_GLOBAL_OUTPUT_INDEX_UNDEFINED, "td.m_global_output_index != WALLET_GLOBAL_OUTPUT_INDEX_UNDEFINED validation failed"); - auto amount_gindex_pair = std::make_pair(td.amount_for_global_output_index(), td.m_global_output_index); - WLT_CHECK_AND_ASSERT_MES_NO_RET(m_amount_gindex_to_transfer_id.count(amount_gindex_pair) == 0, "update m_amount_gindex_to_transfer_id: amount_for_global_output_index: " << td.amount_for_global_output_index() << ", gindex: " << td.m_global_output_index << " already exists"); - m_amount_gindex_to_transfer_id[amount_gindex_pair] = transfer_index; - } - - if (ptc.max_out_unlock_time < get_tx_unlock_time(tx, o)) - ptc.max_out_unlock_time = get_tx_unlock_time(tx, o); - - if (out_type_to_key || out_type_zc) - { - if (td.is_native_coin()) - { - WLT_LOG_L0("Received native coins, transfer #" << transfer_index << ", amount: " << print_money_brief(td.amount()) << (out_type_zc ? " (hidden)" : "") << ", with tx: " << ptc.tx_hash() << ", at height " << height); - } - else - { - // TODO @#@# output asset's ticker/name - WLT_LOG_L0("Received asset " << print16(td.get_asset_id()) << ", transfer #" << transfer_index << ", amount: " << print_money_brief(td.amount()) << (out_type_zc ? " (hidden)" : "") << ", with tx: " << ptc.tx_hash() << ", at height " << height); - } - } - else if (out_type_htlc) - { - WLT_LOG_L0("Detected HTLC[" << (td.m_flags&WALLET_TRANSFER_DETAIL_FLAG_HTLC_REDEEM ? "REDEEM" : "REFUND") << "], transfer #" << transfer_index << ", amount: " << print_money(td.amount()) << ", with tx: " << ptc.tx_hash() << ", at height " << height); + WLT_LOG_YELLOW("Unexpected output type: " << out_v.type().name() << ", out index: " << o << " in tx " << ptc.tx_hash(), LOG_LEVEL_0); } } - else if (out_type_multisig) - { - crypto::hash multisig_id = currency::get_multisig_out_id(tx, o); - WLT_CHECK_AND_ASSERT_MES_NO_RET(m_multisig_transfers.count(multisig_id) == 0, "multisig_id = " << multisig_id << " already in multisig container"); - transfer_details_base& tdb = m_multisig_transfers[multisig_id]; - tdb.m_ptx_wallet_info = pwallet_info; - tdb.m_internal_output_index = o; - tdb.m_amount = outs[i_in_outs].amount; - WLT_LOG_L0("Received multisig, multisig out id: " << multisig_id << ", amount: " << tdb.amount() << ", with tx: " << ptc.tx_hash()); - } - else - { - WLT_LOG_YELLOW("Unexpected output type: " << out_v.type().name() << ", out index: " << o << " in tx " << ptc.tx_hash(), LOG_LEVEL_0); - } + } - } - } - - //do final calculations - bool has_in_transfers = false; - bool has_out_transfers = false; - for (const auto& bce : ptc.total_balance_change) - { - if (bce.second > 0) + + //do final calculations + bool has_in_transfers = false; + bool has_out_transfers = false; + for (const auto& bce : ptc.total_balance_change) { - has_in_transfers = true; - } - else if (bce.second < 0) - { - has_out_transfers = true; - } - } - - //check if there are asset_registration that belong to this wallet - const asset_descriptor_operation* pado = get_type_in_variant_container(tx.extra); - if (pado && (ptc.employed_entries.receive.size() || ptc.employed_entries.spent.size() || pado->descriptor.owner == m_account.get_public_address().spend_public_key)) - { - //check if there are asset_registration that belong to this wallet - process_ado_in_new_transaction(*pado, ptc); - } - - if (has_in_transfers || has_out_transfers || is_derivation_used_to_encrypt(tx, derivation) || ptc.employed_entries.spent.size()) - { - ptc.timestamp = get_block_datetime(b); - handle_money(b, ptc); - } - - /* - if (ptc.sum_of_own_native_inputs) - {//this actually is transfer transaction, notify about spend - if (ptc.sum_of_own_native_inputs > sum_of_native_outs) - {//usual transfer - handle_money_spent2(b, tx, ptc.sum_of_own_native_inputs - (sum_of_native_outs + get_tx_fee(tx)), ptc.mtd, recipients, remote_aliases); - } - else - {//strange transfer, seems that in one transaction have transfers from different wallets. - if (!is_coinbase(tx)) + if (bce.second > 0) { - WLT_LOG_RED("Unusual transaction " << ptc.tx_hash() << ", sum_of_native_inputs: " << ptc.sum_of_own_native_inputs << ", sum_of_native_outs: " << sum_of_native_outs, LOG_LEVEL_0); + has_in_transfers = true; + } + else if (bce.second < 0) + { + has_out_transfers = true; } - handle_money_received2(b, tx, (sum_of_native_outs - (ptc.sum_of_own_native_inputs - get_tx_fee(tx))), ptc.mtd); - } - } - else - { - if (sum_of_native_outs != 0) - { - handle_money_received2(b, tx, sum_of_native_outs, ptc.mtd); - } - else if (currency::is_derivation_used_to_encrypt(tx, derivation)) - { - //transaction doesn't transfer actually money, but bring some information - handle_money_received2(b, tx, 0, ptc.mtd); - } - else if (ptc.mtd.spent_indices.size()) - { - // multisig spend detected - handle_money_spent2(b, tx, 0, ptc.mtd, recipients, remote_aliases); - } - }*/ -} -//---------------------------------------------------------------------------------------------------- -void wallet2::prepare_wti_decrypted_attachments(wallet_public::wallet_transfer_info& wti, const std::vector& decrypted_att) -{ - PROFILE_FUNC("wallet2::prepare_wti_decrypted_attachments"); - - if (!wti.payment_id.empty()) - { - LOG_ERROR("wti.payment_id is expected to be empty. Go ahead."); - } - get_payment_id_from_decrypted_container(decrypted_att, wti.payment_id); - - for (const auto& item : decrypted_att) - { - if (item.type() == typeid(currency::tx_service_attachment)) - { - wti.service_entries.push_back(boost::get(item)); - } - } - - if (wti.is_income_mode_encryption()) - { - account_public_address sender_address = AUTO_VAL_INIT(sender_address); - wti.show_sender = handle_2_alternative_types_in_variant_container(decrypted_att, [&](const tx_payer& p) { sender_address = p.acc_addr; return false; /* <- continue? */ }); - if (wti.show_sender) - if(!wti.remote_addresses.size()) - wti.remote_addresses.push_back(currency::get_account_address_as_str(sender_address)); - } - else - { - if (wti.remote_addresses.empty()) - { - handle_2_alternative_types_in_variant_container(decrypted_att, [&](const tx_receiver& p) { - std::string addr_str; - if (wti.payment_id.empty()) - addr_str = currency::get_account_address_as_str(p.acc_addr); - else - addr_str = currency::get_account_address_and_payment_id_as_str(p.acc_addr, wti.payment_id); // show integrated address if there's a payment id provided - wti.remote_addresses.push_back(addr_str); - LOG_PRINT_YELLOW("prepare_wti_decrypted_attachments, income=false, rem. addr = " << addr_str, LOG_LEVEL_0); - return true; // continue iterating through the container - }); - } - } - - currency::tx_comment cm; - if (currency::get_type_in_variant_container(decrypted_att, cm)) - wti.comment = cm.comment; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::resend_unconfirmed() -{ - COMMAND_RPC_FORCE_RELAY_RAW_TXS::request req = AUTO_VAL_INIT(req); - COMMAND_RPC_FORCE_RELAY_RAW_TXS::response res = AUTO_VAL_INIT(res); - - for (auto& ut : m_unconfirmed_txs) - { - req.txs_as_hex.push_back(epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(ut.second.tx))); - WLT_LOG_GREEN("Relaying tx: " << ut.second.tx_hash, LOG_LEVEL_0); - } - - if (!req.txs_as_hex.size()) - return; - - bool r = m_core_proxy->call_COMMAND_RPC_FORCE_RELAY_RAW_TXS(req, res); - WLT_CHECK_AND_ASSERT_MES(r, void(), "wrong result at call_COMMAND_RPC_FORCE_RELAY_RAW_TXS"); - WLT_CHECK_AND_ASSERT_MES(res.status == API_RETURN_CODE_OK, void(), "wrong result at call_COMMAND_RPC_FORCE_RELAY_RAW_TXS: status != OK, status=" << res.status); - - WLT_LOG_GREEN("Relayed " << req.txs_as_hex.size() << " txs", LOG_LEVEL_0); -} -//----------------------------------------------------------------------------------------------------- -void wallet2::accept_proposal(const crypto::hash& contract_id, uint64_t b_acceptance_fee, currency::transaction* p_acceptance_tx /* = nullptr */) -{ - auto contr_it = m_contracts.find(contract_id); - THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it != m_contracts.end(), "Unknow contract id: " << contract_id); - - THROW_IF_FALSE_WALLET_INT_ERR_EX(!contr_it->second.is_a, "contr_it->second.is_a supposed to be false, but it is " << contr_it->second.is_a); - THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it->second.state == tools::wallet_public::escrow_contract_details_basic::proposal_sent, "contr_it->second.state supposed to be proposal_sent(" << tools::wallet_public::escrow_contract_details_basic::proposal_sent << ") but it is: " << tools::wallet_public::get_escrow_contract_state_name(contr_it->second.state)); - - construct_tx_param construct_param = AUTO_VAL_INIT(construct_param); - construct_param.fee = b_acceptance_fee; - mode_separate_context msc = AUTO_VAL_INIT(msc); - msc.escrow = true; - msc.tx_for_mode_separate = contr_it->second.proposal.tx_template; - currency::transaction& tx = msc.tx_for_mode_separate; - 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 = get_current_split_strategy(); - - //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(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 = tx; - pwallet_info->m_block_height = 0; - pwallet_info->m_block_timestamp = 0; - tdb.m_ptx_wallet_info = pwallet_info; - tdb.m_internal_output_index = n; - tdb.m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_SPENT); - //--------------------------------- - //@#@ todo: proper handling with zarcanum_based stuff - //figure out fee that was left for release contract - THROW_IF_FALSE_WALLET_INT_ERR_EX(tx.vout[n].type() == typeid(tx_out_bare), "Unexpected output type in accept proposal"); - THROW_IF_FALSE_WALLET_INT_ERR_EX(boost::get(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 = boost::get(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); - - //prepare templates to let buyer release or burn escrow - bc_services::escrow_relese_templates_body artb = AUTO_VAL_INIT(artb); - build_escrow_release_templates(contract_id, - left_for_fee_in_multisig, - artb.tx_normal_template, - artb.tx_burn_template, - contr_it->second.private_detailes); - - - //---second part of bad design --- - auto it = m_multisig_transfers.find(contract_id); - THROW_IF_FALSE_WALLET_EX(it != m_multisig_transfers.end(), error::wallet_internal_error, "Internal error"); - m_multisig_transfers.erase(it); - //--------------------------------- - - tx_service_attachment tsa = AUTO_VAL_INIT(tsa); - bc_services::pack_attachment_as_gzipped_bin(artb, tsa); - tsa.service_id = BC_ESCROW_SERVICE_ID; - tsa.instruction = BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_TEMPLATES; - tsa.flags |= TX_SERVICE_ATTACHMENT_ENCRYPT_BODY; - construct_param.extra.push_back(tsa); - - //build transaction - currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); - ftp.tx_version = this->get_current_tx_version(); - prepare_transaction(construct_param, ftp, msc); - 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)) + ">"); - - try - { - finalize_transaction(ftp, tx, one_time_key, true); - } - catch (...) - { - clear_transfers_from_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, std::string("exception in finalize_transaction, tx: ") + epee::string_tools::pod_to_hex(get_transaction_hash(tx))); - throw; - } - - 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 = tx; -} -//--------------------------------------------------------------------------------- -uint64_t wallet2::get_current_tx_version() -{ - uint64_t tx_expected_block_height = get_top_block_height() + 1; - return currency::get_tx_version(tx_expected_block_height, this->m_core_runtime_config.hard_forks); -} -//--------------------------------------------------------------------------------- -void wallet2::finish_contract(const crypto::hash& contract_id, const std::string& release_type, currency::transaction* p_release_tx /* = nullptr */) -{ - auto contr_it = m_contracts.find(contract_id); - THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it != m_contracts.end(), "Unknow contract id: " << contract_id); - - THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it->second.is_a, "contr_it->second.is_a is supposed to be true, but it is " << contr_it->second.is_a); - THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it->second.state == tools::wallet_public::escrow_contract_details_basic::contract_accepted - || contr_it->second.state == tools::wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent, - "incorrect contract state at finish_contract(): (" << contr_it->second.state << "), expected states: contract_accepted (" << tools::wallet_public::escrow_contract_details_basic::contract_accepted << "), " << - "contract_cancel_proposal_sent (" << tools::wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent << ")"); - - auto multisig_it = m_multisig_transfers.find(contract_id); - THROW_IF_FALSE_WALLET_INT_ERR_EX(multisig_it != m_multisig_transfers.end(), "Unknow multisig id: " << contract_id); - - transaction tx = AUTO_VAL_INIT(tx); - if (release_type == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_NORMAL) - { - tx = contr_it->second.release_body.tx_normal_template; - } - else if (release_type == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_BURN) - { - tx = contr_it->second.release_body.tx_burn_template; - } - else - { - THROW_IF_FALSE_WALLET_INT_ERR_EX(false, "Unknow release_type = " << release_type); - } - - bool is_input_fully_signed = false; - bool r = sign_multisig_input_in_tx(tx, 0, m_account.get_keys(), multisig_it->second.m_ptx_wallet_info->m_tx, &is_input_fully_signed); - THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "sign_multisig_input_in_tx failed"); - // There's a two-party multisig input in tx and B-party had to already sign it upon template creation, so adding A-party sign here should make the tx fully signed. Make sure it does: - THROW_IF_FALSE_WALLET_INT_ERR_EX(is_input_fully_signed, "sign_multisig_input_in_tx returned is_input_fully_signed == false"); - - send_transaction_to_network(tx); - - if (p_release_tx != nullptr) - *p_release_tx = tx; -} -//----------------------------------------------------------------------------------------------------- -void wallet2::accept_cancel_contract(const crypto::hash& contract_id, currency::transaction* p_cancellation_acceptance_tx /* = nullptr */) -{ - TIME_MEASURE_START_MS(timing1); - auto contr_it = m_contracts.find(contract_id); - THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it != m_contracts.end(), "Unknow contract id"); - TIME_MEASURE_FINISH_MS(timing1); - - THROW_IF_FALSE_WALLET_INT_ERR_EX(!contr_it->second.is_a, "contr_it->second.is_a is supposed to be false, but it is " << contr_it->second.is_a); - THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it->second.state == tools::wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent, - "incorrect contract state: (" << contr_it->second.state << "), expected state: contract_cancel_proposal_sent (" << tools::wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent << ")"); - - TIME_MEASURE_START_MS(timing2); - auto multisig_it = m_multisig_transfers.find(contract_id); - THROW_IF_FALSE_WALLET_INT_ERR_EX(multisig_it != m_multisig_transfers.end(), "Can't find multisig transfer by id: " << contract_id); - TIME_MEASURE_FINISH_MS(timing2); - - transaction tx = contr_it->second.cancel_body.tx_cancel_template; - - TIME_MEASURE_START_MS(timing3); - bool is_input_fully_signed = false; - bool r = sign_multisig_input_in_tx(tx, 0, m_account.get_keys(), multisig_it->second.m_ptx_wallet_info->m_tx, &is_input_fully_signed); - THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "sign_multisig_input_in_tx failed"); - // There's a two-party multisig input in tx and A-party had to already sign it upon template creation, so adding B-party sign here should make the tx fully signed. Make sure it does: - THROW_IF_FALSE_WALLET_INT_ERR_EX(is_input_fully_signed, "sign_multisig_input_in_tx returned is_input_fully_signed == false"); - - send_transaction_to_network(tx); - TIME_MEASURE_FINISH_MS(timing3); - if (timing1 + timing2 + timing1 > 500) - WLT_LOG_RED("[wallet2::accept_cancel_contract] LOW PERFORMANCE: " << timing1 << "," << timing2 << "," << timing1, LOG_LEVEL_0); - - if (p_cancellation_acceptance_tx != nullptr) - *p_cancellation_acceptance_tx = tx; -} -//----------------------------------------------------------------------------------------------------- -void wallet2::request_cancel_contract(const crypto::hash& contract_id, uint64_t fee, uint64_t expiration_period, currency::transaction* p_cancellation_proposal_tx /* = nullptr */) -{ - auto contr_it = m_contracts.find(contract_id); - THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it != m_contracts.end(), "Unknow contract id: " << contract_id); - - THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it->second.is_a, "contr_it->second.is_a supposed to be true at request_cancel_contract"); - THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it->second.state == tools::wallet_public::escrow_contract_details_basic::contract_accepted - || contr_it->second.state == tools::wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent, - "incorrect contract state at request_cancel_contract(): " << tools::wallet_public::get_escrow_contract_state_name(contr_it->second.state) << ", expected states: contract_accepted (" << tools::wallet_public::escrow_contract_details_basic::contract_accepted << "), " << - "contract_cancel_proposal_sent (" << tools::wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent << ")"); - - auto multisig_it = m_multisig_transfers.find(contract_id); - THROW_IF_FALSE_WALLET_INT_ERR_EX(multisig_it != m_multisig_transfers.end(), "Unknow multisig id: " << contract_id); - - - ////////////////////////////////////////////////////////////////////////// - construct_tx_param construct_param = AUTO_VAL_INIT(construct_param); - construct_param.fee = fee; - - //------- - //prepare templates to let seller cancel escrow - bc_services::escrow_cancel_templates_body ectb = AUTO_VAL_INIT(ectb); - build_escrow_cancel_template(contract_id, - expiration_period, - ectb.tx_cancel_template, - contr_it->second.private_detailes); - - tx_service_attachment tsa = AUTO_VAL_INIT(tsa); - bc_services::pack_attachment_as_gzipped_bin(ectb, tsa); - tsa.service_id = BC_ESCROW_SERVICE_ID; - tsa.instruction = BC_ESCROW_SERVICE_INSTRUCTION_CANCEL_PROPOSAL; - 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 = get_current_split_strategy(); - - currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); - ftp.tx_version = this->get_current_tx_version(); - prepare_transaction(construct_param, ftp); - currency::transaction tx = AUTO_VAL_INIT(tx); - crypto::secret_key sk = AUTO_VAL_INIT(sk); - 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)) + ">"); - - try - { - finalize_transaction(ftp, tx, sk, true); - } - catch (...) - { - clear_transfers_from_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, std::string("exception in finalize_transaction, tx: ") + epee::string_tools::pod_to_hex(get_transaction_hash(tx))); - throw; - } - - print_tx_sent_message(tx, "(transport for cancel proposal)", fee); - - if (p_cancellation_proposal_tx != nullptr) - *p_cancellation_proposal_tx = tx; -} -//----------------------------------------------------------------------------------------------------- -void wallet2::scan_tx_to_key_inputs(std::vector& found_transfers, const currency::transaction& tx) -{ - for (auto& in : tx.vin) - { - if (in.type() == typeid(currency::txin_to_key)) - { - - auto it = m_key_images.find(boost::get(in).k_image); - if (it != m_key_images.end()) - found_transfers.push_back(it->second); - } - } -} -//----------------------------------------------------------------------------------------------------- -void wallet2::change_contract_state(wallet_public::escrow_contract_details_basic& contract, uint32_t new_state, const crypto::hash& contract_id, const wallet_public::wallet_transfer_info& wti) const -{ - WLT_LOG_YELLOW("escrow contract STATE CHANGE (" << (contract.is_a ? "A," : "B,") << contract_id << " via tx " << get_transaction_hash(wti.tx) << ", height: " << wti.height << ") : " - << wallet_public::get_escrow_contract_state_name(contract.state) << " -> " << wallet_public::get_escrow_contract_state_name(new_state), LOG_LEVEL_1); - - contract.state = new_state; - contract.height = wti.height; // update height of last state change -} -//----------------------------------------------------------------------------------------------------- -void wallet2::change_contract_state(wallet_public::escrow_contract_details_basic& contract, uint32_t new_state, const crypto::hash& contract_id, const std::string& reason /*= "internal intention"*/) const -{ - WLT_LOG_YELLOW("escrow contract STATE CHANGE (" << (contract.is_a ? "A," : "B,") << contract_id << " " << reason << ") : " - << wallet_public::get_escrow_contract_state_name(contract.state) << " -> " << wallet_public::get_escrow_contract_state_name(new_state), LOG_LEVEL_1); - - contract.state = new_state; -} -//----------------------------------------------------------------------------------------------------- -void from_outs_to_received_items(const std::vector& outs, std::vector& received, const currency::transaction& tx) -{ - for (const auto& item : outs) - { - if(!out_is_multisig(tx.vout[item.index])) - received.push_back(tools::payment_details_subtransfer{ item.asset_id, item.amount}); - } -} -//----------------------------------------------------------------------------------------------------- -bool wallet2::handle_proposal(wallet_public::wallet_transfer_info& wti, const bc_services::proposal_body& prop) -{ - PROFILE_FUNC("wallet2::handle_proposal"); - crypto::hash ms_id = AUTO_VAL_INIT(ms_id); - bc_services::contract_private_details cpd = AUTO_VAL_INIT(cpd); - std::vector decrypted_items; - if (!validate_escrow_proposal(wti, prop, decrypted_items, ms_id, cpd)) - return false; - - wallet_public::escrow_contract_details_basic& ed = epee::misc_utils::get_or_insert_value_initialized(m_contracts, ms_id); - ed.expiration_time = currency::get_tx_expiration_time(prop.tx_template); - ed.timestamp = wti.timestamp; - ed.is_a = cpd.a_addr.spend_public_key == m_account.get_keys().account_address.spend_public_key; - change_contract_state(ed, wallet_public::escrow_contract_details_basic::proposal_sent, ms_id, wti); - ed.private_detailes = cpd; - currency::get_payment_id_from_decrypted_container(decrypted_items, ed.payment_id); - ed.proposal = prop; - ed.height = wti.height; - wti.contract.resize(1); - static_cast(wti.contract.back()) = ed; - wti.contract.back().contract_id = ms_id; - - //correct fee in case if it "B", cz fee is paid by "A" - if (!ed.is_a) - wti.fee = 0; - else - { - //if it's A' then mark proposal template's inputs as spent and add to expiration list - std::vector found_transfers; - scan_tx_to_key_inputs(found_transfers, prop.tx_template); - //scan outputs to figure out amount of change in escrow - crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); - std::vector outs; - bool r = lookup_acc_outs(m_account.get_keys(), prop.tx_template, outs, derivation); - THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "Failed to lookup_acc_outs for tx: " << get_transaction_hash(prop.tx_template)); - std::vector received; - from_outs_to_received_items(outs, received, prop.tx_template); - add_transfers_to_expiration_list(found_transfers, received, ed.expiration_time, wti.tx_hash); - WLT_LOG_GREEN("Locked " << found_transfers.size() << " transfers due to proposal " << ms_id, LOG_LEVEL_0); - } - - - return true; -} -//----------------------------------------------------------------------------------------------------- -bool wallet2::handle_release_contract(wallet_public::wallet_transfer_info& wti, const std::string& release_instruction) -{ - PROFILE_FUNC("wallet2::handle_release_contract"); - size_t n = get_multisig_in_index(wti.tx.vin); - WLT_CHECK_AND_ASSERT_MES(n != wti.tx.vin.size(), false, "Multisig out not found in tx template in proposal"); - crypto::hash ms_id = boost::get(wti.tx.vin[n]).multisig_out_id; - WLT_CHECK_AND_ASSERT_MES(ms_id != null_hash, false, "Multisig out not found in tx template in proposal"); - auto it = m_contracts.find(ms_id); - WLT_CHECK_AND_ASSERT_MES(it != m_contracts.end(), false, "Multisig out not found in tx template in proposal"); - - if (release_instruction == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_NORMAL) - change_contract_state(it->second, wallet_public::escrow_contract_details_basic::contract_released_normal, ms_id, wti); - else if (release_instruction == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_CANCEL) - change_contract_state(it->second, wallet_public::escrow_contract_details_basic::contract_released_cancelled, ms_id, wti); - else if (release_instruction == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_BURN) - { - change_contract_state(it->second, wallet_public::escrow_contract_details_basic::contract_released_burned, ms_id, wti); - wallet_public::wallet_sub_transfer_info* subptr = nullptr; - for (auto& s: wti.subtransfers) - { - if (s.asset_id == currency::native_coin_asset_id) - subptr = &s; - } - if (subptr == nullptr) - { - wti.subtransfers.push_back(wallet_public::wallet_sub_transfer_info()); - subptr = &wti.subtransfers.back(); } - subptr->amount = it->second.private_detailes.amount_to_pay + it->second.private_detailes.amount_a_pledge + it->second.private_detailes.amount_b_pledge; - if (!it->second.is_a) + //check if there are asset_registration that belong to this wallet + const asset_descriptor_operation* pado = get_type_in_variant_container(tx.extra); + if (pado && (ptc.employed_entries.receive.size() || ptc.employed_entries.spent.size() || pado->descriptor.owner == m_account.get_public_address().spend_public_key)) { - wti.fee = currency::get_tx_fee(wti.tx); + //check if there are asset_registration that belong to this wallet + process_ado_in_new_transaction(*pado, ptc); + } + + if (has_in_transfers || has_out_transfers || is_derivation_used_to_encrypt(tx, derivation) || ptc.employed_entries.spent.size()) + { + ptc.timestamp = get_block_datetime(b); + handle_money(b, ptc); + } + + /* + if (ptc.sum_of_own_native_inputs) + {//this actually is transfer transaction, notify about spend + if (ptc.sum_of_own_native_inputs > sum_of_native_outs) + {//usual transfer + handle_money_spent2(b, tx, ptc.sum_of_own_native_inputs - (sum_of_native_outs + get_tx_fee(tx)), ptc.mtd, recipients, remote_aliases); + } + else + {//strange transfer, seems that in one transaction have transfers from different wallets. + if (!is_coinbase(tx)) + { + WLT_LOG_RED("Unusual transaction " << ptc.tx_hash() << ", sum_of_native_inputs: " << ptc.sum_of_own_native_inputs << ", sum_of_native_outs: " << sum_of_native_outs, LOG_LEVEL_0); + } + handle_money_received2(b, tx, (sum_of_native_outs - (ptc.sum_of_own_native_inputs - get_tx_fee(tx))), ptc.mtd); + } } else + { + if (sum_of_native_outs != 0) + { + handle_money_received2(b, tx, sum_of_native_outs, ptc.mtd); + } + else if (currency::is_derivation_used_to_encrypt(tx, derivation)) + { + //transaction doesn't transfer actually money, but bring some information + handle_money_received2(b, tx, 0, ptc.mtd); + } + else if (ptc.mtd.spent_indices.size()) + { + // multisig spend detected + handle_money_spent2(b, tx, 0, ptc.mtd, recipients, remote_aliases); + } + }*/ + } + //---------------------------------------------------------------------------------------------------- + void wallet2::prepare_wti_decrypted_attachments(wallet_public::wallet_transfer_info& wti, const std::vector& decrypted_att) + { + PROFILE_FUNC("wallet2::prepare_wti_decrypted_attachments"); + + if (!wti.payment_id.empty()) + { + LOG_ERROR("wti.payment_id is expected to be empty. Go ahead."); + } + get_payment_id_from_decrypted_container(decrypted_att, wti.payment_id); + + for (const auto& item : decrypted_att) + { + if (item.type() == typeid(currency::tx_service_attachment)) + { + wti.service_entries.push_back(boost::get(item)); + } + } + + if (wti.is_income_mode_encryption()) + { + account_public_address sender_address = AUTO_VAL_INIT(sender_address); + wti.show_sender = handle_2_alternative_types_in_variant_container(decrypted_att, [&](const tx_payer& p) { sender_address = p.acc_addr; return false; /* <- continue? */ }); + if (wti.show_sender) + if (!wti.remote_addresses.size()) + wti.remote_addresses.push_back(currency::get_account_address_as_str(sender_address)); + } + else + { + if (wti.remote_addresses.empty()) + { + handle_2_alternative_types_in_variant_container(decrypted_att, [&](const tx_receiver& p) { + std::string addr_str; + if (wti.payment_id.empty()) + addr_str = currency::get_account_address_as_str(p.acc_addr); + else + addr_str = currency::get_account_address_and_payment_id_as_str(p.acc_addr, wti.payment_id); // show integrated address if there's a payment id provided + wti.remote_addresses.push_back(addr_str); + LOG_PRINT_YELLOW("prepare_wti_decrypted_attachments, income=false, rem. addr = " << addr_str, LOG_LEVEL_0); + return true; // continue iterating through the container + }); + } + } + + currency::tx_comment cm; + if (currency::get_type_in_variant_container(decrypted_att, cm)) + wti.comment = cm.comment; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::resend_unconfirmed() + { + COMMAND_RPC_FORCE_RELAY_RAW_TXS::request req = AUTO_VAL_INIT(req); + COMMAND_RPC_FORCE_RELAY_RAW_TXS::response res = AUTO_VAL_INIT(res); + + for (auto& ut : m_unconfirmed_txs) + { + req.txs_as_hex.push_back(epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(ut.second.tx))); + WLT_LOG_GREEN("Relaying tx: " << ut.second.tx_hash, LOG_LEVEL_0); + } + + if (!req.txs_as_hex.size()) + return; + + bool r = m_core_proxy->call_COMMAND_RPC_FORCE_RELAY_RAW_TXS(req, res); + WLT_CHECK_AND_ASSERT_MES(r, void(), "wrong result at call_COMMAND_RPC_FORCE_RELAY_RAW_TXS"); + WLT_CHECK_AND_ASSERT_MES(res.status == API_RETURN_CODE_OK, void(), "wrong result at call_COMMAND_RPC_FORCE_RELAY_RAW_TXS: status != OK, status=" << res.status); + + WLT_LOG_GREEN("Relayed " << req.txs_as_hex.size() << " txs", LOG_LEVEL_0); + } + //----------------------------------------------------------------------------------------------------- + void wallet2::accept_proposal(const crypto::hash& contract_id, uint64_t b_acceptance_fee, currency::transaction* p_acceptance_tx /* = nullptr */) + { + auto contr_it = m_contracts.find(contract_id); + THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it != m_contracts.end(), "Unknow contract id: " << contract_id); + + THROW_IF_FALSE_WALLET_INT_ERR_EX(!contr_it->second.is_a, "contr_it->second.is_a supposed to be false, but it is " << contr_it->second.is_a); + THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it->second.state == tools::wallet_public::escrow_contract_details_basic::proposal_sent, "contr_it->second.state supposed to be proposal_sent(" << tools::wallet_public::escrow_contract_details_basic::proposal_sent << ") but it is: " << tools::wallet_public::get_escrow_contract_state_name(contr_it->second.state)); + + construct_tx_param construct_param = AUTO_VAL_INIT(construct_param); + construct_param.fee = b_acceptance_fee; + mode_separate_context msc = AUTO_VAL_INIT(msc); + msc.escrow = true; + msc.tx_for_mode_separate = contr_it->second.proposal.tx_template; + currency::transaction& tx = msc.tx_for_mode_separate; + 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 = get_current_split_strategy(); + + //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(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 = tx; + pwallet_info->m_block_height = 0; + pwallet_info->m_block_timestamp = 0; + tdb.m_ptx_wallet_info = pwallet_info; + tdb.m_internal_output_index = n; + tdb.m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_SPENT); + //--------------------------------- + //@#@ todo: proper handling with zarcanum_based stuff + //figure out fee that was left for release contract + THROW_IF_FALSE_WALLET_INT_ERR_EX(tx.vout[n].type() == typeid(tx_out_bare), "Unexpected output type in accept proposal"); + THROW_IF_FALSE_WALLET_INT_ERR_EX(boost::get(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 = boost::get(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); + + //prepare templates to let buyer release or burn escrow + bc_services::escrow_relese_templates_body artb = AUTO_VAL_INIT(artb); + build_escrow_release_templates(contract_id, + left_for_fee_in_multisig, + artb.tx_normal_template, + artb.tx_burn_template, + contr_it->second.private_detailes); + + + //---second part of bad design --- + auto it = m_multisig_transfers.find(contract_id); + THROW_IF_FALSE_WALLET_EX(it != m_multisig_transfers.end(), error::wallet_internal_error, "Internal error"); + m_multisig_transfers.erase(it); + //--------------------------------- + + tx_service_attachment tsa = AUTO_VAL_INIT(tsa); + bc_services::pack_attachment_as_gzipped_bin(artb, tsa); + tsa.service_id = BC_ESCROW_SERVICE_ID; + tsa.instruction = BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_TEMPLATES; + tsa.flags |= TX_SERVICE_ATTACHMENT_ENCRYPT_BODY; + construct_param.extra.push_back(tsa); + + //build transaction + currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); + ftp.tx_version = this->get_current_tx_version(); + prepare_transaction(construct_param, ftp, msc); + 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)) + ">"); + + try + { + finalize_transaction(ftp, tx, one_time_key, true); + } + catch (...) + { + clear_transfers_from_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, std::string("exception in finalize_transaction, tx: ") + epee::string_tools::pod_to_hex(get_transaction_hash(tx))); + throw; + } + + 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 = tx; + } + //--------------------------------------------------------------------------------- + uint64_t wallet2::get_current_tx_version() + { + uint64_t tx_expected_block_height = get_top_block_height() + 1; + return currency::get_tx_version(tx_expected_block_height, this->m_core_runtime_config.hard_forks); + } + //--------------------------------------------------------------------------------- + void wallet2::finish_contract(const crypto::hash& contract_id, const std::string& release_type, currency::transaction* p_release_tx /* = nullptr */) + { + auto contr_it = m_contracts.find(contract_id); + THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it != m_contracts.end(), "Unknow contract id: " << contract_id); + + THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it->second.is_a, "contr_it->second.is_a is supposed to be true, but it is " << contr_it->second.is_a); + THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it->second.state == tools::wallet_public::escrow_contract_details_basic::contract_accepted + || contr_it->second.state == tools::wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent, + "incorrect contract state at finish_contract(): (" << contr_it->second.state << "), expected states: contract_accepted (" << tools::wallet_public::escrow_contract_details_basic::contract_accepted << "), " << + "contract_cancel_proposal_sent (" << tools::wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent << ")"); + + auto multisig_it = m_multisig_transfers.find(contract_id); + THROW_IF_FALSE_WALLET_INT_ERR_EX(multisig_it != m_multisig_transfers.end(), "Unknow multisig id: " << contract_id); + + transaction tx = AUTO_VAL_INIT(tx); + if (release_type == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_NORMAL) + { + tx = contr_it->second.release_body.tx_normal_template; + } + else if (release_type == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_BURN) + { + tx = contr_it->second.release_body.tx_burn_template; + } + else + { + THROW_IF_FALSE_WALLET_INT_ERR_EX(false, "Unknow release_type = " << release_type); + } + + bool is_input_fully_signed = false; + bool r = sign_multisig_input_in_tx(tx, 0, m_account.get_keys(), multisig_it->second.m_ptx_wallet_info->m_tx, &is_input_fully_signed); + THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "sign_multisig_input_in_tx failed"); + // There's a two-party multisig input in tx and B-party had to already sign it upon template creation, so adding A-party sign here should make the tx fully signed. Make sure it does: + THROW_IF_FALSE_WALLET_INT_ERR_EX(is_input_fully_signed, "sign_multisig_input_in_tx returned is_input_fully_signed == false"); + + send_transaction_to_network(tx); + + if (p_release_tx != nullptr) + *p_release_tx = tx; + } + //----------------------------------------------------------------------------------------------------- + void wallet2::accept_cancel_contract(const crypto::hash& contract_id, currency::transaction* p_cancellation_acceptance_tx /* = nullptr */) + { + TIME_MEASURE_START_MS(timing1); + auto contr_it = m_contracts.find(contract_id); + THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it != m_contracts.end(), "Unknow contract id"); + TIME_MEASURE_FINISH_MS(timing1); + + THROW_IF_FALSE_WALLET_INT_ERR_EX(!contr_it->second.is_a, "contr_it->second.is_a is supposed to be false, but it is " << contr_it->second.is_a); + THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it->second.state == tools::wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent, + "incorrect contract state: (" << contr_it->second.state << "), expected state: contract_cancel_proposal_sent (" << tools::wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent << ")"); + + TIME_MEASURE_START_MS(timing2); + auto multisig_it = m_multisig_transfers.find(contract_id); + THROW_IF_FALSE_WALLET_INT_ERR_EX(multisig_it != m_multisig_transfers.end(), "Can't find multisig transfer by id: " << contract_id); + TIME_MEASURE_FINISH_MS(timing2); + + transaction tx = contr_it->second.cancel_body.tx_cancel_template; + + TIME_MEASURE_START_MS(timing3); + bool is_input_fully_signed = false; + bool r = sign_multisig_input_in_tx(tx, 0, m_account.get_keys(), multisig_it->second.m_ptx_wallet_info->m_tx, &is_input_fully_signed); + THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "sign_multisig_input_in_tx failed"); + // There's a two-party multisig input in tx and A-party had to already sign it upon template creation, so adding B-party sign here should make the tx fully signed. Make sure it does: + THROW_IF_FALSE_WALLET_INT_ERR_EX(is_input_fully_signed, "sign_multisig_input_in_tx returned is_input_fully_signed == false"); + + send_transaction_to_network(tx); + TIME_MEASURE_FINISH_MS(timing3); + if (timing1 + timing2 + timing1 > 500) + WLT_LOG_RED("[wallet2::accept_cancel_contract] LOW PERFORMANCE: " << timing1 << "," << timing2 << "," << timing1, LOG_LEVEL_0); + + if (p_cancellation_acceptance_tx != nullptr) + *p_cancellation_acceptance_tx = tx; + } + //----------------------------------------------------------------------------------------------------- + void wallet2::request_cancel_contract(const crypto::hash& contract_id, uint64_t fee, uint64_t expiration_period, currency::transaction* p_cancellation_proposal_tx /* = nullptr */) + { + auto contr_it = m_contracts.find(contract_id); + THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it != m_contracts.end(), "Unknow contract id: " << contract_id); + + THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it->second.is_a, "contr_it->second.is_a supposed to be true at request_cancel_contract"); + THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it->second.state == tools::wallet_public::escrow_contract_details_basic::contract_accepted + || contr_it->second.state == tools::wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent, + "incorrect contract state at request_cancel_contract(): " << tools::wallet_public::get_escrow_contract_state_name(contr_it->second.state) << ", expected states: contract_accepted (" << tools::wallet_public::escrow_contract_details_basic::contract_accepted << "), " << + "contract_cancel_proposal_sent (" << tools::wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent << ")"); + + auto multisig_it = m_multisig_transfers.find(contract_id); + THROW_IF_FALSE_WALLET_INT_ERR_EX(multisig_it != m_multisig_transfers.end(), "Unknow multisig id: " << contract_id); + + + ////////////////////////////////////////////////////////////////////////// + construct_tx_param construct_param = AUTO_VAL_INIT(construct_param); + construct_param.fee = fee; + + //------- + //prepare templates to let seller cancel escrow + bc_services::escrow_cancel_templates_body ectb = AUTO_VAL_INIT(ectb); + build_escrow_cancel_template(contract_id, + expiration_period, + ectb.tx_cancel_template, + contr_it->second.private_detailes); + + tx_service_attachment tsa = AUTO_VAL_INIT(tsa); + bc_services::pack_attachment_as_gzipped_bin(ectb, tsa); + tsa.service_id = BC_ESCROW_SERVICE_ID; + tsa.instruction = BC_ESCROW_SERVICE_INSTRUCTION_CANCEL_PROPOSAL; + 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 = get_current_split_strategy(); + + currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); + ftp.tx_version = this->get_current_tx_version(); + prepare_transaction(construct_param, ftp); + currency::transaction tx = AUTO_VAL_INIT(tx); + crypto::secret_key sk = AUTO_VAL_INIT(sk); + 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)) + ">"); + + try + { + finalize_transaction(ftp, tx, sk, true); + } + catch (...) + { + clear_transfers_from_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, std::string("exception in finalize_transaction, tx: ") + epee::string_tools::pod_to_hex(get_transaction_hash(tx))); + throw; + } + + print_tx_sent_message(tx, "(transport for cancel proposal)", fee); + + if (p_cancellation_proposal_tx != nullptr) + *p_cancellation_proposal_tx = tx; + } + //----------------------------------------------------------------------------------------------------- + void wallet2::scan_tx_to_key_inputs(std::vector& found_transfers, const currency::transaction& tx) + { + for (auto& in : tx.vin) + { + if (in.type() == typeid(currency::txin_to_key)) + { + + auto it = m_key_images.find(boost::get(in).k_image); + if (it != m_key_images.end()) + found_transfers.push_back(it->second); + } + } + } + //----------------------------------------------------------------------------------------------------- + void wallet2::change_contract_state(wallet_public::escrow_contract_details_basic& contract, uint32_t new_state, const crypto::hash& contract_id, const wallet_public::wallet_transfer_info& wti) const + { + WLT_LOG_YELLOW("escrow contract STATE CHANGE (" << (contract.is_a ? "A," : "B,") << contract_id << " via tx " << get_transaction_hash(wti.tx) << ", height: " << wti.height << ") : " + << wallet_public::get_escrow_contract_state_name(contract.state) << " -> " << wallet_public::get_escrow_contract_state_name(new_state), LOG_LEVEL_1); + + contract.state = new_state; + contract.height = wti.height; // update height of last state change + } + //----------------------------------------------------------------------------------------------------- + void wallet2::change_contract_state(wallet_public::escrow_contract_details_basic& contract, uint32_t new_state, const crypto::hash& contract_id, const std::string& reason /*= "internal intention"*/) const + { + WLT_LOG_YELLOW("escrow contract STATE CHANGE (" << (contract.is_a ? "A," : "B,") << contract_id << " " << reason << ") : " + << wallet_public::get_escrow_contract_state_name(contract.state) << " -> " << wallet_public::get_escrow_contract_state_name(new_state), LOG_LEVEL_1); + + contract.state = new_state; + } + //----------------------------------------------------------------------------------------------------- + void from_outs_to_received_items(const std::vector& outs, std::vector& received, const currency::transaction& tx) + { + for (const auto& item : outs) + { + if (!out_is_multisig(tx.vout[item.index])) + received.push_back(tools::payment_details_subtransfer{ item.asset_id, item.amount }); + } + } + //----------------------------------------------------------------------------------------------------- + bool wallet2::handle_proposal(wallet_public::wallet_transfer_info& wti, const bc_services::proposal_body& prop) + { + PROFILE_FUNC("wallet2::handle_proposal"); + crypto::hash ms_id = AUTO_VAL_INIT(ms_id); + bc_services::contract_private_details cpd = AUTO_VAL_INIT(cpd); + std::vector decrypted_items; + if (!validate_escrow_proposal(wti, prop, decrypted_items, ms_id, cpd)) + return false; + + wallet_public::escrow_contract_details_basic& ed = epee::misc_utils::get_or_insert_value_initialized(m_contracts, ms_id); + ed.expiration_time = currency::get_tx_expiration_time(prop.tx_template); + ed.timestamp = wti.timestamp; + ed.is_a = cpd.a_addr.spend_public_key == m_account.get_keys().account_address.spend_public_key; + change_contract_state(ed, wallet_public::escrow_contract_details_basic::proposal_sent, ms_id, wti); + ed.private_detailes = cpd; + currency::get_payment_id_from_decrypted_container(decrypted_items, ed.payment_id); + ed.proposal = prop; + ed.height = wti.height; + wti.contract.resize(1); + static_cast(wti.contract.back()) = ed; + wti.contract.back().contract_id = ms_id; + + //correct fee in case if it "B", cz fee is paid by "A" + if (!ed.is_a) + wti.fee = 0; + else + { + //if it's A' then mark proposal template's inputs as spent and add to expiration list + std::vector found_transfers; + scan_tx_to_key_inputs(found_transfers, prop.tx_template); + //scan outputs to figure out amount of change in escrow + crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); + std::vector outs; + bool r = lookup_acc_outs(m_account.get_keys(), prop.tx_template, outs, derivation); + THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "Failed to lookup_acc_outs for tx: " << get_transaction_hash(prop.tx_template)); + std::vector received; + from_outs_to_received_items(outs, received, prop.tx_template); + add_transfers_to_expiration_list(found_transfers, received, ed.expiration_time, wti.tx_hash); + WLT_LOG_GREEN("Locked " << found_transfers.size() << " transfers due to proposal " << ms_id, LOG_LEVEL_0); + } + + + return true; + } + //----------------------------------------------------------------------------------------------------- + bool wallet2::handle_release_contract(wallet_public::wallet_transfer_info& wti, const std::string& release_instruction) + { + PROFILE_FUNC("wallet2::handle_release_contract"); + size_t n = get_multisig_in_index(wti.tx.vin); + WLT_CHECK_AND_ASSERT_MES(n != wti.tx.vin.size(), false, "Multisig out not found in tx template in proposal"); + crypto::hash ms_id = boost::get(wti.tx.vin[n]).multisig_out_id; + WLT_CHECK_AND_ASSERT_MES(ms_id != null_hash, false, "Multisig out not found in tx template in proposal"); + auto it = m_contracts.find(ms_id); + WLT_CHECK_AND_ASSERT_MES(it != m_contracts.end(), false, "Multisig out not found in tx template in proposal"); + + if (release_instruction == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_NORMAL) + change_contract_state(it->second, wallet_public::escrow_contract_details_basic::contract_released_normal, ms_id, wti); + else if (release_instruction == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_CANCEL) + change_contract_state(it->second, wallet_public::escrow_contract_details_basic::contract_released_cancelled, ms_id, wti); + else if (release_instruction == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_BURN) + { + change_contract_state(it->second, wallet_public::escrow_contract_details_basic::contract_released_burned, ms_id, wti); + wallet_public::wallet_sub_transfer_info* subptr = nullptr; + for (auto& s : wti.subtransfers) + { + if (s.asset_id == currency::native_coin_asset_id) + subptr = &s; + } + if (subptr == nullptr) + { + wti.subtransfers.push_back(wallet_public::wallet_sub_transfer_info()); + subptr = &wti.subtransfers.back(); + } + + subptr->amount = it->second.private_detailes.amount_to_pay + it->second.private_detailes.amount_a_pledge + it->second.private_detailes.amount_b_pledge; + if (!it->second.is_a) + { + wti.fee = currency::get_tx_fee(wti.tx); + } + else + { + wti.fee = 0; + } + } + else + { + WLT_LOG_ERROR("wrong release_instruction: " << release_instruction); + return false; + } + + wti.contract.resize(1); + static_cast(wti.contract.back()) = it->second; + wti.contract.back().contract_id = ms_id; + + //if it's A(buyer) then fee paid by B(seller) + if (it->second.is_a) { wti.fee = 0; } - } - else - { - WLT_LOG_ERROR("wrong release_instruction: " << release_instruction); - return false; - } - - wti.contract.resize(1); - static_cast(wti.contract.back()) = it->second; - wti.contract.back().contract_id = ms_id; - - //if it's A(buyer) then fee paid by B(seller) - if (it->second.is_a) - { - wti.fee = 0; - } - return true; -} - -//----------------------------------------------------------------------------------------------------- -bool wallet2::handle_contract(wallet_public::wallet_transfer_info& wti, const bc_services::contract_private_details& cntr, const std::vector& decrypted_attach) -{ - PROFILE_FUNC("wallet2::handle_contract"); - bool is_a = cntr.a_addr == m_account.get_public_address(); - crypto::hash ms_id = AUTO_VAL_INIT(ms_id); - bc_services::escrow_relese_templates_body rel = AUTO_VAL_INIT(rel); - if (!validate_escrow_contract(wti, cntr, is_a, decrypted_attach, ms_id, rel)) - return false; - - wallet_public::escrow_contract_details_basic& ed = epee::misc_utils::get_or_insert_value_initialized(m_contracts, ms_id); - ed.is_a = is_a; - ed.expiration_time = currency::get_tx_expiration_time(wti.tx); - if (wti.timestamp) - ed.timestamp = wti.timestamp; - ed.height = wti.height; - ed.payment_id = wti.payment_id; - change_contract_state(ed, wallet_public::escrow_contract_details_basic::contract_accepted, ms_id, wti); - ed.private_detailes = cntr; - ed.release_body = rel; - - wti.contract.resize(1); - static_cast(wti.contract.back()) = ed; - wti.contract.back().contract_id = ms_id; - - //fee workaround: in consolidating transactions impossible no figure out which part of participants paid fee for tx, so we correct it - //in code which know escrow protocol, and we know that fee paid by B(seller) - if (ed.is_a) - { - WLT_CHECK_AND_ASSERT_MES(wti.subtransfers.size(), false, "Unexpected subtransfers size"); //TODO: subject for refactoring - wti.subtransfers.back().amount += wti.fee; - wti.fee = 0; - } - - - return true; -} -//----------------------------------------------------------------------------------------------------- -bool wallet2::handle_cancel_proposal(wallet_public::wallet_transfer_info& wti, const bc_services::escrow_cancel_templates_body& ectb, const std::vector& decrypted_attach) -{ - PROFILE_FUNC("wallet2::handle_cancel_proposal"); - //validate cancel proposal - WLT_CHECK_AND_ASSERT_MES(ectb.tx_cancel_template.vin.size() && ectb.tx_cancel_template.vin[0].type() == typeid(currency::txin_multisig), false, "Wrong cancel ecrow proposal"); - crypto::hash contract_id = boost::get(ectb.tx_cancel_template.vin[0]).multisig_out_id; - auto it = m_contracts.find(contract_id); - WLT_CHECK_AND_ASSERT_MES(it != m_contracts.end(), false, "Multisig out not found in tx template in proposal"); - - bool r = validate_escrow_cancel_proposal(wti, ectb, decrypted_attach, contract_id, it->second.private_detailes, it->second.proposal.tx_template); - WLT_CHECK_AND_ASSERT_MES(r, false, "failed to validate escrow cancel request, contract id: " << contract_id); - - uint32_t contract_state = it->second.state; - switch (contract_state) - { - case wallet_public::escrow_contract_details::contract_accepted: - change_contract_state(it->second, wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent, contract_id, wti); BOOST_FALLTHROUGH; - // pass through - case wallet_public::escrow_contract_details::contract_cancel_proposal_sent: // update contract info even if already in that state - it->second.cancel_body.tx_cancel_template = ectb.tx_cancel_template; - it->second.cancel_expiration_time = currency::get_tx_expiration_time(ectb.tx_cancel_template); - //update wti info to let GUI know - wti.contract.resize(1); - static_cast(wti.contract.back()) = it->second; - wti.contract.back().contract_id = contract_id; return true; - default: - WLT_LOG_RED("handle_cancel_proposal for contract (" << (it->second.is_a ? "A," : "B,") << contract_id << " via tx " << get_transaction_hash(wti.tx) << ", height: " << wti.height << ") : " << ENDL << - "incorrect state " << wallet_public::get_escrow_contract_state_name(it->second.state) << ", while 'contract_accepted' or 'contract_cancel_proposal_sent' was expected -- decline cancel proposal", LOG_LEVEL_1); } - - return false; -} -//----------------------------------------------------------------------------------------------------- -bool wallet2::process_contract_info(wallet_public::wallet_transfer_info& wti, const std::vector& decrypted_attach) -{ - PROFILE_FUNC("wallet2::process_contract_info"); - for (const auto& v : decrypted_attach) + + //----------------------------------------------------------------------------------------------------- + bool wallet2::handle_contract(wallet_public::wallet_transfer_info& wti, const bc_services::contract_private_details& cntr, const std::vector& decrypted_attach) { - if (v.type() == typeid(tx_service_attachment)) + PROFILE_FUNC("wallet2::handle_contract"); + bool is_a = cntr.a_addr == m_account.get_public_address(); + crypto::hash ms_id = AUTO_VAL_INIT(ms_id); + bc_services::escrow_relese_templates_body rel = AUTO_VAL_INIT(rel); + if (!validate_escrow_contract(wti, cntr, is_a, decrypted_attach, ms_id, rel)) + return false; + + wallet_public::escrow_contract_details_basic& ed = epee::misc_utils::get_or_insert_value_initialized(m_contracts, ms_id); + ed.is_a = is_a; + ed.expiration_time = currency::get_tx_expiration_time(wti.tx); + if (wti.timestamp) + ed.timestamp = wti.timestamp; + ed.height = wti.height; + ed.payment_id = wti.payment_id; + change_contract_state(ed, wallet_public::escrow_contract_details_basic::contract_accepted, ms_id, wti); + ed.private_detailes = cntr; + ed.release_body = rel; + + wti.contract.resize(1); + static_cast(wti.contract.back()) = ed; + wti.contract.back().contract_id = ms_id; + + //fee workaround: in consolidating transactions impossible no figure out which part of participants paid fee for tx, so we correct it + //in code which know escrow protocol, and we know that fee paid by B(seller) + if (ed.is_a) { - const tx_service_attachment& sa = boost::get(v); - if (sa.service_id == BC_ESCROW_SERVICE_ID) + WLT_CHECK_AND_ASSERT_MES(wti.subtransfers.size(), false, "Unexpected subtransfers size"); //TODO: subject for refactoring + wti.subtransfers.back().amount += wti.fee; + wti.fee = 0; + } + + + return true; + } + //----------------------------------------------------------------------------------------------------- + bool wallet2::handle_cancel_proposal(wallet_public::wallet_transfer_info& wti, const bc_services::escrow_cancel_templates_body& ectb, const std::vector& decrypted_attach) + { + PROFILE_FUNC("wallet2::handle_cancel_proposal"); + //validate cancel proposal + WLT_CHECK_AND_ASSERT_MES(ectb.tx_cancel_template.vin.size() && ectb.tx_cancel_template.vin[0].type() == typeid(currency::txin_multisig), false, "Wrong cancel ecrow proposal"); + crypto::hash contract_id = boost::get(ectb.tx_cancel_template.vin[0]).multisig_out_id; + auto it = m_contracts.find(contract_id); + WLT_CHECK_AND_ASSERT_MES(it != m_contracts.end(), false, "Multisig out not found in tx template in proposal"); + + bool r = validate_escrow_cancel_proposal(wti, ectb, decrypted_attach, contract_id, it->second.private_detailes, it->second.proposal.tx_template); + WLT_CHECK_AND_ASSERT_MES(r, false, "failed to validate escrow cancel request, contract id: " << contract_id); + + uint32_t contract_state = it->second.state; + switch (contract_state) + { + case wallet_public::escrow_contract_details::contract_accepted: + change_contract_state(it->second, wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent, contract_id, wti); BOOST_FALLTHROUGH; + // pass through + case wallet_public::escrow_contract_details::contract_cancel_proposal_sent: // update contract info even if already in that state + it->second.cancel_body.tx_cancel_template = ectb.tx_cancel_template; + it->second.cancel_expiration_time = currency::get_tx_expiration_time(ectb.tx_cancel_template); + //update wti info to let GUI know + wti.contract.resize(1); + static_cast(wti.contract.back()) = it->second; + wti.contract.back().contract_id = contract_id; + return true; + default: + WLT_LOG_RED("handle_cancel_proposal for contract (" << (it->second.is_a ? "A," : "B,") << contract_id << " via tx " << get_transaction_hash(wti.tx) << ", height: " << wti.height << ") : " << ENDL << + "incorrect state " << wallet_public::get_escrow_contract_state_name(it->second.state) << ", while 'contract_accepted' or 'contract_cancel_proposal_sent' was expected -- decline cancel proposal", LOG_LEVEL_1); + } + + return false; + } + //----------------------------------------------------------------------------------------------------- + bool wallet2::process_contract_info(wallet_public::wallet_transfer_info& wti, const std::vector& decrypted_attach) + { + PROFILE_FUNC("wallet2::process_contract_info"); + for (const auto& v : decrypted_attach) + { + if (v.type() == typeid(tx_service_attachment)) { - if (sa.instruction == BC_ESCROW_SERVICE_INSTRUCTION_PROPOSAL) + const tx_service_attachment& sa = boost::get(v); + if (sa.service_id == BC_ESCROW_SERVICE_ID) { - bc_services::proposal_body prop = AUTO_VAL_INIT(prop); - if (!t_unserializable_object_from_blob(prop, sa.body)) + if (sa.instruction == BC_ESCROW_SERVICE_INSTRUCTION_PROPOSAL) { - WLT_LOG_ERROR("Failed to unpack attachment for tx: " << wti.tx_hash); - return false; + bc_services::proposal_body prop = AUTO_VAL_INIT(prop); + if (!t_unserializable_object_from_blob(prop, sa.body)) + { + WLT_LOG_ERROR("Failed to unpack attachment for tx: " << wti.tx_hash); + return false; + } + handle_proposal(wti, prop); } - handle_proposal(wti, prop); - } - else if (sa.instruction == BC_ESCROW_SERVICE_INSTRUCTION_PRIVATE_DETAILS) - { - //means some related contract appeared in blockchain - bc_services::contract_private_details cntr = AUTO_VAL_INIT(cntr); - if (!epee::serialization::load_t_from_json(cntr, sa.body)) + else if (sa.instruction == BC_ESCROW_SERVICE_INSTRUCTION_PRIVATE_DETAILS) { - WLT_LOG_ERROR("Failed to unpack attachment for tx: " << wti.tx_hash); - return false; - } - handle_contract(wti, cntr, decrypted_attach); + //means some related contract appeared in blockchain + bc_services::contract_private_details cntr = AUTO_VAL_INIT(cntr); + if (!epee::serialization::load_t_from_json(cntr, sa.body)) + { + WLT_LOG_ERROR("Failed to unpack attachment for tx: " << wti.tx_hash); + return false; + } + handle_contract(wti, cntr, decrypted_attach); - } - else if ( - sa.instruction == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_NORMAL || - sa.instruction == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_CANCEL || - sa.instruction == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_BURN - ) - { - handle_release_contract(wti, sa.instruction); - } - else if (sa.instruction == BC_ESCROW_SERVICE_INSTRUCTION_CANCEL_PROPOSAL) - { - //means some related contract appeared in blockchain - bc_services::escrow_cancel_templates_body cpb = AUTO_VAL_INIT(cpb); - if (!t_unserializable_object_from_blob(cpb, sa.body)) - { - WLT_LOG_ERROR("Failed to unpack attachment for tx: " << wti.tx_hash); - return false; } - handle_cancel_proposal(wti, cpb, decrypted_attach); + else if ( + sa.instruction == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_NORMAL || + sa.instruction == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_CANCEL || + sa.instruction == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_BURN + ) + { + handle_release_contract(wti, sa.instruction); + } + else if (sa.instruction == BC_ESCROW_SERVICE_INSTRUCTION_CANCEL_PROPOSAL) + { + //means some related contract appeared in blockchain + bc_services::escrow_cancel_templates_body cpb = AUTO_VAL_INIT(cpb); + if (!t_unserializable_object_from_blob(cpb, sa.body)) + { + WLT_LOG_ERROR("Failed to unpack attachment for tx: " << wti.tx_hash); + return false; + } + handle_cancel_proposal(wti, cpb, decrypted_attach); + } } } } + + return true; } - - return true; -} -//----------------------------------------------------------------------------------------------------- -void wallet2::prepare_wti(wallet_public::wallet_transfer_info& wti, const process_transaction_context& tx_process_context) -{ - PROFILE_FUNC("wallet2::prepare_wti"); - wti.tx = tx_process_context.tx; - - load_wti_from_process_transaction_context(wti, tx_process_context); - - wti.height = tx_process_context.height; - wti.employed_entries = tx_process_context.employed_entries; - wti.unlock_time = get_max_unlock_time_from_receive_indices(tx_process_context.tx, tx_process_context.employed_entries); - wti.timestamp = tx_process_context.timestamp; - wti.tx_blob_size = static_cast(currency::get_object_blobsize(wti.tx)); - wti.tx_hash = tx_process_context.tx_hash(); - load_wallet_transfer_info_flags(wti); - bc_services::extract_market_instructions(wti.marketplace_entries, wti.tx.attachment); - - // escrow transactions, which are built with TX_FLAG_SIGNATURE_MODE_SEPARATE flag actually encrypt attachments - // with buyer as a sender, and seller as receiver, despite the fact that for both sides transaction seen as outgoing. - // so here to decrypt tx properly we need to figure out, if this transaction is actually escrow acceptance. - //we check if spent_indices have zero then input do not belong to this account, which means that we are seller for this - //escrow, and decryption should be processed as income flag - - //let's assume that the one who pays for tx fee is sender of tx - bool decrypt_attachment_as_income = !(tx_process_context.total_balance_change.count(currency::native_coin_asset_id) && tx_process_context.total_balance_change.at(currency::native_coin_asset_id) < 0 ); - std::vector decrypted_att; - bool has_zero_input_as_spent = false; - for (const auto& item : tx_process_context.employed_entries.spent) + //----------------------------------------------------------------------------------------------------- + void wallet2::prepare_wti(wallet_public::wallet_transfer_info& wti, const process_transaction_context& tx_process_context) { - if (item.index == 0) + PROFILE_FUNC("wallet2::prepare_wti"); + wti.tx = tx_process_context.tx; + + load_wti_from_process_transaction_context(wti, tx_process_context); + + wti.height = tx_process_context.height; + wti.employed_entries = tx_process_context.employed_entries; + wti.unlock_time = get_max_unlock_time_from_receive_indices(tx_process_context.tx, tx_process_context.employed_entries); + wti.timestamp = tx_process_context.timestamp; + wti.tx_blob_size = static_cast(currency::get_object_blobsize(wti.tx)); + wti.tx_hash = tx_process_context.tx_hash(); + load_wallet_transfer_info_flags(wti); + bc_services::extract_market_instructions(wti.marketplace_entries, wti.tx.attachment); + + // escrow transactions, which are built with TX_FLAG_SIGNATURE_MODE_SEPARATE flag actually encrypt attachments + // with buyer as a sender, and seller as receiver, despite the fact that for both sides transaction seen as outgoing. + // so here to decrypt tx properly we need to figure out, if this transaction is actually escrow acceptance. + //we check if spent_indices have zero then input do not belong to this account, which means that we are seller for this + //escrow, and decryption should be processed as income flag + + //let's assume that the one who pays for tx fee is sender of tx + bool decrypt_attachment_as_income = !(tx_process_context.total_balance_change.count(currency::native_coin_asset_id) && tx_process_context.total_balance_change.at(currency::native_coin_asset_id) < 0); + std::vector decrypted_att; + bool has_zero_input_as_spent = false; + for (const auto& item : tx_process_context.employed_entries.spent) { - has_zero_input_as_spent = true; - break; - } - } - - if (wti.tx_type == GUI_TX_TYPE_ESCROW_TRANSFER && !has_zero_input_as_spent) - decrypt_attachment_as_income = true; - - - decrypt_payload_items(decrypt_attachment_as_income, wti.tx, m_account.get_keys(), decrypted_att); - if ((is_watch_only() && !decrypt_attachment_as_income)|| (wti.height > 638000 && !have_type_in_variant_container(decrypted_att))) - { - remove_field_of_type_from_extra(decrypted_att); - remove_field_of_type_from_extra(decrypted_att); - } - if (is_watch_only() && !decrypt_attachment_as_income) - { - remove_field_of_type_from_extra(decrypted_att); - } - prepare_wti_decrypted_attachments(wti, decrypted_att); - process_contract_info(wti, decrypted_att); - process_payment_id_for_wti(wti, tx_process_context); - -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::process_payment_id_for_wti(wallet_public::wallet_transfer_info& wti, const process_transaction_context& ptc) -{ - //if(this->is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM)) - { - if (wti.get_native_is_income() && wti.payment_id.size()) - { - payment_details payment; - payment.m_tx_hash = wti.tx_hash; - payment.m_amount = 0; - payment.m_block_height = wti.height; - payment.m_unlock_time = ptc.max_out_unlock_time; - - for (const auto& bce : ptc.total_balance_change) + if (item.index == 0) { - if (bce.second > 0) + has_zero_input_as_spent = true; + break; + } + } + + if (wti.tx_type == GUI_TX_TYPE_ESCROW_TRANSFER && !has_zero_input_as_spent) + decrypt_attachment_as_income = true; + + + decrypt_payload_items(decrypt_attachment_as_income, wti.tx, m_account.get_keys(), decrypted_att); + if ((is_watch_only() && !decrypt_attachment_as_income) || (wti.height > 638000 && !have_type_in_variant_container(decrypted_att))) + { + remove_field_of_type_from_extra(decrypted_att); + remove_field_of_type_from_extra(decrypted_att); + } + if (is_watch_only() && !decrypt_attachment_as_income) + { + remove_field_of_type_from_extra(decrypted_att); + } + prepare_wti_decrypted_attachments(wti, decrypted_att); + process_contract_info(wti, decrypted_att); + process_payment_id_for_wti(wti, tx_process_context); + + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::process_payment_id_for_wti(wallet_public::wallet_transfer_info& wti, const process_transaction_context& ptc) + { + //if(this->is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM)) + { + if (wti.get_native_is_income() && wti.payment_id.size()) + { + payment_details payment; + payment.m_tx_hash = wti.tx_hash; + payment.m_amount = 0; + payment.m_block_height = wti.height; + payment.m_unlock_time = ptc.max_out_unlock_time; + + for (const auto& bce : ptc.total_balance_change) { - if (bce.first == currency::native_coin_asset_id) + if (bce.second > 0) { - payment.m_amount = static_cast(bce.second); - } - else - { - payment.subtransfers.push_back(payment_details_subtransfer{ bce.first, static_cast(bce.second) }); + if (bce.first == currency::native_coin_asset_id) + { + payment.m_amount = static_cast(bce.second); + } + else + { + payment.subtransfers.push_back(payment_details_subtransfer{ bce.first, static_cast(bce.second) }); + } } } + m_payments.emplace(wti.payment_id, payment); + WLT_LOG_L2("Payment found, id (hex): " << epee::string_tools::buff_to_hex_nodelimer(wti.payment_id) << ", tx: " << payment.m_tx_hash << ", amount: " << print_money_brief(payment.m_amount) << "subtransfers = " << payment.subtransfers.size()); } - m_payments.emplace(wti.payment_id, payment); - WLT_LOG_L2("Payment found, id (hex): " << epee::string_tools::buff_to_hex_nodelimer(wti.payment_id) << ", tx: " << payment.m_tx_hash << ", amount: " << print_money_brief(payment.m_amount) << "subtransfers = " << payment.subtransfers.size()); } + return true; } - return true; -} -void wallet2::rise_on_transfer2(const wallet_public::wallet_transfer_info& wti) -{ - PROFILE_FUNC("wallet2::rise_on_transfer2"); - if (!m_do_rise_transfer) - return; - std::list balances; - uint64_t mined_balance = 0; - this->balance(balances, mined_balance); - m_wcallback->on_transfer2(wti, balances, mined_balance); - // second call for legacy callback handlers - //m_wcallback->on_transfer2(wti, balances, mined_balance); -} -//---------------------------------------------------------------------------------------------------- -/* -void wallet2::handle_money_spent2(const currency::block& b, - const currency::transaction& in_tx, - uint64_t amount, - const money_transfer2_details& td, - const std::vector& recipients, - const std::vector& remote_aliases) -{ - m_transfer_history.push_back(AUTO_VAL_INIT(wallet_public::wallet_transfer_info())); - wallet_public::wallet_transfer_info& wti = m_transfer_history.back(); - wti.is_income = false; - - wti.remote_addresses = recipients; - wti.remote_aliases = remote_aliases; - prepare_wti(wti, get_block_height(b), get_block_datetime(b), in_tx, amount, td); - WLT_LOG_L1("[MONEY SPENT]: " << epee::serialization::store_t_to_json(wti)); - rise_on_transfer2(wti); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::handle_money_received2(const currency::block& b, const currency::transaction& tx, uint64_t amount, const money_transfer2_details& td) -{ - //decrypt attachments - m_transfer_history.push_back(AUTO_VAL_INIT(wallet_public::wallet_transfer_info())); - wallet_public::wallet_transfer_info& wti = m_transfer_history.back(); - wti.is_income = true; - // TODO @#@# this function is only able to handle native coins atm, consider changing -- sowle - wti.asset_id = native_coin_asset_id; - prepare_wti(wti, get_block_height(b), get_block_datetime(b), tx, amount, td); - WLT_LOG_L1("[MONEY RECEIVED]: " << epee::serialization::store_t_to_json(wti)); - rise_on_transfer2(wti); -} -*/ - //---------------------------------------------------------------------------------------------------- -void wallet2::load_wti_from_process_transaction_context(wallet_public::wallet_transfer_info& wti, const process_transaction_context& tx_process_context) -{ - wti.remote_addresses = tx_process_context.recipients; - wti.remote_aliases = tx_process_context.remote_aliases; - for (const auto& bce : tx_process_context.total_balance_change) + void wallet2::rise_on_transfer2(const wallet_public::wallet_transfer_info& wti) { - wallet_public::wallet_sub_transfer_info wsti = AUTO_VAL_INIT(wsti); - wsti.asset_id = bce.first; - if (bce.second == 0) - { - continue; - } - else if (bce.second > 0) - { - //in transfer - wsti.is_income = true; - wsti.amount = static_cast(bce.second); - } - else - { - //out transfer - wsti.is_income = false; - wsti.amount = static_cast(bce.second * (-1)); - } - wti.subtransfers.push_back(wsti); + PROFILE_FUNC("wallet2::rise_on_transfer2"); + if (!m_do_rise_transfer) + return; + std::list balances; + uint64_t mined_balance = 0; + this->balance(balances, mined_balance); + m_wcallback->on_transfer2(wti, balances, mined_balance); + // second call for legacy callback handlers + //m_wcallback->on_transfer2(wti, balances, mined_balance); } -} -//---------------------------------------------------------------------------------------------------- -void wallet2::handle_money(const currency::block& b, const process_transaction_context& tx_process_context) -{ - m_transfer_history.push_back(AUTO_VAL_INIT(wallet_public::wallet_transfer_info())); - wallet_public::wallet_transfer_info& wti = m_transfer_history.back(); - prepare_wti(wti, tx_process_context); - WLT_LOG_L1("[MONEY SPENT]: " << epee::serialization::store_t_to_json(wti)); - rise_on_transfer2(wti); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::process_unconfirmed(const currency::transaction& tx, std::vector& recipients, std::vector& remote_aliases) -{ - auto unconf_it = m_unconfirmed_txs.find(get_transaction_hash(tx)); - if (unconf_it != m_unconfirmed_txs.end()) + //---------------------------------------------------------------------------------------------------- + /* + void wallet2::handle_money_spent2(const currency::block& b, + const currency::transaction& in_tx, + uint64_t amount, + const money_transfer2_details& td, + const std::vector& recipients, + const std::vector& remote_aliases) { - wallet_public::wallet_transfer_info& wti = unconf_it->second; - recipients = wti.remote_addresses; - remote_aliases = wti.remote_aliases; + m_transfer_history.push_back(AUTO_VAL_INIT(wallet_public::wallet_transfer_info())); + wallet_public::wallet_transfer_info& wti = m_transfer_history.back(); + wti.is_income = false; - m_unconfirmed_txs.erase(unconf_it); + wti.remote_addresses = recipients; + wti.remote_aliases = remote_aliases; + prepare_wti(wti, get_block_height(b), get_block_datetime(b), in_tx, amount, td); + WLT_LOG_L1("[MONEY SPENT]: " << epee::serialization::store_t_to_json(wti)); + rise_on_transfer2(wti); } -} -//---------------------------------------------------------------------------------------------------- - -void wallet2::unprocess_htlc_triggers_on_block_removed(uint64_t height) -{ - if (!m_htlcs.size()) - return; - - if (height > m_htlcs.rbegin()->first) + //---------------------------------------------------------------------------------------------------- + void wallet2::handle_money_received2(const currency::block& b, const currency::transaction& tx, uint64_t amount, const money_transfer2_details& td) { - //there is no active htlc that at this height - CHECK_AND_ASSERT_MES(m_active_htlcs.size() == 0, void(), "Self check failed: m_active_htlcs.size() = " << m_active_htlcs.size()); - return; + //decrypt attachments + m_transfer_history.push_back(AUTO_VAL_INIT(wallet_public::wallet_transfer_info())); + wallet_public::wallet_transfer_info& wti = m_transfer_history.back(); + wti.is_income = true; + // TODO @#@# this function is only able to handle native coins atm, consider changing -- sowle + wti.asset_id = native_coin_asset_id; + prepare_wti(wti, get_block_height(b), get_block_datetime(b), tx, amount, td); + WLT_LOG_L1("[MONEY RECEIVED]: " << epee::serialization::store_t_to_json(wti)); + rise_on_transfer2(wti); } - //we have to check if there is a htlc that has to become deactivated - auto pair_of_it = m_htlcs.equal_range(height); - for (auto it = pair_of_it.first; it != pair_of_it.second; it++) + */ + //---------------------------------------------------------------------------------------------------- + void wallet2::load_wti_from_process_transaction_context(wallet_public::wallet_transfer_info& wti, const process_transaction_context& tx_process_context) { - auto& tr = m_transfers[it->second.transfer_index]; - //found contract that supposed to be re-activated and set to active - if (it->second.is_wallet_owns_redeem) + wti.remote_addresses = tx_process_context.recipients; + wti.remote_aliases = tx_process_context.remote_aliases; + for (const auto& bce : tx_process_context.total_balance_change) { - // this means that wallet received atomic as proposal but never activated it, and now we back to phase where out can be activated - //but we keep spend flag anyway - tr.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; //re assure that it has spent flag - tr.m_spent_height = 0; - } - else - { - // this means that wallet created atomic by itself, and second part didn't redeem it, - // so refund money became available, and now we back again to unavailable state - tr.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; //reset spent flag - m_found_free_amounts.clear(); //reset free amounts cache - tr.m_spent_height = 0; - } - //re-add to active contracts - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(tr.m_ptx_wallet_info->m_tx.vout[tr.m_internal_output_index].type() == typeid(tx_out_bare), std::string("Unexprected type of out in unprocess_htlc_triggers_on_block_removed : ") + tr.m_ptx_wallet_info->m_tx.vout[tr.m_internal_output_index].type().name()); - auto pair_key = std::make_pair(tr.m_amount, tr.m_global_output_index); - auto it_active_htlc = m_active_htlcs.find(pair_key); - if (it_active_htlc != m_active_htlcs.end()) - { - LOG_ERROR("Error at putting back htlc: already exist?"); - it_active_htlc->second = it->second.transfer_index; - - } - else - { - m_active_htlcs[pair_key] = it->second.transfer_index; - } - - const crypto::hash tx_id = tr.tx_hash(); - auto tx_id_it = m_active_htlcs_txid.find(tx_id); - if (tx_id_it != m_active_htlcs_txid.end()) - { - LOG_ERROR("Error at putting back htlc_txid: already exist?"); - tx_id_it->second = it->second.transfer_index; - - } - else - { - m_active_htlcs_txid[tx_id] = it->second.transfer_index; - } - } -} -void wallet2::process_htlc_triggers_on_block_added(uint64_t height) -{ - if (!m_htlcs.size()) - return; - - if (height > m_htlcs.rbegin()->first) - { - //there is no active htlc that at this height - CHECK_AND_ASSERT_MES(m_active_htlcs.size() == 0, void(), "Self check failed: m_active_htlcs.size() = " << m_active_htlcs.size()); - return; - } - //we have to check if there is a htlc that has to become deactivated - auto pair_of_it = m_htlcs.equal_range(height); - for (auto it = pair_of_it.first; it != pair_of_it.second; it++) - { - auto& tr = m_transfers[it->second.transfer_index]; - //found contract that supposed to be deactivated and set to innactive - if (it->second.is_wallet_owns_redeem) - { - // this means that wallet received atomic as proposal but never activated it, money returned to initiator - tr.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; //re assure that it has spent flag - tr.m_spent_height = height; - } - else - { - // this means that wallet created atomic by itself, and second part didn't redeem it, so refund money should become available - tr.m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_SPENT); //reset spent flag - m_found_free_amounts.clear(); //reset free amounts cache - tr.m_spent_height = 0; - } - - //reset cache - m_found_free_amounts.clear(); - - //remove it from active contracts - CHECK_AND_ASSERT_MES(tr.m_ptx_wallet_info->m_tx.vout[tr.m_internal_output_index].type() == typeid(tx_out_bare), void(), "Unexpected type out in process_htlc_triggers_on_block_added: " << tr.m_ptx_wallet_info->m_tx.vout[tr.m_internal_output_index].type().name()); - uint64_t amount = tr.m_amount; - - auto it_active_htlc = m_active_htlcs.find(std::make_pair(amount, tr.m_global_output_index)); - if (it_active_htlc == m_active_htlcs.end()) - { - LOG_ERROR("Erasing active htlc(m_active_htlcs), but it seems to be already erased"); - } - else - { - m_active_htlcs.erase(it_active_htlc); - auto it_tx = m_active_htlcs_txid.find(tr.tx_hash()); - if (it_tx == m_active_htlcs_txid.end()) + wallet_public::wallet_sub_transfer_info wsti = AUTO_VAL_INIT(wsti); + wsti.asset_id = bce.first; + if (bce.second == 0) { - LOG_ERROR("Erasing active htlc(;), but it seems to be already erased"); + continue; + } + else if (bce.second > 0) + { + //in transfer + wsti.is_income = true; + wsti.amount = static_cast(bce.second); } else { - m_active_htlcs_txid.erase(it_tx); + //out transfer + wsti.is_income = false; + wsti.amount = static_cast(bce.second * (-1)); } + wti.subtransfers.push_back(wsti); } } -} -//---------------------------------------------------------------------------------------------------- -void wallet2::process_new_blockchain_entry(const currency::block& b, const currency::block_direct_data_entry& bche, const crypto::hash& bl_id, uint64_t height) -{ - //handle transactions from new block - THROW_IF_TRUE_WALLET_EX(height != get_blockchain_current_size() && - !(height == m_minimum_height || get_blockchain_current_size() <= 1), error::wallet_internal_error, - "current_index=" + std::to_string(height) + ", get_blockchain_current_height()=" + std::to_string(get_blockchain_current_size())); - - - - - //optimization: seeking only for blocks that are not older then the wallet creation time plus 1 day. 1 day is for possible user incorrect time setup - const std::vector* pglobal_index = nullptr; - if (get_block_height(b) > get_wallet_minimum_height()) // b.timestamp + 60 * 60 * 24 > m_account.get_createtime()) + //---------------------------------------------------------------------------------------------------- + void wallet2::handle_money(const currency::block& b, const process_transaction_context& tx_process_context) { - pglobal_index = nullptr; - if (bche.coinbase_ptr.get()) + m_transfer_history.push_back(AUTO_VAL_INIT(wallet_public::wallet_transfer_info())); + wallet_public::wallet_transfer_info& wti = m_transfer_history.back(); + prepare_wti(wti, tx_process_context); + WLT_LOG_L1("[MONEY SPENT]: " << epee::serialization::store_t_to_json(wti)); + rise_on_transfer2(wti); + } + //---------------------------------------------------------------------------------------------------- + void wallet2::process_unconfirmed(const currency::transaction& tx, std::vector& recipients, std::vector& remote_aliases) + { + auto unconf_it = m_unconfirmed_txs.find(get_transaction_hash(tx)); + if (unconf_it != m_unconfirmed_txs.end()) { - pglobal_index = &(bche.coinbase_ptr->m_global_output_indexes); - } - TIME_MEASURE_START(miner_tx_handle_time); - process_new_transaction(b.miner_tx, height, b, pglobal_index); - TIME_MEASURE_FINISH(miner_tx_handle_time); + wallet_public::wallet_transfer_info& wti = unconf_it->second; + recipients = wti.remote_addresses; + remote_aliases = wti.remote_aliases; - TIME_MEASURE_START(txs_handle_time); - size_t count = 0; - for(const auto& tx_entry: bche.txs_ptr) + m_unconfirmed_txs.erase(unconf_it); + } + } + //---------------------------------------------------------------------------------------------------- + + void wallet2::unprocess_htlc_triggers_on_block_removed(uint64_t height) + { + if (!m_htlcs.size()) + return; + + if (height > m_htlcs.rbegin()->first) { - if (b.tx_hashes.size() < count || currency::get_transaction_hash(tx_entry->tx) != b.tx_hashes[count]) + //there is no active htlc that at this height + CHECK_AND_ASSERT_MES(m_active_htlcs.size() == 0, void(), "Self check failed: m_active_htlcs.size() = " << m_active_htlcs.size()); + return; + } + //we have to check if there is a htlc that has to become deactivated + auto pair_of_it = m_htlcs.equal_range(height); + for (auto it = pair_of_it.first; it != pair_of_it.second; it++) + { + auto& tr = m_transfers.at(it->second.transfer_index); + //found contract that supposed to be re-activated and set to active + if (it->second.is_wallet_owns_redeem) { - LOG_ERROR("Found tx order fail in process_new_blockchain_entry: count=" << count - << ", b.tx_hashes.size() = " << b.tx_hashes.size() << ", tx real id: " << currency::get_transaction_hash(tx_entry->tx) << ", bl_id: " << bl_id); - } - process_new_transaction(tx_entry->tx, height, b, &(tx_entry->m_global_output_indexes)); - count++; - } - TIME_MEASURE_FINISH(txs_handle_time); - WLT_LOG_L3("Processed block: " << bl_id << ", height " << height << ", " << miner_tx_handle_time + txs_handle_time << "(" << miner_tx_handle_time << "/" << txs_handle_time <<")ms"); - }else - { - WLT_LOG_L3( "Skipped block by timestamp, height: " << height << ", block time " << b.timestamp << ", account time " << m_account.get_createtime()); - } - m_chain.push_new_block_id(bl_id, height); //m_blockchain.push_back(bl_id); - m_last_bc_timestamp = b.timestamp; - if (!is_pos_block(b)) - m_last_pow_block_h = height; - - - process_htlc_triggers_on_block_added(height); - m_wcallback->on_new_block(height, b); -} -//---------------------------------------------------------------------------------------------------- - -//---------------------------------------------------------------------------------------------------- -// void wallet2::get_short_chain_history(std::list& ids) -// { -// ids.clear(); -// size_t i = 0; -// size_t current_multiplier = 1; -// size_t sz = get_blockchain_current_height(); -// if(!sz) -// return; -// size_t current_back_offset = 1; -// bool genesis_included = false; -// while(current_back_offset < sz) -// { -// ids.push_back(m_blockchain[sz-current_back_offset]); -// if(sz-current_back_offset == 0) -// genesis_included = true; -// if(i < 10) -// { -// ++current_back_offset; -// }else -// { -// current_back_offset += current_multiplier *= 2; -// } -// ++i; -// } -// if(!genesis_included) -// ids.push_back(m_blockchain[0]); -// } - -//---------------------------------------------------------------------------------------------------- -void wallet2::set_minimum_height(uint64_t h) -{ - m_minimum_height = h; -} -//---------------------------------------------------------------------------------------------------- -uint64_t wallet2::get_wallet_minimum_height() -{ - - if (m_minimum_height != WALLET_MINIMUM_HEIGHT_UNSET_CONST) - return m_minimum_height; - - currency::COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE::request req = AUTO_VAL_INIT(req); - currency::COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE::response res = AUTO_VAL_INIT(res); - req.timestamp = m_account.get_createtime(); - bool r = m_core_proxy->call_COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE(req, res); - THROW_IF_FALSE_WALLET_EX(r, error::no_connection_to_daemon, "call_COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE"); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(res.status == API_RETURN_CODE_OK, "FAILED TO CALL COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE"); - m_minimum_height = res.h; - return res.h; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::pull_blocks(size_t& blocks_added, std::atomic& stop) -{ - blocks_added = 0; - currency::COMMAND_RPC_GET_BLOCKS_DIRECT::request req = AUTO_VAL_INIT(req); - currency::COMMAND_RPC_GET_BLOCKS_DIRECT::response res = AUTO_VAL_INIT(res); - - req.minimum_height = get_wallet_minimum_height(); - if (req.minimum_height > m_height_of_start_sync) - m_height_of_start_sync = req.minimum_height; - - m_chain.get_short_chain_history(req.block_ids); - bool r = m_core_proxy->call_COMMAND_RPC_GET_BLOCKS_DIRECT(req, res); - if (!r) - throw error::no_connection_to_daemon(LOCATION_STR, "getblocks.bin"); - if (res.status == API_RETURN_CODE_GENESIS_MISMATCH) - { - WLT_LOG_MAGENTA("Reseting genesis block...", LOG_LEVEL_0); - COMMAND_RPC_GET_BLOCKS_DETAILS::request gbd_req = AUTO_VAL_INIT(gbd_req); - COMMAND_RPC_GET_BLOCKS_DETAILS::response gbd_res = AUTO_VAL_INIT(gbd_res); - gbd_req.height_start = 0; - gbd_req.count = 1; - gbd_req.ignore_transactions = true; - r = m_core_proxy->call_COMMAND_RPC_GET_BLOCKS_DETAILS(gbd_req, gbd_res); - THROW_IF_TRUE_WALLET_EX(!r, error::no_connection_to_daemon, "get_blocks_details"); - THROW_IF_TRUE_WALLET_EX(gbd_res.status != API_RETURN_CODE_OK, error::get_blocks_error, gbd_res.status); - THROW_IF_TRUE_WALLET_EX(gbd_res.blocks.size() == 0, error::get_blocks_error, gbd_res.status); - crypto::hash new_genesis_id = null_hash; - r = string_tools::parse_tpod_from_hex_string(gbd_res.blocks.back().id, new_genesis_id); - THROW_IF_TRUE_WALLET_EX(!r, error::no_connection_to_daemon, "get_blocks_details"); - reset_all(); - m_minimum_height = req.minimum_height; - m_chain.set_genesis(new_genesis_id); - WLT_LOG_MAGENTA("New genesis set for wallet: " << new_genesis_id, LOG_LEVEL_0); - m_chain.get_short_chain_history(req.block_ids); - //req.block_ids.push_back(new_genesis_id); - bool r = m_core_proxy->call_COMMAND_RPC_GET_BLOCKS_DIRECT(req, res); - THROW_IF_TRUE_WALLET_EX(!r, error::no_connection_to_daemon, "getblocks.bin"); - } - if (res.status == API_RETURN_CODE_BUSY) - { - WLT_LOG_L1("Core is busy, pull cancelled"); - m_core_proxy->get_editable_proxy_diagnostic_info()->is_busy = true; - stop = true; - return; - } - m_core_proxy->get_editable_proxy_diagnostic_info()->is_busy = false; - THROW_IF_TRUE_WALLET_EX(res.status != API_RETURN_CODE_OK, error::get_blocks_error, res.status); - THROW_IF_TRUE_WALLET_EX(get_blockchain_current_size() && get_blockchain_current_size() <= res.start_height && res.start_height != m_minimum_height, error::wallet_internal_error, - "wrong daemon response: m_start_height=" + std::to_string(res.start_height) + - " not less than local blockchain size=" + std::to_string(get_blockchain_current_size())); - - handle_pulled_blocks(blocks_added, stop, res); -} - -//---------------------------------------------------------------------------------------------------- -void wallet2::handle_pulled_blocks(size_t& blocks_added, std::atomic& stop, - currency::COMMAND_RPC_GET_BLOCKS_DIRECT::response& res) -{ - size_t current_index = res.start_height; - m_last_known_daemon_height = res.current_height; - bool been_matched_block = false; - if (res.start_height == 0 && get_blockchain_current_size() == 1 && !res.blocks.empty()) - { - const currency::block& genesis = res.blocks.front().block_ptr->bl; - THROW_IF_TRUE_WALLET_EX(get_block_height(genesis) != 0, error::wallet_internal_error, "first block expected to be genesis"); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(res.blocks.front().coinbase_ptr, "Unexpected empty coinbase"); - process_genesis_if_needed(genesis, &(res.blocks.front().coinbase_ptr->m_global_output_indexes)); - res.blocks.pop_front(); - ++current_index; - been_matched_block = true; - } - - uint64_t last_matched_index = 0; - for(const auto& bl_entry: res.blocks) - { - if (stop) - break; - - const currency::block& bl = bl_entry.block_ptr->bl; - uint64_t height = get_block_height(bl); - uint64_t processed_blocks_count = get_blockchain_current_size(); - - //TODO: get_block_hash is slow - crypto::hash bl_id = get_block_hash(bl); - - if (processed_blocks_count != 1 && height > processed_blocks_count) - { - if (height != m_minimum_height) - { - //internal error: - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(false, - "height{" << height << "} > processed_blocks_count{" << processed_blocks_count << "}"); + // this means that wallet received atomic as proposal but never activated it, and now we back to phase where out can be activated + //but we keep spend flag anyway + tr.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; //re assure that it has spent flag + tr.m_spent_height = 0; } else { - //possible case, wallet rewound to m_minimum_height - m_chain.clear(); + // this means that wallet created atomic by itself, and second part didn't redeem it, + // so refund money became available, and now we back again to unavailable state + tr.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; //reset spent flag + m_found_free_amounts.clear(); //reset free amounts cache + tr.m_spent_height = 0; } - } - else if (height == processed_blocks_count && been_matched_block) - { - //regular block handling - //self check - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(been_matched_block, - "internal error: been_matched_block == false on process_new_blockchain_entry, bl_id" << bl_id << "h=" << height - << " (start_height=" + std::to_string(res.start_height) + ")"); - - process_new_blockchain_entry(bl, bl_entry, bl_id, current_index); - ++blocks_added; - } - else - { - //checking if we need reorganize (might be just first matched block) - bool block_found = false; - bool block_matched = false; - bool full_reset_needed = false; - m_chain.check_if_block_matched(height, bl_id, block_found, block_matched, full_reset_needed); - if (block_found && block_matched) + //re-add to active contracts + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(tr.m_ptx_wallet_info->m_tx.vout[tr.m_internal_output_index].type() == typeid(tx_out_bare), std::string("Unexprected type of out in unprocess_htlc_triggers_on_block_removed : ") + tr.m_ptx_wallet_info->m_tx.vout[tr.m_internal_output_index].type().name()); + auto pair_key = std::make_pair(tr.m_amount, tr.m_global_output_index); + auto it_active_htlc = m_active_htlcs.find(pair_key); + if (it_active_htlc != m_active_htlcs.end()) { - //block matched in that number - last_matched_index = height; - been_matched_block = true; - WLT_LOG_L4("Block " << bl_id << " @ " << height << " is already in wallet's blockchain"); + LOG_ERROR("Error at putting back htlc: already exist?"); + it_active_htlc->second = it->second.transfer_index; + } else { - //this should happen ONLY after block been matched, if not then is internal error - if (full_reset_needed) + m_active_htlcs[pair_key] = it->second.transfer_index; + } + + const crypto::hash tx_id = tr.tx_hash(); + auto tx_id_it = m_active_htlcs_txid.find(tx_id); + if (tx_id_it != m_active_htlcs_txid.end()) + { + LOG_ERROR("Error at putting back htlc_txid: already exist?"); + tx_id_it->second = it->second.transfer_index; + + } + else + { + m_active_htlcs_txid[tx_id] = it->second.transfer_index; + } + } + } + void wallet2::process_htlc_triggers_on_block_added(uint64_t height) + { + if (!m_htlcs.size()) + return; + + if (height > m_htlcs.rbegin()->first) + { + //there is no active htlc that at this height + CHECK_AND_ASSERT_MES(m_active_htlcs.size() == 0, void(), "Self check failed: m_active_htlcs.size() = " << m_active_htlcs.size()); + return; + } + //we have to check if there is a htlc that has to become deactivated + auto pair_of_it = m_htlcs.equal_range(height); + for (auto it = pair_of_it.first; it != pair_of_it.second; it++) + { + auto& tr = m_transfers.at(it->second.transfer_index); + //found contract that supposed to be deactivated and set to innactive + if (it->second.is_wallet_owns_redeem) + { + // this means that wallet received atomic as proposal but never activated it, money returned to initiator + tr.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; //re assure that it has spent flag + tr.m_spent_height = height; + } + else + { + // this means that wallet created atomic by itself, and second part didn't redeem it, so refund money should become available + tr.m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_SPENT); //reset spent flag + m_found_free_amounts.clear(); //reset free amounts cache + tr.m_spent_height = 0; + } + + //reset cache + m_found_free_amounts.clear(); + + //remove it from active contracts + CHECK_AND_ASSERT_MES(tr.m_ptx_wallet_info->m_tx.vout[tr.m_internal_output_index].type() == typeid(tx_out_bare), void(), "Unexpected type out in process_htlc_triggers_on_block_added: " << tr.m_ptx_wallet_info->m_tx.vout[tr.m_internal_output_index].type().name()); + uint64_t amount = tr.m_amount; + + auto it_active_htlc = m_active_htlcs.find(std::make_pair(amount, tr.m_global_output_index)); + if (it_active_htlc == m_active_htlcs.end()) + { + LOG_ERROR("Erasing active htlc(m_active_htlcs), but it seems to be already erased"); + } + else + { + m_active_htlcs.erase(it_active_htlc); + auto it_tx = m_active_htlcs_txid.find(tr.tx_hash()); + if (it_tx == m_active_htlcs_txid.end()) { - last_matched_index = 0; - been_matched_block = true; + LOG_ERROR("Erasing active htlc(;), but it seems to be already erased"); } else { - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(been_matched_block, - "unmatched block while never been mathced block"); + m_active_htlcs_txid.erase(it_tx); } - //TODO: take into account date of wallet creation - //reorganize - detach_blockchain(last_matched_index+1); - process_new_blockchain_entry(bl, bl_entry, bl_id, height); + } + } + } + //---------------------------------------------------------------------------------------------------- + void wallet2::process_new_blockchain_entry(const currency::block& b, const currency::block_direct_data_entry& bche, const crypto::hash& bl_id, uint64_t height) + { + //handle transactions from new block + THROW_IF_TRUE_WALLET_EX(height != get_blockchain_current_size() && + !(height == m_minimum_height || get_blockchain_current_size() <= 1), error::wallet_internal_error, + "current_index=" + std::to_string(height) + ", get_blockchain_current_height()=" + std::to_string(get_blockchain_current_size())); + + + + + //optimization: seeking only for blocks that are not older then the wallet creation time plus 1 day. 1 day is for possible user incorrect time setup + const std::vector* pglobal_index = nullptr; + if (get_block_height(b) > get_wallet_minimum_height()) // b.timestamp + 60 * 60 * 24 > m_account.get_createtime()) + { + pglobal_index = nullptr; + if (bche.coinbase_ptr.get()) + { + pglobal_index = &(bche.coinbase_ptr->m_global_output_indexes); + } + TIME_MEASURE_START(miner_tx_handle_time); + process_new_transaction(b.miner_tx, height, b, pglobal_index); + TIME_MEASURE_FINISH(miner_tx_handle_time); + + TIME_MEASURE_START(txs_handle_time); + size_t count = 0; + for (const auto& tx_entry : bche.txs_ptr) + { + if (b.tx_hashes.size() < count || currency::get_transaction_hash(tx_entry->tx) != b.tx_hashes[count]) + { + LOG_ERROR("Found tx order fail in process_new_blockchain_entry: count=" << count + << ", b.tx_hashes.size() = " << b.tx_hashes.size() << ", tx real id: " << currency::get_transaction_hash(tx_entry->tx) << ", bl_id: " << bl_id); + } + process_new_transaction(tx_entry->tx, height, b, &(tx_entry->m_global_output_indexes)); + count++; + } + TIME_MEASURE_FINISH(txs_handle_time); + WLT_LOG_L3("Processed block: " << bl_id << ", height " << height << ", " << miner_tx_handle_time + txs_handle_time << "(" << miner_tx_handle_time << "/" << txs_handle_time << ")ms"); + } + else + { + WLT_LOG_L3("Skipped block by timestamp, height: " << height << ", block time " << b.timestamp << ", account time " << m_account.get_createtime()); + } + m_chain.push_new_block_id(bl_id, height); //m_blockchain.push_back(bl_id); + m_last_bc_timestamp = b.timestamp; + if (!is_pos_block(b)) + m_last_pow_block_h = height; + + + process_htlc_triggers_on_block_added(height); + m_wcallback->on_new_block(height, b); + } + //---------------------------------------------------------------------------------------------------- + + //---------------------------------------------------------------------------------------------------- + // void wallet2::get_short_chain_history(std::list& ids) + // { + // ids.clear(); + // size_t i = 0; + // size_t current_multiplier = 1; + // size_t sz = get_blockchain_current_height(); + // if(!sz) + // return; + // size_t current_back_offset = 1; + // bool genesis_included = false; + // while(current_back_offset < sz) + // { + // ids.push_back(m_blockchain[sz-current_back_offset]); + // if(sz-current_back_offset == 0) + // genesis_included = true; + // if(i < 10) + // { + // ++current_back_offset; + // }else + // { + // current_back_offset += current_multiplier *= 2; + // } + // ++i; + // } + // if(!genesis_included) + // ids.push_back(m_blockchain[0]); + // } + + //---------------------------------------------------------------------------------------------------- + void wallet2::set_minimum_height(uint64_t h) + { + m_minimum_height = h; + } + //---------------------------------------------------------------------------------------------------- + uint64_t wallet2::get_wallet_minimum_height() + { + + if (m_minimum_height != WALLET_MINIMUM_HEIGHT_UNSET_CONST) + return m_minimum_height; + + currency::COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE::request req = AUTO_VAL_INIT(req); + currency::COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE::response res = AUTO_VAL_INIT(res); + req.timestamp = m_account.get_createtime(); + bool r = m_core_proxy->call_COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE(req, res); + THROW_IF_FALSE_WALLET_EX(r, error::no_connection_to_daemon, "call_COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE"); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(res.status == API_RETURN_CODE_OK, "FAILED TO CALL COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE"); + m_minimum_height = res.h; + return res.h; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::pull_blocks(size_t& blocks_added, std::atomic& stop) + { + blocks_added = 0; + currency::COMMAND_RPC_GET_BLOCKS_DIRECT::request req = AUTO_VAL_INIT(req); + currency::COMMAND_RPC_GET_BLOCKS_DIRECT::response res = AUTO_VAL_INIT(res); + + req.minimum_height = get_wallet_minimum_height(); + if (req.minimum_height > m_height_of_start_sync) + m_height_of_start_sync = req.minimum_height; + + m_chain.get_short_chain_history(req.block_ids); + bool r = m_core_proxy->call_COMMAND_RPC_GET_BLOCKS_DIRECT(req, res); + if (!r) + throw error::no_connection_to_daemon(LOCATION_STR, "getblocks.bin"); + if (res.status == API_RETURN_CODE_GENESIS_MISMATCH) + { + WLT_LOG_MAGENTA("Reseting genesis block...", LOG_LEVEL_0); + COMMAND_RPC_GET_BLOCKS_DETAILS::request gbd_req = AUTO_VAL_INIT(gbd_req); + COMMAND_RPC_GET_BLOCKS_DETAILS::response gbd_res = AUTO_VAL_INIT(gbd_res); + gbd_req.height_start = 0; + gbd_req.count = 1; + gbd_req.ignore_transactions = true; + r = m_core_proxy->call_COMMAND_RPC_GET_BLOCKS_DETAILS(gbd_req, gbd_res); + THROW_IF_TRUE_WALLET_EX(!r, error::no_connection_to_daemon, "get_blocks_details"); + THROW_IF_TRUE_WALLET_EX(gbd_res.status != API_RETURN_CODE_OK, error::get_blocks_error, gbd_res.status); + THROW_IF_TRUE_WALLET_EX(gbd_res.blocks.size() == 0, error::get_blocks_error, gbd_res.status); + crypto::hash new_genesis_id = null_hash; + r = string_tools::parse_tpod_from_hex_string(gbd_res.blocks.back().id, new_genesis_id); + THROW_IF_TRUE_WALLET_EX(!r, error::no_connection_to_daemon, "get_blocks_details"); + reset_all(); + m_minimum_height = req.minimum_height; + m_chain.set_genesis(new_genesis_id); + WLT_LOG_MAGENTA("New genesis set for wallet: " << new_genesis_id, LOG_LEVEL_0); + m_chain.get_short_chain_history(req.block_ids); + //req.block_ids.push_back(new_genesis_id); + bool r = m_core_proxy->call_COMMAND_RPC_GET_BLOCKS_DIRECT(req, res); + THROW_IF_TRUE_WALLET_EX(!r, error::no_connection_to_daemon, "getblocks.bin"); + } + if (res.status == API_RETURN_CODE_BUSY) + { + WLT_LOG_L1("Core is busy, pull cancelled"); + m_core_proxy->get_editable_proxy_diagnostic_info()->is_busy = true; + stop = true; + return; + } + m_core_proxy->get_editable_proxy_diagnostic_info()->is_busy = false; + THROW_IF_TRUE_WALLET_EX(res.status != API_RETURN_CODE_OK, error::get_blocks_error, res.status); + THROW_IF_TRUE_WALLET_EX(get_blockchain_current_size() && get_blockchain_current_size() <= res.start_height && res.start_height != m_minimum_height, error::wallet_internal_error, + "wrong daemon response: m_start_height=" + std::to_string(res.start_height) + + " not less than local blockchain size=" + std::to_string(get_blockchain_current_size())); + + handle_pulled_blocks(blocks_added, stop, res); + } + + //---------------------------------------------------------------------------------------------------- + void wallet2::handle_pulled_blocks(size_t& blocks_added, std::atomic& stop, + currency::COMMAND_RPC_GET_BLOCKS_DIRECT::response& res) + { + size_t current_index = res.start_height; + m_last_known_daemon_height = res.current_height; + bool been_matched_block = false; + if (res.start_height == 0 && get_blockchain_current_size() == 1 && !res.blocks.empty()) + { + const currency::block& genesis = res.blocks.front().block_ptr->bl; + THROW_IF_TRUE_WALLET_EX(get_block_height(genesis) != 0, error::wallet_internal_error, "first block expected to be genesis"); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(res.blocks.front().coinbase_ptr, "Unexpected empty coinbase"); + process_genesis_if_needed(genesis, &(res.blocks.front().coinbase_ptr->m_global_output_indexes)); + res.blocks.pop_front(); + ++current_index; + been_matched_block = true; + } + + uint64_t last_matched_index = 0; + for (const auto& bl_entry : res.blocks) + { + if (stop) + break; + + const currency::block& bl = bl_entry.block_ptr->bl; + uint64_t height = get_block_height(bl); + uint64_t processed_blocks_count = get_blockchain_current_size(); + + //TODO: get_block_hash is slow + crypto::hash bl_id = get_block_hash(bl); + + if (processed_blocks_count != 1 && height > processed_blocks_count) + { + if (height != m_minimum_height) + { + //internal error: + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(false, + "height{" << height << "} > processed_blocks_count{" << processed_blocks_count << "}"); + } + else + { + //possible case, wallet rewound to m_minimum_height + m_chain.clear(); + } + } + else if (height == processed_blocks_count && been_matched_block) + { + //regular block handling + //self check + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(been_matched_block, + "internal error: been_matched_block == false on process_new_blockchain_entry, bl_id" << bl_id << "h=" << height + << " (start_height=" + std::to_string(res.start_height) + ")"); + + process_new_blockchain_entry(bl, bl_entry, bl_id, current_index); ++blocks_added; } - } - - - ++current_index; - if (res.current_height > m_height_of_start_sync) - { - uint64_t next_percent = (100 * (current_index - m_height_of_start_sync)) / (res.current_height - m_height_of_start_sync); - if (next_percent != m_last_sync_percent) + else { - m_wcallback->on_sync_progress(next_percent); - m_last_sync_percent = next_percent; + //checking if we need reorganize (might be just first matched block) + bool block_found = false; + bool block_matched = false; + bool full_reset_needed = false; + m_chain.check_if_block_matched(height, bl_id, block_found, block_matched, full_reset_needed); + if (block_found && block_matched) + { + //block matched in that number + last_matched_index = height; + been_matched_block = true; + WLT_LOG_L4("Block " << bl_id << " @ " << height << " is already in wallet's blockchain"); + } + else + { + //this should happen ONLY after block been matched, if not then is internal error + if (full_reset_needed) + { + last_matched_index = 0; + been_matched_block = true; + } + else + { + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(been_matched_block, + "unmatched block while never been mathced block"); + } + //TODO: take into account date of wallet creation + //reorganize + detach_blockchain(last_matched_index + 1); + process_new_blockchain_entry(bl, bl_entry, bl_id, height); + ++blocks_added; + } + } + + + ++current_index; + if (res.current_height > m_height_of_start_sync) + { + uint64_t next_percent = (100 * (current_index - m_height_of_start_sync)) / (res.current_height - m_height_of_start_sync); + if (next_percent != m_last_sync_percent) + { + m_wcallback->on_sync_progress(next_percent); + m_last_sync_percent = next_percent; + } } } + + WLT_LOG_L2("[PULL BLOCKS] " << res.start_height << " --> " << get_blockchain_current_size() - 1); } - - WLT_LOG_L2("[PULL BLOCKS] " << res.start_height << " --> " << get_blockchain_current_size() - 1); -} -//---------------------------------------------------------------------------------------------------- -uint64_t wallet2::get_sync_progress() -{ - return m_last_sync_percent; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::refresh() -{ - size_t blocks_fetched = 0; - refresh(blocks_fetched); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::refresh(size_t & blocks_fetched) -{ - bool received_money = false; - m_stop = false; - refresh(blocks_fetched, received_money, m_stop); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::refresh(std::atomic& stop) -{ - bool f; - size_t n; - refresh(n, f, stop); -} -//---------------------------------------------------------------------------------------------------- -detail::split_strategy_id_t wallet2::get_current_split_strategy() -{ - if (is_need_to_split_outputs()) - return tools::detail::ssi_digit; - else - return tools::detail::ssi_void; -} -// -void wallet2::transfer(uint64_t amount, const currency::account_public_address& acc, currency::transaction& result_tx, const crypto::public_key& asset_id /* = currency::native_coin_asset_id */) -{ - std::vector extra; - std::vector attachments; - - std::vector dst; - dst.resize(1); - dst.back().addr.push_back(acc); - dst.back().amount = amount; - dst.back().asset_id = asset_id; - this->transfer(dst, 0, 0, TX_DEFAULT_FEE, extra, attachments, get_current_split_strategy(), tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), result_tx); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::transfer(uint64_t amount, size_t fake_outs_count, const currency::account_public_address& acc, uint64_t fee /* = TX_DEFAULT_FEE*/, - const crypto::public_key& asset_id /* = currency::native_coin_asset_id */) -{ - std::vector extra; - std::vector attachments; - transaction result_tx = AUTO_VAL_INIT(result_tx); - - std::vector dst; - dst.resize(1); - dst.back().addr.push_back(acc); - dst.back().amount = amount; - dst.back().asset_id = asset_id; - this->transfer(dst, fake_outs_count, 0, fee, extra, attachments, get_current_split_strategy(), tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), result_tx); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::transfer(uint64_t amount, const currency::account_public_address& acc, const crypto::public_key& asset_id /* = currency::native_coin_asset_id */) -{ - transaction result_tx = AUTO_VAL_INIT(result_tx); - this->transfer(amount, acc, result_tx, asset_id); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::reset_creation_time(uint64_t timestamp) -{ - m_account.set_createtime(timestamp); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::update_current_tx_limit() -{ - currency::COMMAND_RPC_GET_INFO::request req = AUTO_VAL_INIT(req); - currency::COMMAND_RPC_GET_INFO::response res = AUTO_VAL_INIT(res); - bool r = m_core_proxy->call_COMMAND_RPC_GET_INFO(req, res); - THROW_IF_TRUE_WALLET_EX(!r, error::no_connection_to_daemon, "getinfo"); - THROW_IF_TRUE_WALLET_EX(res.status == API_RETURN_CODE_BUSY, error::daemon_busy, "getinfo"); - THROW_IF_TRUE_WALLET_EX(res.status != API_RETURN_CODE_OK, error::get_blocks_error, res.status); - THROW_IF_TRUE_WALLET_EX(res.current_blocks_median < CURRENCY_BLOCK_GRANTED_FULL_REWARD_ZONE, error::get_blocks_error, "bad median size"); - m_upper_transaction_size_limit = res.current_blocks_median - CURRENCY_COINBASE_BLOB_RESERVED_SIZE; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::has_related_alias_entry_unconfirmed(const currency::transaction& tx) -{ - std::string local_adr = m_account.get_public_address_str(); - tx_extra_info tei = AUTO_VAL_INIT(tei); - parse_and_validate_tx_extra(tx, tei); - if (tei.m_alias.m_alias.size()) + //---------------------------------------------------------------------------------------------------- + uint64_t wallet2::get_sync_progress() { - //have some check address involved - if (tei.m_alias.m_address.spend_public_key == m_account.get_keys().account_address.spend_public_key && - tei.m_alias.m_address.view_public_key == m_account.get_keys().account_address.view_public_key) - return true; - - //check if it's update and address before was our address - currency::COMMAND_RPC_GET_ALIAS_DETAILS::request req = AUTO_VAL_INIT(req); - currency::COMMAND_RPC_GET_ALIAS_DETAILS::response res = AUTO_VAL_INIT(res); - req.alias = tei.m_alias.m_alias; - m_core_proxy->call_COMMAND_RPC_GET_ALIAS_DETAILS(req, res); - if (res.status != API_RETURN_CODE_OK) - return false; - if (local_adr == res.alias_details.address) - return true; - + return m_last_sync_percent; } - return false; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::has_bare_unspent_outputs() const -{ - if (m_account.get_createtime() > ZANO_HARDFORK_04_TIMESTAMP_ACTUAL) + //---------------------------------------------------------------------------------------------------- + void wallet2::refresh() + { + size_t blocks_fetched = 0; + refresh(blocks_fetched); + } + //---------------------------------------------------------------------------------------------------- + void wallet2::refresh(size_t& blocks_fetched) + { + bool received_money = false; + m_stop = false; + refresh(blocks_fetched, received_money, m_stop); + } + //---------------------------------------------------------------------------------------------------- + void wallet2::refresh(std::atomic& stop) + { + bool f; + size_t n; + refresh(n, f, stop); + } + //---------------------------------------------------------------------------------------------------- + detail::split_strategy_id_t wallet2::get_current_split_strategy() + { + if (is_need_to_split_outputs()) + return tools::detail::ssi_digit; + else + return tools::detail::ssi_void; + } + // + void wallet2::transfer(uint64_t amount, const currency::account_public_address& acc, currency::transaction& result_tx, const crypto::public_key& asset_id /* = currency::native_coin_asset_id */) + { + std::vector extra; + std::vector attachments; + + std::vector dst; + dst.resize(1); + dst.back().addr.push_back(acc); + dst.back().amount = amount; + dst.back().asset_id = asset_id; + this->transfer(dst, 0, 0, TX_DEFAULT_FEE, extra, attachments, get_current_split_strategy(), tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), result_tx); + } + //---------------------------------------------------------------------------------------------------- + void wallet2::transfer(uint64_t amount, size_t fake_outs_count, const currency::account_public_address& acc, uint64_t fee /* = TX_DEFAULT_FEE*/, + const crypto::public_key& asset_id /* = currency::native_coin_asset_id */) + { + std::vector extra; + std::vector attachments; + transaction result_tx = AUTO_VAL_INIT(result_tx); + + std::vector dst; + dst.resize(1); + dst.back().addr.push_back(acc); + dst.back().amount = amount; + dst.back().asset_id = asset_id; + this->transfer(dst, fake_outs_count, 0, fee, extra, attachments, get_current_split_strategy(), tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), result_tx); + } + //---------------------------------------------------------------------------------------------------- + void wallet2::transfer(uint64_t amount, const currency::account_public_address& acc, const crypto::public_key& asset_id /* = currency::native_coin_asset_id */) + { + transaction result_tx = AUTO_VAL_INIT(result_tx); + this->transfer(amount, acc, result_tx, asset_id); + } + //---------------------------------------------------------------------------------------------------- + void wallet2::reset_creation_time(uint64_t timestamp) + { + m_account.set_createtime(timestamp); + } + //---------------------------------------------------------------------------------------------------- + void wallet2::update_current_tx_limit() + { + currency::COMMAND_RPC_GET_INFO::request req = AUTO_VAL_INIT(req); + currency::COMMAND_RPC_GET_INFO::response res = AUTO_VAL_INIT(res); + bool r = m_core_proxy->call_COMMAND_RPC_GET_INFO(req, res); + THROW_IF_TRUE_WALLET_EX(!r, error::no_connection_to_daemon, "getinfo"); + THROW_IF_TRUE_WALLET_EX(res.status == API_RETURN_CODE_BUSY, error::daemon_busy, "getinfo"); + THROW_IF_TRUE_WALLET_EX(res.status != API_RETURN_CODE_OK, error::get_blocks_error, res.status); + THROW_IF_TRUE_WALLET_EX(res.current_blocks_median < CURRENCY_BLOCK_GRANTED_FULL_REWARD_ZONE, error::get_blocks_error, "bad median size"); + m_upper_transaction_size_limit = res.current_blocks_median - CURRENCY_COINBASE_BLOB_RESERVED_SIZE; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::has_related_alias_entry_unconfirmed(const currency::transaction& tx) + { + std::string local_adr = m_account.get_public_address_str(); + tx_extra_info tei = AUTO_VAL_INIT(tei); + parse_and_validate_tx_extra(tx, tei); + if (tei.m_alias.m_alias.size()) + { + //have some check address involved + if (tei.m_alias.m_address.spend_public_key == m_account.get_keys().account_address.spend_public_key && + tei.m_alias.m_address.view_public_key == m_account.get_keys().account_address.view_public_key) + return true; + + //check if it's update and address before was our address + currency::COMMAND_RPC_GET_ALIAS_DETAILS::request req = AUTO_VAL_INIT(req); + currency::COMMAND_RPC_GET_ALIAS_DETAILS::response res = AUTO_VAL_INIT(res); + req.alias = tei.m_alias.m_alias; + m_core_proxy->call_COMMAND_RPC_GET_ALIAS_DETAILS(req, res); + if (res.status != API_RETURN_CODE_OK) + return false; + if (local_adr == res.alias_details.address) + return true; + + } return false; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::has_bare_unspent_outputs() const + { + if (m_account.get_createtime() > ZANO_HARDFORK_04_TIMESTAMP_ACTUAL) + return false; - [[maybe_unused]] uint64_t bal = 0; - if (!m_has_bare_unspent_outputs.has_value()) - bal = balance(); + [[maybe_unused]] uint64_t bal = 0; + if (!m_has_bare_unspent_outputs.has_value()) + bal = balance(); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(m_has_bare_unspent_outputs.has_value(), "m_has_bare_unspent_outputs has no value after balance()"); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(m_has_bare_unspent_outputs.has_value(), "m_has_bare_unspent_outputs has no value after balance()"); - return m_has_bare_unspent_outputs.value(); -} -//---------------------------------------------------------------------------------------------------- + return m_has_bare_unspent_outputs.value(); + } + //---------------------------------------------------------------------------------------------------- #define MAX_INPUTS_FOR_SIMPLE_TX_EURISTIC 20 -bool wallet2::get_bare_unspent_outputs_stats(std::vector& tids_grouped_by_txs) const -{ - tids_grouped_by_txs.clear(); - - // 1/3. Populate a list of bare unspent outputs - std::unordered_map> buo_ids; // tx hash -> Bare Unspent Outs list - for(size_t tid = 0; tid != m_transfers.size(); ++tid) + bool wallet2::get_bare_unspent_outputs_stats(std::vector& tids_grouped_by_txs) const { - const auto& td = m_transfers[tid]; - if (!td.is_zc() && td.is_spendable()) + tids_grouped_by_txs.clear(); + + // 1/3. Populate a list of bare unspent outputs + std::unordered_map> buo_ids; // tx hash -> Bare Unspent Outs list + for (const auto& tr : m_transfers) { - buo_ids[td.tx_hash()].push_back(tid); + uint64_t tid = tr.first; + const auto& td = m_transfers.at(tid); + if (!td.is_zc() && td.is_spendable()) + { + buo_ids[td.tx_hash()].push_back(tid); + } } - } - if (buo_ids.empty()) - return true; + if (buo_ids.empty()) + return true; - // 2/3. Split them into groups - tids_grouped_by_txs.emplace_back(); - for(auto& buo_el : buo_ids) - { - if (tids_grouped_by_txs.back().tids.size() + buo_el.second.size() > MAX_INPUTS_FOR_SIMPLE_TX_EURISTIC) - tids_grouped_by_txs.emplace_back(); - - for(auto& tid : buo_el.second) + // 2/3. Split them into groups + tids_grouped_by_txs.emplace_back(); + for (auto& buo_el : buo_ids) { - if (tids_grouped_by_txs.back().tids.size() >= MAX_INPUTS_FOR_SIMPLE_TX_EURISTIC) + if (tids_grouped_by_txs.back().tids.size() + buo_el.second.size() > MAX_INPUTS_FOR_SIMPLE_TX_EURISTIC) tids_grouped_by_txs.emplace_back(); - tids_grouped_by_txs.back().tids.push_back((uint64_t)tid); - tids_grouped_by_txs.back().total_amount += m_transfers[tid].m_amount; + + for (auto& tid : buo_el.second) + { + if (tids_grouped_by_txs.back().tids.size() >= MAX_INPUTS_FOR_SIMPLE_TX_EURISTIC) + tids_grouped_by_txs.emplace_back(); + tids_grouped_by_txs.back().tids.push_back((uint64_t)tid); + tids_grouped_by_txs.back().total_amount += m_transfers.at(tid).m_amount; + } } - } - // 3/3. Iterate through groups and check whether total amount is big enough to cover min fee. - // Add additional zc output if not. - std::multimap usable_zc_outs_tids; // grouped by amount - bool usable_zc_outs_tids_precalculated = false; - auto precalculate_usable_zc_outs_if_needed = [&](){ + // 3/3. Iterate through groups and check whether total amount is big enough to cover min fee. + // Add additional zc output if not. + std::multimap usable_zc_outs_tids; // grouped by amount + bool usable_zc_outs_tids_precalculated = false; + auto precalculate_usable_zc_outs_if_needed = [&]() { if (usable_zc_outs_tids_precalculated) return; size_t decoys = is_auditable() ? 0 : m_core_runtime_config.hf4_minimum_mixins; - for(size_t tid = 0; tid != m_transfers.size(); ++tid) + for (const auto& tr : m_transfers) { - auto& td = m_transfers[tid]; + uint64_t tid = tr.first; + auto& td = m_transfers.at(tid); if (td.is_zc() && td.is_native_coin() && is_transfer_ready_to_go(td, decoys)) usable_zc_outs_tids.insert(std::make_pair(td.m_amount, tid)); } usable_zc_outs_tids_precalculated = true; - }; + }; - std::unordered_set used_zc_outs; - for(auto it = tids_grouped_by_txs.begin(); it != tids_grouped_by_txs.end(); ) - { - auto& group = *it; - if (group.total_amount < TX_MINIMUM_FEE) + std::unordered_set used_zc_outs; + for (auto it = tids_grouped_by_txs.begin(); it != tids_grouped_by_txs.end(); ) { - precalculate_usable_zc_outs_if_needed(); - - uint64_t min_required_amount = TX_MINIMUM_FEE - group.total_amount; - auto jt = usable_zc_outs_tids.lower_bound(min_required_amount); - bool found = false; - while(jt != usable_zc_outs_tids.end()) + auto& group = *it; + if (group.total_amount < TX_MINIMUM_FEE) { - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(jt->first >= min_required_amount, "jt->first=" << jt->first << ", min_required_amount=" << min_required_amount); - if (used_zc_outs.count(jt->second) == 0) + precalculate_usable_zc_outs_if_needed(); + + uint64_t min_required_amount = TX_MINIMUM_FEE - group.total_amount; + auto jt = usable_zc_outs_tids.lower_bound(min_required_amount); + bool found = false; + while (jt != usable_zc_outs_tids.end()) { - group.tids.push_back((uint64_t)jt->second); - used_zc_outs.insert(jt->second); - group.additional_tid = true; - group.additional_tid_amount = jt->first; - found = true; - break; + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(jt->first >= min_required_amount, "jt->first=" << jt->first << ", min_required_amount=" << min_required_amount); + if (used_zc_outs.count(jt->second) == 0) + { + group.tids.push_back((uint64_t)jt->second); + used_zc_outs.insert(jt->second); + group.additional_tid = true; + group.additional_tid_amount = jt->first; + found = true; + break; + } + ++jt; + } + + if (!found) + { + // no usable outs for required amount, remove this group and go to the next + it = tids_grouped_by_txs.erase(it); + continue; } - ++jt; } - if (!found) - { - // no usable outs for required amount, remove this group and go to the next - it = tids_grouped_by_txs.erase(it); - continue; - } + ++it; } - ++it; + return true; } - - return true; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::sweep_bare_unspent_outputs(const currency::account_public_address& target_address, const std::vector& tids_grouped_by_txs, - std::function on_tx_sent) -{ - if (m_watch_only) - return false; - - size_t decoys_count = is_auditable() ? 0 : CURRENCY_DEFAULT_DECOY_SET_SIZE; - - bool send_to_network = true; - - size_t batch_index = 0; - for(const batch_of_bare_unspent_outs& group : tids_grouped_by_txs) + //---------------------------------------------------------------------------------------------------- + bool wallet2::sweep_bare_unspent_outputs(const currency::account_public_address& target_address, const std::vector& tids_grouped_by_txs, + std::function on_tx_sent) { - currency::finalized_tx ftx{}; - currency::finalize_tx_param ftp{}; - ftp.pevents_dispatcher = &m_debug_events_dispatcher; - ftp.tx_version = this->get_current_tx_version(); + if (m_watch_only) + return false; - if (!prepare_tx_sources(decoys_count, /*use_all_decoys_if_found_less_than_required*/ true, ftp.sources, group.tids)) + size_t decoys_count = is_auditable() ? 0 : CURRENCY_DEFAULT_DECOY_SET_SIZE; + + bool send_to_network = true; + + size_t batch_index = 0; + for (const batch_of_bare_unspent_outs& group : tids_grouped_by_txs) { - on_tx_sent(batch_index, transaction{}, 0, 0, false, "sources for tx couldn't be prepared"); - LOG_PRINT_L0("prepare_tx_sources failed, batch_index = " << batch_index); - return false; - } - uint64_t fee = TX_DEFAULT_FEE; - std::vector destinations{tx_destination_entry(group.total_amount + group.additional_tid_amount - fee, target_address)}; - assets_selection_context needed_money_map{std::make_pair(native_coin_asset_id, selection_for_amount{group.total_amount + group.additional_tid_amount, group.total_amount + group.additional_tid_amount})}; - try - { - prepare_tx_destinations(needed_money_map, get_current_split_strategy(), tx_dust_policy{}, destinations, 0 /* tx_flags */, ftp.prepared_destinations); - } - catch(...) - { - on_tx_sent(batch_index, transaction{}, 0, 0, false, "destinations for tx couldn't be prepared"); - LOG_PRINT_L0("prepare_tx_destinations failed, batch_index = " << batch_index); - return false; + currency::finalized_tx ftx{}; + currency::finalize_tx_param ftp{}; + ftp.pevents_dispatcher = &m_debug_events_dispatcher; + ftp.tx_version = this->get_current_tx_version(); + + if (!prepare_tx_sources(decoys_count, /*use_all_decoys_if_found_less_than_required*/ true, ftp.sources, group.tids)) + { + on_tx_sent(batch_index, transaction{}, 0, 0, false, "sources for tx couldn't be prepared"); + LOG_PRINT_L0("prepare_tx_sources failed, batch_index = " << batch_index); + return false; + } + uint64_t fee = TX_DEFAULT_FEE; + std::vector destinations{ tx_destination_entry(group.total_amount + group.additional_tid_amount - fee, target_address) }; + assets_selection_context needed_money_map{ std::make_pair(native_coin_asset_id, selection_for_amount{group.total_amount + group.additional_tid_amount, group.total_amount + group.additional_tid_amount}) }; + try + { + prepare_tx_destinations(needed_money_map, get_current_split_strategy(), tx_dust_policy{}, destinations, 0 /* tx_flags */, ftp.prepared_destinations); + } + catch (...) + { + on_tx_sent(batch_index, transaction{}, 0, 0, false, "destinations for tx couldn't be prepared"); + LOG_PRINT_L0("prepare_tx_destinations failed, batch_index = " << batch_index); + return false; + } + + mark_transfers_as_spent(ftp.selected_transfers, std::string("sweep bare UTXO, tx: ") + epee::string_tools::pod_to_hex(get_transaction_hash(ftx.tx))); + try + { + finalize_transaction(ftp, ftx, send_to_network); + on_tx_sent(batch_index, ftx.tx, group.total_amount + group.additional_tid_amount, fee, true, std::string()); + } + catch (std::exception& e) + { + clear_transfers_from_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, std::string("exception on sweep bare UTXO, tx: ") + epee::string_tools::pod_to_hex(get_transaction_hash(ftx.tx))); + on_tx_sent(batch_index, transaction{}, 0, 0, false, e.what()); + return false; + } + + ++batch_index; } - mark_transfers_as_spent(ftp.selected_transfers, std::string("sweep bare UTXO, tx: ") + epee::string_tools::pod_to_hex(get_transaction_hash(ftx.tx))); - try - { - finalize_transaction(ftp, ftx, send_to_network); - on_tx_sent(batch_index, ftx.tx, group.total_amount + group.additional_tid_amount, fee, true, std::string()); - } - catch(std::exception& e) - { - clear_transfers_from_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, std::string("exception on sweep bare UTXO, tx: ") + epee::string_tools::pod_to_hex(get_transaction_hash(ftx.tx))); - on_tx_sent(batch_index, transaction{}, 0, 0, false, e.what()); - return false; - } - - ++batch_index; + return true; } - - return true; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::sweep_bare_unspent_outputs(const currency::account_public_address& target_address, const std::vector& tids_grouped_by_txs, - size_t& total_txs_sent, uint64_t& total_amount_sent, uint64_t& total_fee_spent, uint64_t& total_bare_outs_sent) -{ - total_txs_sent = 0; - total_amount_sent = 0; - total_fee_spent = 0; - total_bare_outs_sent = 0; - auto on_tx_sent_callback = [&](size_t batch_index, const currency::transaction& tx, uint64_t amount, uint64_t fee, bool sent_ok, const std::string& err) { + //---------------------------------------------------------------------------------------------------- + bool wallet2::sweep_bare_unspent_outputs(const currency::account_public_address& target_address, const std::vector& tids_grouped_by_txs, + size_t& total_txs_sent, uint64_t& total_amount_sent, uint64_t& total_fee_spent, uint64_t& total_bare_outs_sent) + { + total_txs_sent = 0; + total_amount_sent = 0; + total_fee_spent = 0; + total_bare_outs_sent = 0; + auto on_tx_sent_callback = [&](size_t batch_index, const currency::transaction& tx, uint64_t amount, uint64_t fee, bool sent_ok, const std::string& err) { if (sent_ok) { total_bare_outs_sent += count_type_in_variant_container(tx.vin); @@ -2426,517 +2431,518 @@ bool wallet2::sweep_bare_unspent_outputs(const currency::account_public_address& total_fee_spent += fee; total_amount_sent += amount; } - }; + }; - return sweep_bare_unspent_outputs(target_address, tids_grouped_by_txs, on_tx_sent_callback); -} -//---------------------------------------------------------------------------------------------------- -uint64_t wallet2::get_directly_spent_transfer_index_by_input_in_tracking_wallet(const currency::txin_to_key& intk) -{ - return get_directly_spent_transfer_index_by_input_in_tracking_wallet(intk.amount, intk.key_offsets); -} -//---------------------------------------------------------------------------------------------------- -uint64_t wallet2::get_directly_spent_transfer_index_by_input_in_tracking_wallet(const currency::txin_zc_input& inzc) -{ - return get_directly_spent_transfer_index_by_input_in_tracking_wallet(0, inzc.key_offsets); -} -//---------------------------------------------------------------------------------------------------- -uint64_t wallet2::get_directly_spent_transfer_index_by_input_in_tracking_wallet(uint64_t amount, const std::vector & key_offsets) -{ - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(is_auditable() && is_watch_only(), "this is not an auditable-watch-only (tracking) wallet"); - - uint64_t tid = UINT64_MAX; - - // try to find a reference among own UTXOs - std::vector abs_key_offsets = relative_output_offsets_to_absolute(key_offsets); // potential speed-up: don't convert to abs offsets as we interested only in direct spends for auditable wallets. Now it's kind a bit paranoid. - for (auto v : abs_key_offsets) + return sweep_bare_unspent_outputs(target_address, tids_grouped_by_txs, on_tx_sent_callback); + } + //---------------------------------------------------------------------------------------------------- + uint64_t wallet2::get_directly_spent_transfer_index_by_input_in_tracking_wallet(const currency::txin_to_key& intk) { - if (v.type() != typeid(uint64_t)) - continue; - uint64_t gindex = boost::get(v); - auto it = m_amount_gindex_to_transfer_id.find(std::make_pair(amount, gindex)); - if (it != m_amount_gindex_to_transfer_id.end()) + return get_directly_spent_transfer_index_by_input_in_tracking_wallet(intk.amount, intk.key_offsets); + } + //---------------------------------------------------------------------------------------------------- + uint64_t wallet2::get_directly_spent_transfer_index_by_input_in_tracking_wallet(const currency::txin_zc_input& inzc) + { + return get_directly_spent_transfer_index_by_input_in_tracking_wallet(0, inzc.key_offsets); + } + //---------------------------------------------------------------------------------------------------- + uint64_t wallet2::get_directly_spent_transfer_index_by_input_in_tracking_wallet(uint64_t amount, const std::vector& key_offsets) + { + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(is_auditable() && is_watch_only(), "this is not an auditable-watch-only (tracking) wallet"); + + uint64_t tid = UINT64_MAX; + + // try to find a reference among own UTXOs + std::vector abs_key_offsets = relative_output_offsets_to_absolute(key_offsets); // potential speed-up: don't convert to abs offsets as we interested only in direct spends for auditable wallets. Now it's kind a bit paranoid. + for (auto v : abs_key_offsets) { - tid = it->second; - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(tid < m_transfers.size(), "invalid tid: " << tid << ", ref from input with amount: " << amount << ", gindex: " << gindex); - auto& td = m_transfers[it->second]; - if (key_offsets.size() != 1) - { - // own output was used in non-direct transaction - // the core should not allow this to happen, the only way it may happen - mixing in own output that was sent without mix_attr == 1 - // log strange situation - std::stringstream ss; - ss << "own transfer tid=" << tid << " tx=" << td.tx_hash() << " mix_attr=" << td.mix_attr() << ", is referenced by a transaction with mixins, ref from input with amount: " << amount << ", gindex: " << gindex; - WLT_LOG_YELLOW(ss.str(), LOG_LEVEL_0); - if (m_wcallback) - m_wcallback->on_message(i_wallet2_callback::ms_yellow, ss.str()); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.mix_attr() != CURRENCY_TO_KEY_OUT_FORCED_NO_MIX, ss.str()); // if mix_attr == 1 this should never happen (mixing in an output with mix_attr = 1) as the core must reject such txs - // our own output has mix_attr != 1 for some reason (a sender did not set correct mix_attr e.g.) - // but mixin count > 1 so we can't say it is spent for sure - tid = UINT64_MAX; + if (v.type() != typeid(uint64_t)) continue; - } - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.m_spent_height == 0, "transfer is spent in blockchain, tid: " << tid << ", ref from input with amount: " << amount << ", gindex: " << gindex); - // okay, own output is being spent, return it - break; - } - } - return tid; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::handle_unconfirmed_tx(process_transaction_context& ptc) -{ - const transaction& tx = ptc.tx; - ptc.timestamp = m_core_runtime_config.get_core_time(); - // read extra - std::vector outs; - //uint64_t sum_of_received_native_outs = 0; - crypto::public_key tx_pub_key = null_pkey; - bool r = parse_and_validate_tx_extra(tx, tx_pub_key); - THROW_IF_TRUE_WALLET_EX(!r, error::tx_extra_parse_error, tx); - //check if we have money - crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); - r = lookup_acc_outs(m_account.get_keys(), tx, tx_pub_key, outs, derivation); - THROW_IF_TRUE_WALLET_EX(!r, error::acc_outs_lookup_error, tx, tx_pub_key, m_account.get_keys()); - - //collect incomes - for (auto& o : outs) - { - if(out_is_multisig(tx.vout[o.index])) - continue; - - ptc.total_balance_change[o.asset_id] += o.amount; - ptc.employed_entries.receive.push_back(wallet_public::employed_tx_entry{ o.index, o.amount, o.asset_id }); - } - - - bool new_multisig_spend_detected = false; - //check if we have spendings - //uint64_t sum_of_spent_native_coin = 0; - std::list spend_transfers; - // check all outputs for spending (compare key images) - for (size_t i = 0; i != tx.vin.size(); i++) - { - auto& in = tx.vin[i]; - if (in.type() == typeid(currency::txin_to_key)) - { - const currency::txin_to_key& intk = boost::get(in); - uint64_t tid = UINT64_MAX; - if (is_auditable() && is_watch_only()) + uint64_t gindex = boost::get(v); + auto it = m_amount_gindex_to_transfer_id.find(std::make_pair(amount, gindex)); + if (it != m_amount_gindex_to_transfer_id.end()) { - // tracking wallet, assuming all outputs are spent directly because of mix_attr = 1 - tid = get_directly_spent_transfer_index_by_input_in_tracking_wallet(intk.amount, intk.key_offsets); - } - else - { - // wallet with spend secret key -- we can calculate own key images and then search among them - auto it = m_key_images.find(intk.k_image); - if (it != m_key_images.end()) + tid = it->second; + //WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(tid < m_transfers.size(), "invalid tid: " << tid << ", ref from input with amount: " << amount << ", gindex: " << gindex); + auto& td = m_transfers.at(it->second); + if (key_offsets.size() != 1) { - tid = it->second; - } - } - - if (tid != UINT64_MAX) - { - // own output is being spent by this input - //sum_of_spent_native_coin += intk.amount; - ptc.employed_entries.spent.push_back(wallet_public::employed_tx_entry{ i, m_transfers[tid].amount(), m_transfers[tid].get_asset_id() }); - spend_transfers.push_back(tid); - ptc.total_balance_change[currency::native_coin_asset_id] -= m_transfers[tid].amount(); - CHECK_AND_ASSERT_THROW_MES(m_transfers[tid].get_asset_id() == currency::native_coin_asset_id, "Unexpected asset id for native txin_to_key"); - } - } - else if (in.type() == typeid(currency::txin_zc_input)) - { - // bad design -- remove redundancy like using wallet2::process_input_t() - const currency::txin_zc_input& zc = boost::get(in); - uint64_t tid = UINT64_MAX; - if (is_auditable() && is_watch_only()) - { - // tracking wallet, assuming all outputs are spent directly because of mix_attr = 1 - tid = get_directly_spent_transfer_index_by_input_in_tracking_wallet(zc); - } - else - { - // wallet with spend secret key -- we can calculate own key images and then search among them - auto it = m_key_images.find(zc.k_image); - if (it != m_key_images.end()) - { - tid = it->second; - } - } - - if (tid != UINT64_MAX) - { - ptc.employed_entries.spent.push_back(wallet_public::employed_tx_entry{ i, m_transfers[tid].amount(), m_transfers[tid].get_asset_id() }); - spend_transfers.push_back(tid); - ptc.total_balance_change[m_transfers[tid].get_asset_id()] -= m_transfers[tid].amount(); - } - } - else if (in.type() == typeid(currency::txin_multisig)) - { - crypto::hash multisig_id = boost::get(in).multisig_out_id; - auto it = m_multisig_transfers.find(multisig_id); - if (it != m_multisig_transfers.end()) - { - //ptc.employed_entries.spent_indices.push_back(i); - ptc.employed_entries.spent.push_back(wallet_public::employed_tx_entry{ i }); - if (ptc.pmultisig_entries) - { - r = ptc.pmultisig_entries->insert(std::make_pair(multisig_id, std::make_pair(tx, ptc.employed_entries))).second; - if (!r) - { - WLT_LOG_RED("Warning: Receiving the same multisig out id from tx pool more then once: " << multisig_id, LOG_LEVEL_0); - } - } - if (m_unconfirmed_multisig_transfers.count(multisig_id) == 0) - { - // new unconfirmed multisig (see also comments on multisig tranafers handling below) - uint32_t flags_before = it->second.m_flags; - it->second.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; // mark as spent - it->second.m_spent_height = 0; // height 0 means unconfirmed - WLT_LOG_L0("From tx pool got new unconfirmed multisig out with id: " << multisig_id << " tx: " << get_transaction_hash(tx) << " Marked as SPENT, flags: " << flags_before << " -> " << it->second.m_flags); - new_multisig_spend_detected = true; - } - - } - } - } - - //do final calculations - bool has_in_transfers = false; - bool has_out_transfers = false; - for (const auto& bce : ptc.total_balance_change) - { - if (bce.second > 0) - { - has_in_transfers = true; - } - else if (bce.second < 0) - { - has_out_transfers = true; - } - } - if (!is_tx_expired(tx, ptc.tx_expiration_ts_median) && (new_multisig_spend_detected || has_in_transfers || has_out_transfers || (currency::is_derivation_used_to_encrypt(tx, derivation)))) - { - m_unconfirmed_in_transfers[ptc.tx_hash()] = tx; - if (m_unconfirmed_txs.count(ptc.tx_hash())) - return; - - //prepare notification about pending transaction - wallet_public::wallet_transfer_info& unconfirmed_wti = misc_utils::get_or_insert_value_initialized(m_unconfirmed_txs, ptc.tx_hash()); - prepare_wti(unconfirmed_wti, ptc); - for (auto tr_index : spend_transfers) - { - if (tr_index > m_transfers.size()) - { - WLT_LOG_ERROR("INTERNAL ERROR: tr_index " << tr_index << " more then m_transfers.size()=" << m_transfers.size()); - continue; - } - uint32_t flags_before = m_transfers[tr_index].m_flags; - m_transfers[tr_index].m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; - WLT_LOG_L1("wallet transfer #" << tr_index << " is marked as spent, flags: " << flags_before << " -> " << m_transfers[tr_index].m_flags << ", reason: UNCONFIRMED tx: " << ptc.tx_hash()); - unconfirmed_wti.selected_indicies.push_back(tr_index); - } - rise_on_transfer2(unconfirmed_wti); - } -} - - -void wallet2::scan_tx_pool(bool& has_related_alias_in_unconfirmed) -{ - //get transaction pool content - currency::COMMAND_RPC_GET_TX_POOL::request req = AUTO_VAL_INIT(req); - currency::COMMAND_RPC_GET_TX_POOL::response res = AUTO_VAL_INIT(res); - bool r = m_core_proxy->call_COMMAND_RPC_GET_TX_POOL(req, res); - if (res.status == API_RETURN_CODE_BUSY) - throw error::daemon_busy(LOCATION_STR, "get_tx_pool"); - if (!r) - throw error::no_connection_to_daemon(LOCATION_STR, "get_tx_pool"); - THROW_IF_TRUE_WALLET_EX(res.status != API_RETURN_CODE_OK, error::get_blocks_error, res.status); - - - //- @#@ ----- debug -#ifdef _DEBUG - std::stringstream ss; - ss << "TXS FROM POOL: " << ENDL; - for (const auto &tx_blob : res.txs) - { - currency::transaction tx; - bool r = parse_and_validate_tx_from_blob(tx_blob, tx); - THROW_IF_TRUE_WALLET_EX(!r, error::tx_parse_error, tx_blob); - crypto::hash tx_hash = currency::get_transaction_hash(tx); - - ss << tx_hash << ENDL; - } - ss << "UNCONFIRMED TXS: " << ENDL; - for (const auto &tx_it : m_unconfirmed_in_transfers) - { - ss << tx_it.first << ENDL; - } - std::string config_tx = ss.str(); -#endif - //- @#@ ----- debug - - std::unordered_map unconfirmed_in_transfers_local(std::move(m_unconfirmed_in_transfers)); - multisig_entries_map unconfirmed_multisig_transfers_from_tx_pool; - - has_related_alias_in_unconfirmed = false; - uint64_t tx_expiration_ts_median = res.tx_expiration_ts_median; //get_tx_expiration_median(); - for (const auto &tx_blob : res.txs) - { - currency::transaction tx; - //money_transfer2_details td; - process_transaction_context ptc(tx); - ptc.tx_expiration_ts_median = tx_expiration_ts_median; - ptc.pmultisig_entries = &unconfirmed_multisig_transfers_from_tx_pool; - bool r = parse_and_validate_tx_from_blob(tx_blob, tx); - THROW_IF_TRUE_WALLET_EX(!r, error::tx_parse_error, tx_blob); - has_related_alias_in_unconfirmed |= has_related_alias_entry_unconfirmed(tx); - - //crypto::hash tx_hash = currency::get_transaction_hash(tx); - auto it = unconfirmed_in_transfers_local.find(ptc.tx_hash()); - if (it != unconfirmed_in_transfers_local.end()) - { - m_unconfirmed_in_transfers.insert(*it); - continue; - } - - handle_unconfirmed_tx(ptc); - } - - // Compare unconfirmed multisigs containers - // IF EXISTS IN unconfirmed_multisig_transfers_in_tx_pool AND EXISTS IN m_unconfirmed_multisig_transfers => already processed, do nothing - // IF EXISTS IN unconfirmed_multisig_transfers_in_tx_pool AND NOT EXISTS IN m_unconfirmed_multisig_transfers => new unconfirmed, add to m_, mark as spent and nofity (see code above) - // IF NOT EXISTS IN unconfirmed_multisig_transfers_in_tx_pool AND EXISTS IN m_unconfirmed_multisig_transfers => EITHER became confirmed (added to the blockchain) OR removed from the pool for some other reason (clear spent flag if there's spent height == 0, means wasn't added to the blockchain) - - std::unordered_set unconfirmed_in_multisig_transfers; - for(auto& el : m_unconfirmed_in_transfers) - for(auto &in : el.second.vin) - if (in.type() == typeid(txin_multisig)) - unconfirmed_in_multisig_transfers.insert(boost::get(in).multisig_out_id); - - for (auto& multisig_id : m_unconfirmed_multisig_transfers) - { - if (unconfirmed_multisig_transfers_from_tx_pool.count(multisig_id) != 0) - continue; - - if (unconfirmed_in_multisig_transfers.count(multisig_id) != 0) - continue; - - // Process unconfirmed tx dissapeared from the pool - auto it = m_multisig_transfers.find(multisig_id); - if (it != m_multisig_transfers.end() && it->second.m_flags&WALLET_TRANSFER_DETAIL_FLAG_SPENT) - { - if (it->second.m_spent_height == 0) - { - // Looks like this tx didn't get into the blockchain, just removed from the pool for some reason. - // So, clear spent flag. - uint32_t flags_before = it->second.m_flags; - it->second.m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_SPENT); - WLT_LOG_L0("Unconfirmed multisig out with id: " << multisig_id << " was presiously marked as spent and now seems to be removed from the pool, while still not added to the blockchain. Marked as NOT SPENT" << ENDL - << "ms source tx: " << (it->second.m_ptx_wallet_info != nullptr ? get_transaction_hash(it->second.m_ptx_wallet_info->m_tx) : null_hash) << " flags: " << flags_before << " -> " << it->second.m_flags); - } - } - } - - // Populate updated unconfirmed list of multisign transfers - m_unconfirmed_multisig_transfers.clear(); - for (auto& p : unconfirmed_multisig_transfers_from_tx_pool) - m_unconfirmed_multisig_transfers.insert(p.first); -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::on_idle() -{ - scan_not_compliant_unconfirmed_txs(); - return true; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::scan_not_compliant_unconfirmed_txs() -{ - uint64_t tx_expiration_ts_median = get_tx_expiration_median(); - uint64_t time_limit = m_core_runtime_config.get_core_time() - CURRENCY_MEMPOOL_TX_LIVETIME; - for (auto it = m_unconfirmed_txs.begin(); it != m_unconfirmed_txs.end(); ) - { - bool remove_this_tx = false; - std::stringstream reason_ss; - if (it->second.timestamp < time_limit) - { - remove_this_tx = true; - reason_ss << "outdated, "; - } - if (is_tx_expired(it->second.tx, tx_expiration_ts_median)) - { - remove_this_tx = true; - reason_ss << "expired, "; - } - if (is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM) && it->second.tx.version < TRANSACTION_VERSION_POST_HF4) - { - remove_this_tx = true; - reason_ss << "not compliant with HF4, "; - } - - if (remove_this_tx) - { - WLT_LOG_BLUE("removing unconfirmed tx " << it->second.tx_hash << ", reason: " << reason_ss.str() << "tx_expiration_ts_median=" << tx_expiration_ts_median, LOG_LEVEL_0); - //lookup all used transfer and update flags - for (auto i : it->second.selected_indicies) - { - if (i >= m_transfers.size()) - { - WLT_LOG_ERROR("Wrong index '" << i << "' in 'selected_indicies', while m_transfers.size() = " << m_transfers.size()); + // own output was used in non-direct transaction + // the core should not allow this to happen, the only way it may happen - mixing in own output that was sent without mix_attr == 1 + // log strange situation + std::stringstream ss; + ss << "own transfer tid=" << tid << " tx=" << td.tx_hash() << " mix_attr=" << td.mix_attr() << ", is referenced by a transaction with mixins, ref from input with amount: " << amount << ", gindex: " << gindex; + WLT_LOG_YELLOW(ss.str(), LOG_LEVEL_0); + if (m_wcallback) + m_wcallback->on_message(i_wallet2_callback::ms_yellow, ss.str()); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.mix_attr() != CURRENCY_TO_KEY_OUT_FORCED_NO_MIX, ss.str()); // if mix_attr == 1 this should never happen (mixing in an output with mix_attr = 1) as the core must reject such txs + // our own output has mix_attr != 1 for some reason (a sender did not set correct mix_attr e.g.) + // but mixin count > 1 so we can't say it is spent for sure + tid = UINT64_MAX; continue; } - if (!m_transfers[i].m_spent_height) - { - uint32_t flags_before = m_transfers[i].m_flags; - m_transfers[i].m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_SPENT); // TODO: consider removing other blocking flags (e.g. for escrow tx) -- sowle - WLT_LOG_BLUE("mark transfer #" << i << " as unspent, flags: " << flags_before << " -> " << m_transfers[i].m_flags << ", reason: removing unconfirmed tx " << it->second.tx_hash, LOG_LEVEL_0); - } - } - //fire some event - m_wcallback->on_transfer_canceled(it->second); - m_unconfirmed_txs.erase(it++); - } - else - { - it++; - } - } - - //scan marked as spent but don't have height (unconfirmed, and actually not unconfirmed) - std::unordered_set ki_in_unconfirmed; - for (auto it = m_unconfirmed_txs.begin(); it != m_unconfirmed_txs.end(); it++) - { - if (!it->second.has_outgoing_entries()) - continue; - - for (auto& in_v : it->second.tx.vin) - { - crypto::key_image ki{}; - if (get_key_image_from_txin_v(in_v, ki)) - ki_in_unconfirmed.insert(ki); - } - } - - size_t sz = m_transfers.size(); - for (size_t i = 0; i != sz; i++) - { - auto& t = m_transfers[i]; - - if (t.m_flags&WALLET_TRANSFER_DETAIL_FLAG_SPENT && !t.m_spent_height && !static_cast(t.m_flags&WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION) - && !t.is_htlc()) - { - //check if there is unconfirmed for this transfer is no longer exist? - if (!ki_in_unconfirmed.count((t.m_key_image))) - { - uint32_t flags_before = t.m_flags; - t.m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_SPENT); - WLT_LOG_BLUE("Transfer [" << i << "] marked as unspent, flags: " << flags_before << " -> " << t.m_flags << ", reason: there is no unconfirmed tx relataed to this key image", LOG_LEVEL_0); - } - } - } - - return true; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::refresh(size_t & blocks_fetched, bool& received_money, std::atomic& stop) -{ - load_whitelisted_tokens_if_not_loaded(); - - received_money = false; - blocks_fetched = 0; - size_t added_blocks = 0; - size_t try_count = 0; - crypto::hash last_tx_hash_id = m_transfers.size() ? get_transaction_hash(m_transfers.back().m_ptx_wallet_info->m_tx) : null_hash; - m_height_of_start_sync = get_blockchain_current_size(); - m_last_sync_percent = 0; - while (!stop.load(std::memory_order_relaxed)) - { - try - { - pull_blocks(added_blocks, stop); - blocks_fetched += added_blocks; - if(!added_blocks) + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.m_spent_height == 0, "transfer is spent in blockchain, tid: " << tid << ", ref from input with amount: " << amount << ", gindex: " << gindex); + // okay, own output is being spent, return it break; + } } - catch (error::no_connection_to_daemon&) - { - blocks_fetched += added_blocks; - if (++try_count > 3) - return; - WLT_LOG_L2("no connection to the daemon, wait and try pull_blocks again (try_count: " << try_count << ", blocks_fetched: " << blocks_fetched << ")"); - if (m_wcallback) - m_wcallback->on_message(tools::i_wallet2_callback::ms_red, "no connection to daemon"); - std::this_thread::sleep_for(std::chrono::seconds(3)); - } - catch (const std::exception& e) - { - blocks_fetched += added_blocks; - WLT_LOG_ERROR("refresh->pull_blocks failed, try_count: " << try_count << ", blocks_fetched: " << blocks_fetched << ", exception: " << e.what()); - if (m_wcallback) - m_wcallback->on_message(tools::i_wallet2_callback::ms_red, std::string("error on pulling blocks: ") + e.what()); - return; - } + return tid; } - - if(last_tx_hash_id != (m_transfers.size() ? get_transaction_hash(m_transfers.back().m_ptx_wallet_info->m_tx) : null_hash)) - received_money = true; - - if (blocks_fetched) + //---------------------------------------------------------------------------------------------------- + void wallet2::handle_unconfirmed_tx(process_transaction_context& ptc) { - on_idle(); + const transaction& tx = ptc.tx; + ptc.timestamp = m_core_runtime_config.get_core_time(); + // read extra + std::vector outs; + //uint64_t sum_of_received_native_outs = 0; + crypto::public_key tx_pub_key = null_pkey; + bool r = parse_and_validate_tx_extra(tx, tx_pub_key); + THROW_IF_TRUE_WALLET_EX(!r, error::tx_extra_parse_error, tx); + //check if we have money + crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); + r = lookup_acc_outs(m_account.get_keys(), tx, tx_pub_key, outs, derivation); + THROW_IF_TRUE_WALLET_EX(!r, error::acc_outs_lookup_error, tx, tx_pub_key, m_account.get_keys()); - uint64_t tx_expiration_ts_median = get_tx_expiration_median(); - handle_expiration_list(tx_expiration_ts_median); - handle_contract_expirations(tx_expiration_ts_median); - - m_found_free_amounts.clear(); - } - - - WLT_LOG("Refresh done, blocks received: " << blocks_fetched, blocks_fetched > 0 ? LOG_LEVEL_1 : LOG_LEVEL_2); -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::handle_expiration_list(uint64_t tx_expiration_ts_median) -{ - for (auto it = m_money_expirations.begin(); it != m_money_expirations.end(); ) - { - if (it->expiration_time < TX_EXPIRATION_MEDIAN_SHIFT || tx_expiration_ts_median > it->expiration_time - TX_EXPIRATION_MEDIAN_SHIFT) + //collect incomes + for (auto& o : outs) { - for (auto tr_ind : it->selected_transfers) + if (out_is_multisig(tx.vout[o.index])) + continue; + + ptc.total_balance_change[o.asset_id] += o.amount; + ptc.employed_entries.receive.push_back(wallet_public::employed_tx_entry{ o.index, o.amount, o.asset_id }); + } + + + bool new_multisig_spend_detected = false; + //check if we have spendings + //uint64_t sum_of_spent_native_coin = 0; + std::list spend_transfers; + // check all outputs for spending (compare key images) + for (size_t i = 0; i != tx.vin.size(); i++) + { + auto& in = tx.vin[i]; + if (in.type() == typeid(currency::txin_to_key)) { - auto &transfer = m_transfers[tr_ind]; - if (!transfer.m_spent_height) + const currency::txin_to_key& intk = boost::get(in); + uint64_t tid = UINT64_MAX; + if (is_auditable() && is_watch_only()) { - // Clear WALLET_TRANSFER_DETAIL_FLAG_BLOCKED and WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION flags only. - // Note: transfer may still be marked as spent - uint32_t flags_before = transfer.m_flags; - transfer.m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_BLOCKED); - transfer.m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION); - WLT_LOG_GREEN("Unlocked money from expiration_list: transfer #" << tr_ind << ", flags: " << flags_before << " -> " << transfer.m_flags << ", amount: " << print_money(transfer.amount()) << ", tx: " << - (transfer.m_ptx_wallet_info != nullptr ? get_transaction_hash(transfer.m_ptx_wallet_info->m_tx) : null_hash), LOG_LEVEL_0); + // tracking wallet, assuming all outputs are spent directly because of mix_attr = 1 + tid = get_directly_spent_transfer_index_by_input_in_tracking_wallet(intk.amount, intk.key_offsets); + } + else + { + // wallet with spend secret key -- we can calculate own key images and then search among them + auto it = m_key_images.find(intk.k_image); + if (it != m_key_images.end()) + { + tid = it->second; + } } + if (tid != UINT64_MAX) + { + // own output is being spent by this input + //sum_of_spent_native_coin += intk.amount; + ptc.employed_entries.spent.push_back(wallet_public::employed_tx_entry{ i, m_transfers.at(tid).amount(), m_transfers.at(tid).get_asset_id() }); + spend_transfers.push_back(tid); + ptc.total_balance_change[currency::native_coin_asset_id] -= m_transfers.at(tid).amount(); + CHECK_AND_ASSERT_THROW_MES(m_transfers.at(tid).get_asset_id() == currency::native_coin_asset_id, "Unexpected asset id for native txin_to_key"); + } + } + else if (in.type() == typeid(currency::txin_zc_input)) + { + // bad design -- remove redundancy like using wallet2::process_input_t() + const currency::txin_zc_input& zc = boost::get(in); + uint64_t tid = UINT64_MAX; + if (is_auditable() && is_watch_only()) + { + // tracking wallet, assuming all outputs are spent directly because of mix_attr = 1 + tid = get_directly_spent_transfer_index_by_input_in_tracking_wallet(zc); + } + else + { + // wallet with spend secret key -- we can calculate own key images and then search among them + auto it = m_key_images.find(zc.k_image); + if (it != m_key_images.end()) + { + tid = it->second; + } + } + + if (tid != UINT64_MAX) + { + ptc.employed_entries.spent.push_back(wallet_public::employed_tx_entry{ i, m_transfers.at(tid).amount(), m_transfers.at(tid).get_asset_id() }); + spend_transfers.push_back(tid); + ptc.total_balance_change[m_transfers.at(tid).get_asset_id()] -= m_transfers.at(tid).amount(); + } + } + else if (in.type() == typeid(currency::txin_multisig)) + { + crypto::hash multisig_id = boost::get(in).multisig_out_id; + auto it = m_multisig_transfers.find(multisig_id); + if (it != m_multisig_transfers.end()) + { + //ptc.employed_entries.spent_indices.push_back(i); + ptc.employed_entries.spent.push_back(wallet_public::employed_tx_entry{ i }); + if (ptc.pmultisig_entries) + { + r = ptc.pmultisig_entries->insert(std::make_pair(multisig_id, std::make_pair(tx, ptc.employed_entries))).second; + if (!r) + { + WLT_LOG_RED("Warning: Receiving the same multisig out id from tx pool more then once: " << multisig_id, LOG_LEVEL_0); + } + } + if (m_unconfirmed_multisig_transfers.count(multisig_id) == 0) + { + // new unconfirmed multisig (see also comments on multisig tranafers handling below) + uint32_t flags_before = it->second.m_flags; + it->second.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; // mark as spent + it->second.m_spent_height = 0; // height 0 means unconfirmed + WLT_LOG_L0("From tx pool got new unconfirmed multisig out with id: " << multisig_id << " tx: " << get_transaction_hash(tx) << " Marked as SPENT, flags: " << flags_before << " -> " << it->second.m_flags); + new_multisig_spend_detected = true; + } + + } } - WLT_LOG_GREEN("expiration_list entry removed by median: " << tx_expiration_ts_median << ", expiration time: " << it->expiration_time << ", related tx: " << it->related_tx_id, LOG_LEVEL_0); - it = m_money_expirations.erase(it); } - else + + //do final calculations + bool has_in_transfers = false; + bool has_out_transfers = false; + for (const auto& bce : ptc.total_balance_change) { - it++; + if (bce.second > 0) + { + has_in_transfers = true; + } + else if (bce.second < 0) + { + has_out_transfers = true; + } + } + if (!is_tx_expired(tx, ptc.tx_expiration_ts_median) && (new_multisig_spend_detected || has_in_transfers || has_out_transfers || (currency::is_derivation_used_to_encrypt(tx, derivation)))) + { + m_unconfirmed_in_transfers[ptc.tx_hash()] = tx; + if (m_unconfirmed_txs.count(ptc.tx_hash())) + return; + + //prepare notification about pending transaction + wallet_public::wallet_transfer_info& unconfirmed_wti = misc_utils::get_or_insert_value_initialized(m_unconfirmed_txs, ptc.tx_hash()); + prepare_wti(unconfirmed_wti, ptc); + for (auto tr_index : spend_transfers) + { + //if (tr_index > m_transfers.size()) + //{ + // WLT_LOG_ERROR("INTERNAL ERROR: tr_index " << tr_index << " more then m_transfers.size()=" << m_transfers.size()); + // continue; + //} + uint32_t flags_before = m_transfers.at(tr_index).m_flags; + m_transfers.at(tr_index).m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; + WLT_LOG_L1("wallet transfer #" << tr_index << " is marked as spent, flags: " << flags_before << " -> " << m_transfers.at(tr_index).m_flags << ", reason: UNCONFIRMED tx: " << ptc.tx_hash()); + unconfirmed_wti.selected_indicies.push_back(tr_index); + } + rise_on_transfer2(unconfirmed_wti); } } - return true; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::handle_contract_expirations(uint64_t tx_expiration_ts_median) -{ - for (auto& contract : m_contracts) + + + void wallet2::scan_tx_pool(bool& has_related_alias_in_unconfirmed) { - switch (contract.second.state) + //get transaction pool content + currency::COMMAND_RPC_GET_TX_POOL::request req = AUTO_VAL_INIT(req); + currency::COMMAND_RPC_GET_TX_POOL::response res = AUTO_VAL_INIT(res); + bool r = m_core_proxy->call_COMMAND_RPC_GET_TX_POOL(req, res); + if (res.status == API_RETURN_CODE_BUSY) + throw error::daemon_busy(LOCATION_STR, "get_tx_pool"); + if (!r) + throw error::no_connection_to_daemon(LOCATION_STR, "get_tx_pool"); + THROW_IF_TRUE_WALLET_EX(res.status != API_RETURN_CODE_OK, error::get_blocks_error, res.status); + + + //- @#@ ----- debug +#ifdef _DEBUG + std::stringstream ss; + ss << "TXS FROM POOL: " << ENDL; + for (const auto& tx_blob : res.txs) { + currency::transaction tx; + bool r = parse_and_validate_tx_from_blob(tx_blob, tx); + THROW_IF_TRUE_WALLET_EX(!r, error::tx_parse_error, tx_blob); + crypto::hash tx_hash = currency::get_transaction_hash(tx); + + ss << tx_hash << ENDL; + } + ss << "UNCONFIRMED TXS: " << ENDL; + for (const auto& tx_it : m_unconfirmed_in_transfers) + { + ss << tx_it.first << ENDL; + } + std::string config_tx = ss.str(); +#endif + //- @#@ ----- debug + + std::unordered_map unconfirmed_in_transfers_local(std::move(m_unconfirmed_in_transfers)); + multisig_entries_map unconfirmed_multisig_transfers_from_tx_pool; + + has_related_alias_in_unconfirmed = false; + uint64_t tx_expiration_ts_median = res.tx_expiration_ts_median; //get_tx_expiration_median(); + for (const auto& tx_blob : res.txs) + { + currency::transaction tx; + //money_transfer2_details td; + process_transaction_context ptc(tx); + ptc.tx_expiration_ts_median = tx_expiration_ts_median; + ptc.pmultisig_entries = &unconfirmed_multisig_transfers_from_tx_pool; + bool r = parse_and_validate_tx_from_blob(tx_blob, tx); + THROW_IF_TRUE_WALLET_EX(!r, error::tx_parse_error, tx_blob); + has_related_alias_in_unconfirmed |= has_related_alias_entry_unconfirmed(tx); + + //crypto::hash tx_hash = currency::get_transaction_hash(tx); + auto it = unconfirmed_in_transfers_local.find(ptc.tx_hash()); + if (it != unconfirmed_in_transfers_local.end()) + { + m_unconfirmed_in_transfers.insert(*it); + continue; + } + + handle_unconfirmed_tx(ptc); + } + + // Compare unconfirmed multisigs containers + // IF EXISTS IN unconfirmed_multisig_transfers_in_tx_pool AND EXISTS IN m_unconfirmed_multisig_transfers => already processed, do nothing + // IF EXISTS IN unconfirmed_multisig_transfers_in_tx_pool AND NOT EXISTS IN m_unconfirmed_multisig_transfers => new unconfirmed, add to m_, mark as spent and nofity (see code above) + // IF NOT EXISTS IN unconfirmed_multisig_transfers_in_tx_pool AND EXISTS IN m_unconfirmed_multisig_transfers => EITHER became confirmed (added to the blockchain) OR removed from the pool for some other reason (clear spent flag if there's spent height == 0, means wasn't added to the blockchain) + + std::unordered_set unconfirmed_in_multisig_transfers; + for (auto& el : m_unconfirmed_in_transfers) + for (auto& in : el.second.vin) + if (in.type() == typeid(txin_multisig)) + unconfirmed_in_multisig_transfers.insert(boost::get(in).multisig_out_id); + + for (auto& multisig_id : m_unconfirmed_multisig_transfers) + { + if (unconfirmed_multisig_transfers_from_tx_pool.count(multisig_id) != 0) + continue; + + if (unconfirmed_in_multisig_transfers.count(multisig_id) != 0) + continue; + + // Process unconfirmed tx dissapeared from the pool + auto it = m_multisig_transfers.find(multisig_id); + if (it != m_multisig_transfers.end() && it->second.m_flags & WALLET_TRANSFER_DETAIL_FLAG_SPENT) + { + if (it->second.m_spent_height == 0) + { + // Looks like this tx didn't get into the blockchain, just removed from the pool for some reason. + // So, clear spent flag. + uint32_t flags_before = it->second.m_flags; + it->second.m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_SPENT); + WLT_LOG_L0("Unconfirmed multisig out with id: " << multisig_id << " was presiously marked as spent and now seems to be removed from the pool, while still not added to the blockchain. Marked as NOT SPENT" << ENDL + << "ms source tx: " << (it->second.m_ptx_wallet_info != nullptr ? get_transaction_hash(it->second.m_ptx_wallet_info->m_tx) : null_hash) << " flags: " << flags_before << " -> " << it->second.m_flags); + } + } + } + + // Populate updated unconfirmed list of multisign transfers + m_unconfirmed_multisig_transfers.clear(); + for (auto& p : unconfirmed_multisig_transfers_from_tx_pool) + m_unconfirmed_multisig_transfers.insert(p.first); + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::on_idle() + { + scan_not_compliant_unconfirmed_txs(); + return true; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::scan_not_compliant_unconfirmed_txs() + { + uint64_t tx_expiration_ts_median = get_tx_expiration_median(); + uint64_t time_limit = m_core_runtime_config.get_core_time() - CURRENCY_MEMPOOL_TX_LIVETIME; + for (auto it = m_unconfirmed_txs.begin(); it != m_unconfirmed_txs.end(); ) + { + bool remove_this_tx = false; + std::stringstream reason_ss; + if (it->second.timestamp < time_limit) + { + remove_this_tx = true; + reason_ss << "outdated, "; + } + if (is_tx_expired(it->second.tx, tx_expiration_ts_median)) + { + remove_this_tx = true; + reason_ss << "expired, "; + } + if (is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM) && it->second.tx.version < TRANSACTION_VERSION_POST_HF4) + { + remove_this_tx = true; + reason_ss << "not compliant with HF4, "; + } + + if (remove_this_tx) + { + WLT_LOG_BLUE("removing unconfirmed tx " << it->second.tx_hash << ", reason: " << reason_ss.str() << "tx_expiration_ts_median=" << tx_expiration_ts_median, LOG_LEVEL_0); + //lookup all used transfer and update flags + for (auto i : it->second.selected_indicies) + { + //if (i >= m_transfers.size()) + //{ + // WLT_LOG_ERROR("Wrong index '" << i << "' in 'selected_indicies', while m_transfers.size() = " << m_transfers.size()); + // continue; + //} + if (!m_transfers.at(i).m_spent_height) + { + uint32_t flags_before = m_transfers.at(i).m_flags; + m_transfers.at(i).m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_SPENT); // TODO: consider removing other blocking flags (e.g. for escrow tx) -- sowle + WLT_LOG_BLUE("mark transfer #" << i << " as unspent, flags: " << flags_before << " -> " << m_transfers.at(i).m_flags << ", reason: removing unconfirmed tx " << it->second.tx_hash, LOG_LEVEL_0); + } + } + //fire some event + m_wcallback->on_transfer_canceled(it->second); + m_unconfirmed_txs.erase(it++); + } + else + { + it++; + } + } + + //scan marked as spent but don't have height (unconfirmed, and actually not unconfirmed) + std::unordered_set ki_in_unconfirmed; + for (auto it = m_unconfirmed_txs.begin(); it != m_unconfirmed_txs.end(); it++) + { + if (!it->second.has_outgoing_entries()) + continue; + + for (auto& in_v : it->second.tx.vin) + { + crypto::key_image ki{}; + if (get_key_image_from_txin_v(in_v, ki)) + ki_in_unconfirmed.insert(ki); + } + } + + + for (auto& tr : m_transfers) + { + uint64_t i = tr.first; + auto& t = tr.second; + + if (t.m_flags & WALLET_TRANSFER_DETAIL_FLAG_SPENT && !t.m_spent_height && !static_cast(t.m_flags & WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION) + && !t.is_htlc()) + { + //check if there is unconfirmed for this transfer is no longer exist? + if (!ki_in_unconfirmed.count((t.m_key_image))) + { + uint32_t flags_before = t.m_flags; + t.m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_SPENT); + WLT_LOG_BLUE("Transfer [" << i << "] marked as unspent, flags: " << flags_before << " -> " << t.m_flags << ", reason: there is no unconfirmed tx relataed to this key image", LOG_LEVEL_0); + } + } + } + + return true; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::refresh(size_t& blocks_fetched, bool& received_money, std::atomic& stop) + { + load_whitelisted_tokens_if_not_loaded(); + + received_money = false; + blocks_fetched = 0; + size_t added_blocks = 0; + size_t try_count = 0; + crypto::hash last_tx_hash_id = m_transfers.size() ? get_transaction_hash((--m_transfers.end())->second.m_ptx_wallet_info->m_tx) : null_hash; + m_height_of_start_sync = get_blockchain_current_size(); + m_last_sync_percent = 0; + while (!stop.load(std::memory_order_relaxed)) + { + try + { + pull_blocks(added_blocks, stop); + blocks_fetched += added_blocks; + if (!added_blocks) + break; + } + catch (error::no_connection_to_daemon&) + { + blocks_fetched += added_blocks; + if (++try_count > 3) + return; + WLT_LOG_L2("no connection to the daemon, wait and try pull_blocks again (try_count: " << try_count << ", blocks_fetched: " << blocks_fetched << ")"); + if (m_wcallback) + m_wcallback->on_message(tools::i_wallet2_callback::ms_red, "no connection to daemon"); + std::this_thread::sleep_for(std::chrono::seconds(3)); + } + catch (const std::exception& e) + { + blocks_fetched += added_blocks; + WLT_LOG_ERROR("refresh->pull_blocks failed, try_count: " << try_count << ", blocks_fetched: " << blocks_fetched << ", exception: " << e.what()); + if (m_wcallback) + m_wcallback->on_message(tools::i_wallet2_callback::ms_red, std::string("error on pulling blocks: ") + e.what()); + return; + } + } + + if (last_tx_hash_id != (m_transfers.size() ? get_transaction_hash((--m_transfers.end())->second.m_ptx_wallet_info->m_tx) : null_hash)) + received_money = true; + + if (blocks_fetched) + { + on_idle(); + + uint64_t tx_expiration_ts_median = get_tx_expiration_median(); + handle_expiration_list(tx_expiration_ts_median); + handle_contract_expirations(tx_expiration_ts_median); + + m_found_free_amounts.clear(); + } + + + WLT_LOG("Refresh done, blocks received: " << blocks_fetched, blocks_fetched > 0 ? LOG_LEVEL_1 : LOG_LEVEL_2); + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::handle_expiration_list(uint64_t tx_expiration_ts_median) + { + for (auto it = m_money_expirations.begin(); it != m_money_expirations.end(); ) + { + if (it->expiration_time < TX_EXPIRATION_MEDIAN_SHIFT || tx_expiration_ts_median > it->expiration_time - TX_EXPIRATION_MEDIAN_SHIFT) + { + for (auto tr_ind : it->selected_transfers) + { + auto& transfer = m_transfers.at(tr_ind); + if (!transfer.m_spent_height) + { + // Clear WALLET_TRANSFER_DETAIL_FLAG_BLOCKED and WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION flags only. + // Note: transfer may still be marked as spent + uint32_t flags_before = transfer.m_flags; + transfer.m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_BLOCKED); + transfer.m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION); + WLT_LOG_GREEN("Unlocked money from expiration_list: transfer #" << tr_ind << ", flags: " << flags_before << " -> " << transfer.m_flags << ", amount: " << print_money(transfer.amount()) << ", tx: " << + (transfer.m_ptx_wallet_info != nullptr ? get_transaction_hash(transfer.m_ptx_wallet_info->m_tx) : null_hash), LOG_LEVEL_0); + } + + } + WLT_LOG_GREEN("expiration_list entry removed by median: " << tx_expiration_ts_median << ", expiration time: " << it->expiration_time << ", related tx: " << it->related_tx_id, LOG_LEVEL_0); + it = m_money_expirations.erase(it); + } + else + { + it++; + } + } + return true; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::handle_contract_expirations(uint64_t tx_expiration_ts_median) + { + for (auto& contract : m_contracts) + { + switch (contract.second.state) + { case tools::wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent: if (is_tx_expired(contract.second.cancel_body.tx_cancel_template, tx_expiration_ts_median)) change_contract_state(contract.second, tools::wallet_public::escrow_contract_details_basic::contract_accepted, contract.first, "cancel proposal expiration"); @@ -2945,800 +2951,813 @@ void wallet2::handle_contract_expirations(uint64_t tx_expiration_ts_median) if (contract.second.height == 0 && is_tx_expired(contract.second.cancel_body.tx_cancel_template, tx_expiration_ts_median)) change_contract_state(contract.second, tools::wallet_public::escrow_contract_details_basic::contract_accepted, contract.first, "cancel acceptance expiration"); break; + } } } -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::refresh(size_t & blocks_fetched, bool& received_money, bool& ok, std::atomic& stop) -{ - try + //---------------------------------------------------------------------------------------------------- + bool wallet2::refresh(size_t& blocks_fetched, bool& received_money, bool& ok, std::atomic& stop) { - refresh(blocks_fetched, received_money, stop); - ok = true; - } - catch (...) - { - ok = false; - } - return ok; -} - -//---------------------------------------------------------------------------------------------------- -uint64_t wallet2::detach_from_block_ids(uint64_t including_height) -{ - //calculate number of erased blocks - uint64_t blocks_detached = get_blockchain_current_size() - including_height; - //id at height should be kept, the rest - erased - m_chain.detach(including_height); - return blocks_detached; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::remove_transfer_from_amount_gindex_map(uint64_t tid) -{ - for (auto it = m_amount_gindex_to_transfer_id.begin(); it != m_amount_gindex_to_transfer_id.end(); ) - { - if (it->second == tid) - it = m_amount_gindex_to_transfer_id.erase(it); - else - ++it; - } -} -//---------------------------------------------------------------------------------------------------- -void wallet2::detach_blockchain(uint64_t including_height) -{ - WLT_LOG_L0("Detaching blockchain on height " << including_height); - size_t transfers_detached = 0; - - // rollback incoming transfers from detaching subchain - { - auto it = std::find_if(m_transfers.begin(), m_transfers.end(), [&](const transfer_details& td){return td.m_ptx_wallet_info->m_block_height >= including_height; }); - if (it != m_transfers.end()) + try { - size_t i_start = it - m_transfers.begin(); + refresh(blocks_fetched, received_money, stop); + ok = true; + } + catch (...) + { + ok = false; + } + return ok; + } - for (size_t i = i_start; i != m_transfers.size(); i++) + //---------------------------------------------------------------------------------------------------- + uint64_t wallet2::detach_from_block_ids(uint64_t including_height) + { + //calculate number of erased blocks + uint64_t blocks_detached = get_blockchain_current_size() - including_height; + //id at height should be kept, the rest - erased + m_chain.detach(including_height); + return blocks_detached; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::remove_transfer_from_amount_gindex_map(uint64_t tid) + { + for (auto it = m_amount_gindex_to_transfer_id.begin(); it != m_amount_gindex_to_transfer_id.end(); ) + { + if (it->second == tid) + it = m_amount_gindex_to_transfer_id.erase(it); + else + ++it; + } + } + //---------------------------------------------------------------------------------------------------- + void wallet2::detach_blockchain(uint64_t including_height) + { + WLT_LOG_L0("Detaching blockchain on height " << including_height); + size_t transfers_detached = 0; + + // rollback incoming transfers from detaching subchain + { + auto it = std::find_if(m_transfers.begin(), m_transfers.end(), [&](const transfer_details& td){return td.m_ptx_wallet_info->m_block_height >= including_height; }); + if (it != m_transfers.end()) { - //check for htlc - if (m_transfers[i].m_ptx_wallet_info->m_tx.vout[m_transfers[i].m_internal_output_index].type() == typeid(tx_out_bare) && - boost::get(m_transfers[i].m_ptx_wallet_info->m_tx.vout[m_transfers[i].m_internal_output_index]).target.type() == typeid(txout_htlc)) + size_t i_start = it - m_transfers.begin(); + + for (const auto& tr : m_transfers) { - //need to find an entry in m_htlc and remove it - const txout_htlc& hltc = boost::get(boost::get(m_transfers[i].m_ptx_wallet_info->m_tx.vout[m_transfers[i].m_internal_output_index]).target); - uint64_t expiration_height = m_transfers[i].m_ptx_wallet_info->m_block_height + hltc.expiration; - auto pair_of_it = m_htlcs.equal_range(expiration_height); - bool found = false; - for (auto it = pair_of_it.first; it != pair_of_it.second; it++) + uint64_t i = tr.first; + //check for htlc + if (m_transfers.at(i).m_ptx_wallet_info->m_tx.vout[m_transfers.at(i).m_internal_output_index].type() == typeid(tx_out_bare) && + boost::get(m_transfers.at(i).m_ptx_wallet_info->m_tx.vout[m_transfers.at(i).m_internal_output_index]).target.type() == typeid(txout_htlc)) { - if (it->second.transfer_index == i) + //need to find an entry in m_htlc and remove it + const txout_htlc& hltc = boost::get(boost::get(m_transfers.at(i).m_ptx_wallet_info->m_tx.vout[m_transfers.at(i).m_internal_output_index]).target); + uint64_t expiration_height = m_transfers.at(i).m_ptx_wallet_info->m_block_height + hltc.expiration; + auto pair_of_it = m_htlcs.equal_range(expiration_height); + bool found = false; + for (auto it = pair_of_it.first; it != pair_of_it.second; it++) { - found = true; - m_htlcs.erase(it); - break; + if (it->second.transfer_index == i) + { + found = true; + m_htlcs.erase(it); + break; + } + } + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(found, "Internal error: not found record in m_htlcs during rollback"); + } + + + if (!(m_transfers.at(i).m_key_image == null_ki && is_watch_only())) + { + auto it_ki = m_key_images.find(m_transfers.at(i).m_key_image); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it_ki != m_key_images.end(), "key image " << m_transfers.at(i).m_key_image << " not found"); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(m_transfers.at(i).m_ptx_wallet_info->m_block_height >= including_height, "transfer #" << i << " block height is less than " << including_height); + m_key_images.erase(it_ki); + } + remove_transfer_from_amount_gindex_map(i); + ++transfers_detached; + } + m_transfers.erase(it, m_transfers.end()); + } + } + + for (uint64_t i = get_top_block_height(); i != including_height - 1 && i != 0; i--) + { + unprocess_htlc_triggers_on_block_removed(i); + } + size_t blocks_detached = detach_from_block_ids(including_height); + + //rollback spends + // do not clear spent flag in spent transfers as corresponding txs are most likely in the pool + // they will be moved into m_unconfirmed_txs for clearing in future (if tx will expire of removed from pool) + for (const auto& tr : m_transfers) + { + uint64_t i = tr.first; + auto& tr = m_transfers.at(i); + if (tr.m_spent_height >= including_height) + { + WLT_LOG_BLUE("Transfer [" << i << "] spent height: " << tr.m_spent_height << " -> 0, reason: detaching blockchain", LOG_LEVEL_1); + tr.m_spent_height = 0; + //check if it's hltc contract + } + } + + //rollback tranfers history + auto tr_hist_it = m_transfer_history.rend(); + for (auto it = m_transfer_history.rbegin(); it != m_transfer_history.rend(); it++) + { + if (it->height < including_height) + break; + tr_hist_it = it; // note that tr_hist_it->height >= height + } + + if (tr_hist_it != m_transfer_history.rend()) + { + auto it_from = --tr_hist_it.base(); + // before removing wti from m_transfer_history put it into m_unconfirmed_txs as txs from detached blocks are most likely be moved into the pool + for (auto it = it_from; it != m_transfer_history.end(); ++it) + { + // skip coinbase txs as they are not expected to go into the pool + if (is_coinbase(it->tx)) + { + continue; + } + + if (!m_unconfirmed_txs.insert(std::make_pair(it->tx_hash, *it)).second) + { + WLT_LOG_ERROR("can't move wti from transfer history to unronfirmed txs because such it is already here, tx hash: " << it->tx_hash); + } + } + m_transfer_history.erase(it_from, m_transfer_history.end()); + } + + //rollback payments + for (auto it = m_payments.begin(); it != m_payments.end(); ) + { + if (including_height <= it->second.m_block_height) + it = m_payments.erase(it); + else + ++it; + } + + //detach in m_last_zc_global_indexs + while (m_last_zc_global_indexs.size() && including_height <= m_last_zc_global_indexs.begin()->first) + { + m_last_zc_global_indexs.erase(m_last_zc_global_indexs.begin()); + } + + //asset descriptors + handle_rollback_events(including_height); + + WLT_LOG_L0("Detached blockchain on height " << including_height << ", transfers detached " << transfers_detached << ", blocks detached " << blocks_detached); + } + //---------------------------------------------------------------------------------------------------- + void wallet2::operator()(const asset_register_event& e) + { + auto it = m_own_asset_descriptors.find(e.asset_id); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it != m_own_asset_descriptors.end(), "asset_id " << e.asset_id << "not found during rolling asset_register_event"); + m_own_asset_descriptors.erase(it); + } + void wallet2::operator()(const asset_update_event& e) + { + auto it = m_own_asset_descriptors.find(e.asset_id); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it != m_own_asset_descriptors.end(), "asset_id " << e.asset_id << "not found during rolling asset_update_event"); + it->second = e.own_context; + } + void wallet2::operator()(const asset_unown_event& e) + { + auto it = m_own_asset_descriptors.find(e.asset_id); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it == m_own_asset_descriptors.end(), "asset_id " << e.asset_id << "unexpectedly found during rolling asset_unown_event"); + m_own_asset_descriptors[e.asset_id] = e.own_context; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::handle_rollback_events(uint64_t including_height) + { + while (m_rollback_events.size() && m_rollback_events.back().first >= including_height) + { + boost::apply_visitor(*this, m_rollback_events.back().second); + m_rollback_events.pop_back(); + } + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::deinit() + { + m_wcallback.reset(); + return true; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::clear() + { + reset_all(); + return true; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::reset_all() + { + //static_cast(*this) = wallet2_base_state{}; + static_cast(*this).~wallet2_base_state(); + new (static_cast(this)) wallet2_base_state(); + return true; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::store_keys(std::string& buff, const std::string& password, wallet2::keys_file_data& keys_file_data, 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(acc, account_data); + WLT_CHECK_AND_ASSERT_MES(r, false, "failed to serialize wallet keys"); + + crypto::chacha8_key key; + crypto::generate_chacha8_key(password, key); + std::string cipher; + cipher.resize(account_data.size()); + keys_file_data.iv = crypto::rand(); + crypto::chacha8(account_data.data(), account_data.size(), key, keys_file_data.iv, &cipher[0]); + keys_file_data.account_data = cipher; + + r = ::serialization::dump_binary(keys_file_data, buff); + + return true; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::backup_keys(const std::string& path) + { + std::string buff; + wallet2::keys_file_data keys_file_data = AUTO_VAL_INIT(keys_file_data); + bool r = store_keys(buff, m_password, keys_file_data); + WLT_CHECK_AND_ASSERT_MES(r, false, "Failed to store keys"); + + r = file_io_utils::save_string_to_file(path, buff); + WLT_CHECK_AND_ASSERT_MES(r, false, "Failed to save_string_to_file at store keys"); + return true; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::reset_password(const std::string& pass) + { + m_password = pass; + return true; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::is_password_valid(const std::string& pass) + { + return pass == m_password; + } + //---------------------------------------------------------------------------------------------------- + namespace + { + bool verify_keys(const crypto::secret_key& sec, const crypto::public_key& expected_pub) + { + crypto::public_key pub; + bool r = crypto::secret_key_to_public_key(sec, pub); + return r && expected_pub == pub; + } + } + //---------------------------------------------------------------------------------------------------- + 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_T(out_key_to_ki); + 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, "UNRECOVERABLE ERROR, wallet stops: m_pending_key_images > m_pending_key_images_file_container" << ENDL << "Missing/wrong " << string_encoding::convert_to_ansii(m_pending_ki_file) << " file?"); + } + } + //---------------------------------------------------------------------------------------------------- + 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, uint64_t file_signature, keys_file_data& kf_data) + { + bool r = false; + if (file_signature == WALLET_FILE_SIGNATURE_OLD) + { + wallet2::keys_file_data_old kf_data_old; + r = ::serialization::parse_binary(buff, kf_data_old); + kf_data = wallet2::keys_file_data::from_old(kf_data_old); + } + else if (file_signature == WALLET_FILE_SIGNATURE_V2) + { + r = ::serialization::parse_binary(buff, kf_data); + } + THROW_IF_TRUE_WALLET_EX(!r, error::wallet_internal_error, "internal error: failed to deserialize"); + + crypto::chacha8_key key; + crypto::generate_chacha8_key(password, key); + std::string account_data; + account_data.resize(kf_data.account_data.size()); + crypto::chacha8(kf_data.account_data.data(), kf_data.account_data.size(), key, kf_data.iv, &account_data[0]); + + 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.view_secret_key, keys.account_address.view_public_key); + if (keys.spend_secret_key == currency::null_skey) + m_watch_only = true; + else + r = r && verify_keys(keys.spend_secret_key, keys.account_address.spend_public_key); + if (!r) + { + WLT_LOG_L0("Wrong password for wallet " << string_encoding::convert_to_ansii(m_wallet_file)); + tools::error::throw_wallet_ex(std::string(__FILE__ ":" STRINGIZE(__LINE__))); + } + init_log_prefix(); + } + //---------------------------------------------------------------------------------------------------- + void wallet2::assign_account(const currency::account_base& acc) + { + clear(); + m_account = acc; + init_log_prefix(); + if (m_account.is_watch_only()) + m_watch_only = true; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::generate(const std::wstring& path, const std::string& pass, bool auditable_wallet) + { + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(validate_password(pass), "new wallet generation failed: password contains forbidden characters") + clear(); + prepare_file_names(path); + + m_password = pass; + m_account.generate(auditable_wallet); + 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)); + if (m_watch_only && !auditable_wallet) + { + bool stub; + load_keys2ki(true, stub); + } + store(); + } + //---------------------------------------------------------------------------------------------------- + void wallet2::restore(const std::wstring& path, const std::string& pass, const std::string& seed_or_tracking_seed, bool tracking_wallet, const std::string& seed_password) + { + bool r = false; + clear(); + prepare_file_names(path); + m_password = pass; + + if (tracking_wallet) + { + r = m_account.restore_from_tracking_seed(seed_or_tracking_seed); + init_log_prefix(); + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "Could not load tracking wallet from a given seed: invalid tracking seed"); + m_watch_only = true; + } + else + { + r = m_account.restore_from_seed_phrase(seed_or_tracking_seed, seed_password); + init_log_prefix(); + THROW_IF_FALSE_WALLET_EX(r, error::wallet_wrong_seed_error, epee::string_encoding::convert_to_ansii(m_wallet_file)); + } + + 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)); + store(); + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::check_connection() + { + return m_core_proxy->check_connection(); + } + //---------------------------------------------------------------------------------------------------- + void wallet2::set_votes_config_path(const std::string& path_to_config_file/* = tools::get_default_data_dir() + "\voting_config.json"*/) + { + m_votes_config_path = path_to_config_file; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::load_votes_config() + { + if (boost::filesystem::exists(m_votes_config_path)) + { + epee::serialization::load_t_from_json_file(m_votes_config, m_votes_config_path); + } + } + //---------------------------------------------------------------------------------------------------- + void wallet2::load(const std::wstring& wallet_, const std::string& password) + { + clear(); + prepare_file_names(wallet_); + + m_password = password; + + std::string keys_buff; + std::string body_buff; + + + boost::system::error_code e; + bool exists = boost::filesystem::exists(m_wallet_file, e); + THROW_IF_TRUE_WALLET_EX(e || !exists, error::file_not_found, epee::string_encoding::convert_to_ansii(m_wallet_file)); + boost::filesystem::ifstream data_file; + data_file.open(m_wallet_file, std::ios_base::binary | std::ios_base::in); + THROW_IF_TRUE_WALLET_EX(data_file.fail(), error::file_read_error, epee::string_encoding::convert_to_ansii(m_wallet_file)); + + wallet_file_binary_header wbh = AUTO_VAL_INIT(wbh); + + data_file.read((char*)&wbh, sizeof(wbh)); + THROW_IF_TRUE_WALLET_EX(data_file.fail(), error::file_read_error, epee::string_encoding::convert_to_ansii(m_wallet_file)); + + THROW_IF_TRUE_WALLET_EX(wbh.m_signature != WALLET_FILE_SIGNATURE_OLD && wbh.m_signature != WALLET_FILE_SIGNATURE_V2, error::file_read_error, epee::string_encoding::convert_to_ansii(m_wallet_file)); + THROW_IF_TRUE_WALLET_EX( + wbh.m_cb_keys > WALLET_FILE_MAX_KEYS_SIZE, error::file_read_error, epee::string_encoding::convert_to_ansii(m_wallet_file)); + + + keys_buff.resize(wbh.m_cb_keys); + data_file.read((char*)keys_buff.data(), wbh.m_cb_keys); + wallet2::keys_file_data kf_data = AUTO_VAL_INIT(kf_data); + load_keys(keys_buff, password, wbh.m_signature, kf_data); + + bool need_to_resync = false; + if (wbh.m_ver == WALLET_FILE_BINARY_HEADER_VERSION_INITAL) + { + // old WALLET_FILE_BINARY_HEADER_VERSION version means no encryption + need_to_resync = !tools::portable_unserialize_obj_from_stream(*this, data_file); + WLT_LOG_L1("Detected format: WALLET_FILE_BINARY_HEADER_VERSION_INITAL (need_to_resync=" << need_to_resync << ")"); + } + else if (wbh.m_ver == WALLET_FILE_BINARY_HEADER_VERSION_2) + { + tools::encrypt_chacha_in_filter decrypt_filter(password, kf_data.iv); + boost::iostreams::filtering_istream in; + in.push(decrypt_filter); + in.push(data_file); + need_to_resync = !tools::portable_unserialize_obj_from_stream(*this, in); + WLT_LOG_L1("Detected format: WALLET_FILE_BINARY_HEADER_VERSION_2 (need_to_resync=" << need_to_resync << ")"); + } + else + { + WLT_LOG_L0("Unknown wallet body version(" << wbh.m_ver << "), resync initiated."); + need_to_resync = true; + } + + + + if (m_watch_only && !is_auditable()) + load_keys2ki(true, need_to_resync); + + boost::system::error_code ec = AUTO_VAL_INIT(ec); + m_current_wallet_file_size = boost::filesystem::file_size(wallet_, ec); + + 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() + << ", file_size: " << m_current_wallet_file_size + << ", blockchain_size: " << m_chain.get_blockchain_current_size() + ); + WLT_LOG_L1("[LOADING]Blockchain shortener state: " << ENDL << m_chain.get_internal_state_text()); + + load_votes_config(); + + WLT_LOG_L1("(after loading: pending_key_images: " << m_pending_key_images.size() << ", pki file elements: " << m_pending_key_images_file_container.size() << ", tx_keys: " << m_tx_keys.size() << ")"); + + if (need_to_resync) + { + reset_history(); + WLT_LOG_L0("Unable to load history data from wallet file, wallet will be resynced!"); + } + + THROW_IF_TRUE_WALLET_EX(need_to_resync, error::wallet_load_notice_wallet_restored, epee::string_encoding::convert_to_ansii(m_wallet_file)); + } + //---------------------------------------------------------------------------------------------------- + void wallet2::store() + { + store(m_wallet_file, m_password); + } + //---------------------------------------------------------------------------------------------------- + void wallet2::store(const std::wstring& path) + { + store(path, m_password); + } + //---------------------------------------------------------------------------------------------------- + void wallet2::store(const std::wstring& path_to_save, const std::string& password) + { + LOG_PRINT_L0("(before storing: pending_key_images: " << m_pending_key_images.size() << ", pki file elements: " << m_pending_key_images_file_container.size() << ", tx_keys: " << m_tx_keys.size() << ")"); + + std::string ascii_path_to_save = epee::string_encoding::convert_to_ansii(path_to_save); + + //prepare data + std::string keys_buff; + wallet2::keys_file_data keys_file_data = AUTO_VAL_INIT(keys_file_data); + bool r = store_keys(keys_buff, password, keys_file_data, m_watch_only); + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "failed to store_keys for wallet " << ascii_path_to_save); + + //store data + wallet_file_binary_header wbh = AUTO_VAL_INIT(wbh); + wbh.m_signature = WALLET_FILE_SIGNATURE_V2; + wbh.m_cb_keys = keys_buff.size(); + //@#@ change it to proper + wbh.m_ver = WALLET_FILE_BINARY_HEADER_VERSION_2; + std::string header_buff((const char*)&wbh, sizeof(wbh)); + + uint64_t ts = m_core_runtime_config.get_core_time(); + + // save to tmp file, then rename + boost::filesystem::path tmp_file_path = boost::filesystem::path(path_to_save); + tmp_file_path += L".newtmp_" + std::to_wstring(ts); + + boost::filesystem::ofstream data_file; + data_file.open(tmp_file_path, std::ios_base::binary | std::ios_base::out | std::ios::trunc); + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(!data_file.fail(), "failed to open binary wallet file for saving: " << tmp_file_path.string()); + data_file << header_buff << keys_buff; + + WLT_LOG_L0("Storing to temporary file " << tmp_file_path.string() << " ..."); + //creating encryption stream + tools::encrypt_chacha_out_filter decrypt_filter(m_password, keys_file_data.iv); + boost::iostreams::filtering_ostream out; + out.push(decrypt_filter); + out.push(data_file); + + r = tools::portble_serialize_obj_to_stream(*this, out); + if (!r) + { + data_file.close(); + boost::filesystem::remove(tmp_file_path); // remove tmp file if smth went wrong + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(false, "IO error while storing wallet to " << tmp_file_path.string() << " (portble_serialize_obj_to_stream failed)"); + } + + data_file.flush(); + data_file.close(); + boost::uintmax_t tmp_file_size = boost::filesystem::file_size(tmp_file_path); + WLT_LOG_L0("Stored successfully to temporary file " << tmp_file_path.string() << ", file size=" << tmp_file_size); + + WLT_LOG_L1("[LOADING]Blockchain shortener state: " << ENDL << m_chain.get_internal_state_text()); + + // for the sake of safety perform a double-renaming: wallet file -> old tmp, new tmp -> wallet file, remove old tmp + + boost::filesystem::path tmp_old_file_path = boost::filesystem::path(path_to_save); + tmp_old_file_path += L".oldtmp_" + std::to_wstring(ts); + + if (boost::filesystem::is_regular_file(path_to_save)) + { + boost::filesystem::rename(path_to_save, tmp_old_file_path); + WLT_LOG_L1("Renamed: " << ascii_path_to_save << " -> " << tmp_old_file_path.string()); + } + + boost::filesystem::rename(tmp_file_path, path_to_save); + WLT_LOG_L1("Renamed: " << tmp_file_path.string() << " -> " << ascii_path_to_save); + + if (boost::filesystem::remove(tmp_old_file_path)) + { + WLT_LOG_L1("Removed temporary file: " << tmp_old_file_path.string()); + } + + bool path_to_save_exists = boost::filesystem::is_regular_file(path_to_save); + bool tmp_file_path_exists = boost::filesystem::is_regular_file(tmp_file_path); + bool tmp_old_file_path_exists = boost::filesystem::is_regular_file(tmp_old_file_path); + + boost::system::error_code ec = AUTO_VAL_INIT(ec); + m_current_wallet_file_size = boost::filesystem::file_size(path_to_save, ec); + if (path_to_save_exists && !tmp_file_path_exists && !tmp_old_file_path_exists) + { + + WLT_LOG_L0("Wallet was successfully stored to " << ascii_path_to_save << ", file size=" << m_current_wallet_file_size + << " blockchain_size: " << m_chain.get_blockchain_current_size()); + } + else + { + WLT_LOG_ERROR("Wallet stroing to " << ascii_path_to_save << " might not be successfull: path_to_save_exists=" << path_to_save_exists << ", tmp_file_path_exists=" << tmp_file_path_exists << ", tmp_old_file_path_exists=" << tmp_old_file_path_exists); + throw tools::error::wallet_common_error(LOCATION_STR, "Wallet file storing might not be successfull. Please make sure you have backed up your seed phrase and check log for details."); + } + } + //---------------------------------------------------------------------------------------------------- + uint64_t wallet2::get_wallet_file_size()const + { + return m_current_wallet_file_size; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::set_use_deffered_global_outputs(bool use) + { + LOG_PRINT_L0("[DEFFERED_MODE]: " << use); + m_use_deffered_global_outputs = use; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::set_use_assets_whitelisting(bool use) + { + LOG_PRINT_L0("[ASSET_WHITELISTING_MODE]: " << use); + m_use_assets_whitelisting = use; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::store_watch_only(const std::wstring& path_to_save, const std::string& password) const + { + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(path_to_save != m_wallet_file, "trying to save watch-only wallet to the same wallet file!"); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(!m_watch_only, "saving watch-only wallet into a watch-only wallet is not allowed"); + + // prepare data for watch-only wallet + wallet2 wo; + // wallet2 wo(*this); copy-constructor is not working, so do a this serialization workaround + std::stringstream stream_buffer; + tools::portble_serialize_obj_to_stream(*this, stream_buffer); + tools::portable_unserialize_obj_from_stream(wo, stream_buffer); + + wo.m_watch_only = true; + wo.m_account = m_account; + wo.m_account.make_account_watch_only(); + wo.prepare_file_names(path_to_save); + + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(!boost::filesystem::exists(wo.m_wallet_file), "file " << epee::string_encoding::convert_to_ansii(wo.m_wallet_file) << " already exists"); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(!boost::filesystem::exists(wo.m_pending_ki_file), "file " << epee::string_encoding::convert_to_ansii(wo.m_pending_ki_file) << " already exists"); + + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(wo.m_pending_key_images.empty(), "pending key images is expected to be empty"); + if (!is_auditable()) + { + bool stub = false; + wo.load_keys2ki(true, stub); // to create outkey2ki file + } + + // populate pending key images for spent outputs (this will help to resync watch-only wallet) + for (const auto& tr : m_transfers) + { + + const auto& td = tr.second; + if (!td.is_spent()) + continue; // only spent transfers really need to be stored, because watch-only wallet will not be able to figure out they were spent otherwise + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.m_internal_output_index < td.m_ptx_wallet_info->m_tx.vout.size(), "invalid transfer #" << ti); + if (td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].type() != typeid(tx_out_bare)) + continue; + const currency::txout_target_v& out_t = boost::get(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]).target; + if (out_t.type() != typeid(currency::txout_to_key)) + continue; + const crypto::public_key& out_key = boost::get(out_t).key; + wo.m_pending_key_images.insert(std::make_pair(out_key, td.m_key_image)); + wo.m_pending_key_images_file_container.push_back(tools::out_key_to_ki{ out_key, td.m_key_image }); + WLT_LOG_L1("preparing watch-only wallet: added pending ki (" << out_key << ", " << td.m_key_image << ")"); + } + + // TODO additional clearing for watch-only wallet's data + + wo.store(path_to_save, password); + } + //---------------------------------------------------------------------------------------------------- + uint64_t wallet2::unlocked_balance() const + { + uint64_t stub = 0; + uint64_t unlocked_balance = 0; + balance(unlocked_balance, stub, stub, stub); + return unlocked_balance; + } + //---------------------------------------------------------------------------------------------------- + uint64_t wallet2::balance(uint64_t& unloked) const + { + uint64_t fake = 0; + return balance(unloked, fake, fake, fake); + } + //---------------------------------------------------------------------------------------------------- + uint64_t wallet2::balance(uint64_t& unlocked, uint64_t& awaiting_in, uint64_t& awaiting_out, uint64_t& mined, const crypto::public_key& asset_id /* = currency::native_coin_asset_id */) const + { + uint64_t total = 0; + unlocked = 0; + awaiting_in = 0; + awaiting_out = 0; + mined = 0; + std::unordered_map balances; + balance(balances, mined); + auto it = balances.find(asset_id); + if (it != balances.end()) + { + total = it->second.total; + unlocked = it->second.unlocked; + awaiting_in = it->second.awaiting_in; + awaiting_out = it->second.awaiting_out; + } + return total; + } + //---------------------------------------------------------------------------------------------------- + uint64_t wallet2::balance(const crypto::public_key& asset_id, uint64_t& unlocked) const + { + std::unordered_map balances; + uint64_t dummy; + balance(balances, dummy); + auto it = balances.find(asset_id); + if (it == balances.end()) + { + return 0; + } + unlocked = it->second.unlocked; + return it->second.total; + } + //---------------------------------------------------------------------------------------------------- + uint64_t wallet2::balance(const crypto::public_key& asset_id) const + { + uint64_t dummy = 0; + return balance(asset_id, dummy); + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::balance(std::unordered_map& balances, uint64_t& mined) const + { + mined = 0; + m_has_bare_unspent_outputs = false; + std::list items_to_remove; + + for (auto& td : m_transfers) + { + + if (td.is_spent()) + { + if (m_concise_mode && m_spent_height + WALLET_CONCISE_MODE_MAX_REORG_BLOCKS < m_chain.get_top_block_height()) + { + items_to_remove.push_back(td.first); + } + } + else if (td.is_spendable() || (td.is_reserved_for_escrow())) + { + wallet_public::asset_balance_entry_base& e = balances[td.get_asset_id()]; + e.total += td.amount(); + if (is_transfer_unlocked(td)) + e.unlocked += td.amount(); + if (td.m_flags & WALLET_TRANSFER_DETAIL_FLAG_MINED_TRANSFER) + { + if (td.m_ptx_wallet_info->m_block_height == 0) + { + //for genesis block we add actual amounts + mined += td.amount(); + } + else { + mined += CURRENCY_BLOCK_REWARD; //this code would work only for cases where block reward is full. For reduced block rewards might need more flexible code (TODO) + } + } + + if (!td.is_zc()) + m_has_bare_unspent_outputs = true; + } + } + + std::unordered_map subtransfers_by_assets_map; + for (auto& utx : m_unconfirmed_txs) + { + for (auto& subtransfer : utx.second.subtransfers) + { + wallet_public::asset_balance_entry_base& e = balances[subtransfer.asset_id]; + subtransfers_by_assets_map[subtransfer.asset_id] = subtransfer.is_income; + if (subtransfer.is_income) + { + e.total += subtransfer.amount; + e.awaiting_in += subtransfer.amount; + } + else + { + e.awaiting_out += subtransfer.amount; + if (subtransfer.asset_id == currency::native_coin_asset_id) + { + // this "if" present here only due to sophisticated checks in escrow_custom_test, which + // inaccuracy might be driven by tangled processing of sent transactions and unconfirmed + // transactions in pre-refactoring era (few weeks before this commit) + if (!(utx.second.contract.size() && utx.second.contract[0].state == wallet_public::escrow_contract_details_basic::contract_released_burned)) + { + e.awaiting_out -= currency::get_tx_fee(utx.second.tx); } } - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(found, "Internal error: not found record in m_htlcs during rollback"); - } - - - if (!(m_transfers[i].m_key_image == null_ki && is_watch_only())) - { - auto it_ki = m_key_images.find(m_transfers[i].m_key_image); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it_ki != m_key_images.end(), "key image " << m_transfers[i].m_key_image << " not found"); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(m_transfers[i].m_ptx_wallet_info->m_block_height >= including_height, "transfer #" << i << " block height is less than " << including_height); - m_key_images.erase(it_ki); - } - remove_transfer_from_amount_gindex_map(i); - ++transfers_detached; - } - m_transfers.erase(it, m_transfers.end()); - } - } - - for (uint64_t i = get_top_block_height(); i != including_height - 1 && i != 0; i--) - { - unprocess_htlc_triggers_on_block_removed(i); - } - size_t blocks_detached = detach_from_block_ids(including_height); - - //rollback spends - // do not clear spent flag in spent transfers as corresponding txs are most likely in the pool - // they will be moved into m_unconfirmed_txs for clearing in future (if tx will expire of removed from pool) - for (size_t i = 0, sz = m_transfers.size(); i < sz; ++i) - { - auto& tr = m_transfers[i]; - if (tr.m_spent_height >= including_height) - { - WLT_LOG_BLUE("Transfer [" << i << "] spent height: " << tr.m_spent_height << " -> 0, reason: detaching blockchain", LOG_LEVEL_1); - tr.m_spent_height = 0; - //check if it's hltc contract - } - } - - //rollback tranfers history - auto tr_hist_it = m_transfer_history.rend(); - for (auto it = m_transfer_history.rbegin(); it != m_transfer_history.rend(); it++) - { - if (it->height < including_height) - break; - tr_hist_it = it; // note that tr_hist_it->height >= height - } - - if (tr_hist_it != m_transfer_history.rend()) - { - auto it_from = --tr_hist_it.base(); - // before removing wti from m_transfer_history put it into m_unconfirmed_txs as txs from detached blocks are most likely be moved into the pool - for (auto it = it_from; it != m_transfer_history.end(); ++it) - { - // skip coinbase txs as they are not expected to go into the pool - if (is_coinbase(it->tx)) - { - continue; - } - - if (!m_unconfirmed_txs.insert(std::make_pair(it->tx_hash, *it)).second) - { - WLT_LOG_ERROR("can't move wti from transfer history to unronfirmed txs because such it is already here, tx hash: " << it->tx_hash); - } - } - m_transfer_history.erase(it_from, m_transfer_history.end()); - } - - //rollback payments - for (auto it = m_payments.begin(); it != m_payments.end(); ) - { - if(including_height <= it->second.m_block_height) - it = m_payments.erase(it); - else - ++it; - } - - //detach in m_last_zc_global_indexs - while (m_last_zc_global_indexs.size() && including_height <= m_last_zc_global_indexs.begin()->first ) - { - m_last_zc_global_indexs.erase(m_last_zc_global_indexs.begin()); - } - - //asset descriptors - handle_rollback_events(including_height); - - WLT_LOG_L0("Detached blockchain on height " << including_height << ", transfers detached " << transfers_detached << ", blocks detached " << blocks_detached); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::operator()(const asset_register_event& e) -{ - auto it = m_own_asset_descriptors.find(e.asset_id); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it != m_own_asset_descriptors.end(), "asset_id " << e.asset_id << "not found during rolling asset_register_event"); - m_own_asset_descriptors.erase(it); -} -void wallet2::operator()(const asset_update_event& e) -{ - auto it = m_own_asset_descriptors.find(e.asset_id); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it != m_own_asset_descriptors.end(), "asset_id " << e.asset_id << "not found during rolling asset_update_event"); - it->second = e.own_context; -} -void wallet2::operator()(const asset_unown_event& e) -{ - auto it = m_own_asset_descriptors.find(e.asset_id); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it == m_own_asset_descriptors.end(), "asset_id " << e.asset_id << "unexpectedly found during rolling asset_unown_event"); - m_own_asset_descriptors[e.asset_id] = e.own_context; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::handle_rollback_events(uint64_t including_height) -{ - while (m_rollback_events.size() && m_rollback_events.back().first >= including_height) - { - boost::apply_visitor(*this, m_rollback_events.back().second); - m_rollback_events.pop_back(); - } -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::deinit() -{ - m_wcallback.reset(); - return true; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::clear() -{ - reset_all(); - return true; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::reset_all() -{ - //static_cast(*this) = wallet2_base_state{}; - static_cast(*this).~wallet2_base_state(); - new (static_cast(this)) wallet2_base_state(); - return true; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::store_keys(std::string& buff, const std::string& password, wallet2::keys_file_data& keys_file_data, 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(acc, account_data); - WLT_CHECK_AND_ASSERT_MES(r, false, "failed to serialize wallet keys"); - - crypto::chacha8_key key; - crypto::generate_chacha8_key(password, key); - std::string cipher; - cipher.resize(account_data.size()); - keys_file_data.iv = crypto::rand(); - crypto::chacha8(account_data.data(), account_data.size(), key, keys_file_data.iv, &cipher[0]); - keys_file_data.account_data = cipher; - - r = ::serialization::dump_binary(keys_file_data, buff); - - return true; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::backup_keys(const std::string& path) -{ - std::string buff; - wallet2::keys_file_data keys_file_data = AUTO_VAL_INIT(keys_file_data); - bool r = store_keys(buff, m_password, keys_file_data); - WLT_CHECK_AND_ASSERT_MES(r, false, "Failed to store keys"); - - r = file_io_utils::save_string_to_file(path, buff); - WLT_CHECK_AND_ASSERT_MES(r, false, "Failed to save_string_to_file at store keys"); - return true; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::reset_password(const std::string& pass) -{ - m_password = pass; - return true; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::is_password_valid(const std::string& pass) -{ - return pass == m_password; -} -//---------------------------------------------------------------------------------------------------- -namespace -{ - bool verify_keys(const crypto::secret_key& sec, const crypto::public_key& expected_pub) - { - crypto::public_key pub; - bool r = crypto::secret_key_to_public_key(sec, pub); - return r && expected_pub == pub; - } -} -//---------------------------------------------------------------------------------------------------- -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_T(out_key_to_ki); - 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, "UNRECOVERABLE ERROR, wallet stops: m_pending_key_images > m_pending_key_images_file_container" << ENDL << "Missing/wrong " << string_encoding::convert_to_ansii(m_pending_ki_file) << " file?"); - } -} -//---------------------------------------------------------------------------------------------------- -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, uint64_t file_signature, keys_file_data& kf_data) -{ - bool r = false; - if (file_signature == WALLET_FILE_SIGNATURE_OLD) - { - wallet2::keys_file_data_old kf_data_old; - r = ::serialization::parse_binary(buff, kf_data_old); - kf_data = wallet2::keys_file_data::from_old(kf_data_old); - } - else if (file_signature == WALLET_FILE_SIGNATURE_V2) - { - r = ::serialization::parse_binary(buff, kf_data); - } - THROW_IF_TRUE_WALLET_EX(!r, error::wallet_internal_error, "internal error: failed to deserialize"); - - crypto::chacha8_key key; - crypto::generate_chacha8_key(password, key); - std::string account_data; - account_data.resize(kf_data.account_data.size()); - crypto::chacha8(kf_data.account_data.data(), kf_data.account_data.size(), key, kf_data.iv, &account_data[0]); - - 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.view_secret_key, keys.account_address.view_public_key); - if (keys.spend_secret_key == currency::null_skey) - m_watch_only = true; - else - r = r && verify_keys(keys.spend_secret_key, keys.account_address.spend_public_key); - if (!r) - { - WLT_LOG_L0("Wrong password for wallet " << string_encoding::convert_to_ansii(m_wallet_file)); - tools::error::throw_wallet_ex(std::string(__FILE__ ":" STRINGIZE(__LINE__))); - } - init_log_prefix(); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::assign_account(const currency::account_base& acc) -{ - clear(); - m_account = acc; - init_log_prefix(); - if (m_account.is_watch_only()) - m_watch_only = true; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::generate(const std::wstring& path, const std::string& pass, bool auditable_wallet) -{ - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(validate_password(pass), "new wallet generation failed: password contains forbidden characters") - clear(); - prepare_file_names(path); - - m_password = pass; - m_account.generate(auditable_wallet); - 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)); - if (m_watch_only && !auditable_wallet) - { - bool stub; - load_keys2ki(true, stub); - } - store(); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::restore(const std::wstring& path, const std::string& pass, const std::string& seed_or_tracking_seed, bool tracking_wallet, const std::string& seed_password) -{ - bool r = false; - clear(); - prepare_file_names(path); - m_password = pass; - - if (tracking_wallet) - { - r = m_account.restore_from_tracking_seed(seed_or_tracking_seed); - init_log_prefix(); - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "Could not load tracking wallet from a given seed: invalid tracking seed"); - m_watch_only = true; - } - else - { - r = m_account.restore_from_seed_phrase(seed_or_tracking_seed, seed_password); - init_log_prefix(); - THROW_IF_FALSE_WALLET_EX(r, error::wallet_wrong_seed_error, epee::string_encoding::convert_to_ansii(m_wallet_file)); - } - - 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)); - store(); -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::check_connection() -{ - return m_core_proxy->check_connection(); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::set_votes_config_path(const std::string& path_to_config_file/* = tools::get_default_data_dir() + "\voting_config.json"*/) -{ - m_votes_config_path = path_to_config_file; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::load_votes_config() -{ - if (boost::filesystem::exists(m_votes_config_path)) - { - epee::serialization::load_t_from_json_file(m_votes_config, m_votes_config_path); - } -} -//---------------------------------------------------------------------------------------------------- -void wallet2::load(const std::wstring& wallet_, const std::string& password) -{ - clear(); - prepare_file_names(wallet_); - - m_password = password; - - std::string keys_buff; - std::string body_buff; - - - boost::system::error_code e; - bool exists = boost::filesystem::exists(m_wallet_file, e); - THROW_IF_TRUE_WALLET_EX(e || !exists, error::file_not_found, epee::string_encoding::convert_to_ansii(m_wallet_file)); - boost::filesystem::ifstream data_file; - data_file.open(m_wallet_file, std::ios_base::binary | std::ios_base::in); - THROW_IF_TRUE_WALLET_EX(data_file.fail(), error::file_read_error, epee::string_encoding::convert_to_ansii(m_wallet_file)); - - wallet_file_binary_header wbh = AUTO_VAL_INIT(wbh); - - data_file.read((char*)&wbh, sizeof(wbh)); - THROW_IF_TRUE_WALLET_EX(data_file.fail(), error::file_read_error, epee::string_encoding::convert_to_ansii(m_wallet_file)); - - THROW_IF_TRUE_WALLET_EX(wbh.m_signature != WALLET_FILE_SIGNATURE_OLD && wbh.m_signature != WALLET_FILE_SIGNATURE_V2, error::file_read_error, epee::string_encoding::convert_to_ansii(m_wallet_file)); - THROW_IF_TRUE_WALLET_EX( - wbh.m_cb_keys > WALLET_FILE_MAX_KEYS_SIZE, error::file_read_error, epee::string_encoding::convert_to_ansii(m_wallet_file)); - - - keys_buff.resize(wbh.m_cb_keys); - data_file.read((char*)keys_buff.data(), wbh.m_cb_keys); - wallet2::keys_file_data kf_data = AUTO_VAL_INIT(kf_data); - load_keys(keys_buff, password, wbh.m_signature, kf_data); - - bool need_to_resync = false; - if (wbh.m_ver == WALLET_FILE_BINARY_HEADER_VERSION_INITAL) - { - // old WALLET_FILE_BINARY_HEADER_VERSION version means no encryption - need_to_resync = !tools::portable_unserialize_obj_from_stream(*this, data_file); - WLT_LOG_L1("Detected format: WALLET_FILE_BINARY_HEADER_VERSION_INITAL (need_to_resync=" << need_to_resync << ")"); - } - else if (wbh.m_ver == WALLET_FILE_BINARY_HEADER_VERSION_2) - { - tools::encrypt_chacha_in_filter decrypt_filter(password, kf_data.iv); - boost::iostreams::filtering_istream in; - in.push(decrypt_filter); - in.push(data_file); - need_to_resync = !tools::portable_unserialize_obj_from_stream(*this, in); - WLT_LOG_L1("Detected format: WALLET_FILE_BINARY_HEADER_VERSION_2 (need_to_resync=" << need_to_resync << ")"); - } - else - { - WLT_LOG_L0("Unknown wallet body version(" << wbh.m_ver << "), resync initiated."); - need_to_resync = true; - } - - - - if (m_watch_only && !is_auditable()) - load_keys2ki(true, need_to_resync); - - boost::system::error_code ec = AUTO_VAL_INIT(ec); - m_current_wallet_file_size = boost::filesystem::file_size(wallet_, ec); - - 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() - << ", file_size: " << m_current_wallet_file_size - << ", blockchain_size: " << m_chain.get_blockchain_current_size() - ); - WLT_LOG_L1("[LOADING]Blockchain shortener state: " << ENDL << m_chain.get_internal_state_text()); - - load_votes_config(); - - WLT_LOG_L1("(after loading: pending_key_images: " << m_pending_key_images.size() << ", pki file elements: " << m_pending_key_images_file_container.size() << ", tx_keys: " << m_tx_keys.size() << ")"); - - if (need_to_resync) - { - reset_history(); - WLT_LOG_L0("Unable to load history data from wallet file, wallet will be resynced!"); - } - - THROW_IF_TRUE_WALLET_EX(need_to_resync, error::wallet_load_notice_wallet_restored, epee::string_encoding::convert_to_ansii(m_wallet_file)); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::store() -{ - store(m_wallet_file, m_password); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::store(const std::wstring& path) -{ - store(path, m_password); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::store(const std::wstring& path_to_save, const std::string& password) -{ - LOG_PRINT_L0("(before storing: pending_key_images: " << m_pending_key_images.size() << ", pki file elements: " << m_pending_key_images_file_container.size() << ", tx_keys: " << m_tx_keys.size() << ")"); - - std::string ascii_path_to_save = epee::string_encoding::convert_to_ansii(path_to_save); - - //prepare data - std::string keys_buff; - wallet2::keys_file_data keys_file_data = AUTO_VAL_INIT(keys_file_data); - bool r = store_keys(keys_buff, password, keys_file_data, m_watch_only); - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "failed to store_keys for wallet " << ascii_path_to_save); - - //store data - wallet_file_binary_header wbh = AUTO_VAL_INIT(wbh); - wbh.m_signature = WALLET_FILE_SIGNATURE_V2; - wbh.m_cb_keys = keys_buff.size(); - //@#@ change it to proper - wbh.m_ver = WALLET_FILE_BINARY_HEADER_VERSION_2; - std::string header_buff((const char*)&wbh, sizeof(wbh)); - - uint64_t ts = m_core_runtime_config.get_core_time(); - - // save to tmp file, then rename - boost::filesystem::path tmp_file_path = boost::filesystem::path(path_to_save); - tmp_file_path += L".newtmp_" + std::to_wstring(ts); - - boost::filesystem::ofstream data_file; - data_file.open(tmp_file_path, std::ios_base::binary | std::ios_base::out | std::ios::trunc); - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(!data_file.fail(), "failed to open binary wallet file for saving: " << tmp_file_path.string()); - data_file << header_buff << keys_buff; - - WLT_LOG_L0("Storing to temporary file " << tmp_file_path.string() << " ..."); - //creating encryption stream - tools::encrypt_chacha_out_filter decrypt_filter(m_password, keys_file_data.iv); - boost::iostreams::filtering_ostream out; - out.push(decrypt_filter); - out.push(data_file); - - r = tools::portble_serialize_obj_to_stream(*this, out); - if (!r) - { - data_file.close(); - boost::filesystem::remove(tmp_file_path); // remove tmp file if smth went wrong - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(false, "IO error while storing wallet to " << tmp_file_path.string() << " (portble_serialize_obj_to_stream failed)"); - } - - data_file.flush(); - data_file.close(); - boost::uintmax_t tmp_file_size = boost::filesystem::file_size(tmp_file_path); - WLT_LOG_L0("Stored successfully to temporary file " << tmp_file_path.string() << ", file size=" << tmp_file_size); - - WLT_LOG_L1("[LOADING]Blockchain shortener state: " << ENDL << m_chain.get_internal_state_text()); - - // for the sake of safety perform a double-renaming: wallet file -> old tmp, new tmp -> wallet file, remove old tmp - - boost::filesystem::path tmp_old_file_path = boost::filesystem::path(path_to_save); - tmp_old_file_path += L".oldtmp_" + std::to_wstring(ts); - - if (boost::filesystem::is_regular_file(path_to_save)) - { - boost::filesystem::rename(path_to_save, tmp_old_file_path); - WLT_LOG_L1("Renamed: " << ascii_path_to_save << " -> " << tmp_old_file_path.string()); - } - - boost::filesystem::rename(tmp_file_path, path_to_save); - WLT_LOG_L1("Renamed: " << tmp_file_path.string() << " -> " << ascii_path_to_save); - - if (boost::filesystem::remove(tmp_old_file_path)) - { - WLT_LOG_L1("Removed temporary file: " << tmp_old_file_path.string()); - } - - bool path_to_save_exists = boost::filesystem::is_regular_file(path_to_save); - bool tmp_file_path_exists = boost::filesystem::is_regular_file(tmp_file_path); - bool tmp_old_file_path_exists = boost::filesystem::is_regular_file(tmp_old_file_path); - - boost::system::error_code ec = AUTO_VAL_INIT(ec); - m_current_wallet_file_size = boost::filesystem::file_size(path_to_save, ec); - if (path_to_save_exists && !tmp_file_path_exists && !tmp_old_file_path_exists) - { - - WLT_LOG_L0("Wallet was successfully stored to " << ascii_path_to_save << ", file size=" << m_current_wallet_file_size - << " blockchain_size: " << m_chain.get_blockchain_current_size()); - } - else - { - WLT_LOG_ERROR("Wallet stroing to " << ascii_path_to_save << " might not be successfull: path_to_save_exists=" << path_to_save_exists << ", tmp_file_path_exists=" << tmp_file_path_exists << ", tmp_old_file_path_exists=" << tmp_old_file_path_exists); - throw tools::error::wallet_common_error(LOCATION_STR, "Wallet file storing might not be successfull. Please make sure you have backed up your seed phrase and check log for details."); - } -} -//---------------------------------------------------------------------------------------------------- -uint64_t wallet2::get_wallet_file_size()const -{ - return m_current_wallet_file_size; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::set_use_deffered_global_outputs(bool use) -{ - LOG_PRINT_L0("[DEFFERED_MODE]: " << use); - m_use_deffered_global_outputs = use; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::set_use_assets_whitelisting(bool use) -{ - LOG_PRINT_L0("[ASSET_WHITELISTING_MODE]: " << use); - m_use_assets_whitelisting = use; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::store_watch_only(const std::wstring& path_to_save, const std::string& password) const -{ - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(path_to_save != m_wallet_file, "trying to save watch-only wallet to the same wallet file!"); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(!m_watch_only, "saving watch-only wallet into a watch-only wallet is not allowed"); - - // prepare data for watch-only wallet - wallet2 wo; - // wallet2 wo(*this); copy-constructor is not working, so do a this serialization workaround - std::stringstream stream_buffer; - tools::portble_serialize_obj_to_stream(*this, stream_buffer); - tools::portable_unserialize_obj_from_stream(wo, stream_buffer); - - wo.m_watch_only = true; - wo.m_account = m_account; - wo.m_account.make_account_watch_only(); - wo.prepare_file_names(path_to_save); - - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(!boost::filesystem::exists(wo.m_wallet_file), "file " << epee::string_encoding::convert_to_ansii(wo.m_wallet_file) << " already exists"); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(!boost::filesystem::exists(wo.m_pending_ki_file), "file " << epee::string_encoding::convert_to_ansii(wo.m_pending_ki_file) << " already exists"); - - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(wo.m_pending_key_images.empty(), "pending key images is expected to be empty"); - if (!is_auditable()) - { - bool stub = false; - wo.load_keys2ki(true, stub); // to create outkey2ki file - } - - // populate pending key images for spent outputs (this will help to resync watch-only wallet) - for (size_t ti = 0; ti < wo.m_transfers.size(); ++ti) - { - const auto& td = wo.m_transfers[ti]; - if (!td.is_spent()) - continue; // only spent transfers really need to be stored, because watch-only wallet will not be able to figure out they were spent otherwise - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.m_internal_output_index < td.m_ptx_wallet_info->m_tx.vout.size(), "invalid transfer #" << ti); - if(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].type() != typeid(tx_out_bare)) - continue; - const currency::txout_target_v& out_t = boost::get(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]).target; - if (out_t.type() != typeid(currency::txout_to_key)) - continue; - const crypto::public_key& out_key = boost::get(out_t).key; - wo.m_pending_key_images.insert(std::make_pair(out_key, td.m_key_image)); - wo.m_pending_key_images_file_container.push_back(tools::out_key_to_ki{ out_key, td.m_key_image }); - WLT_LOG_L1("preparing watch-only wallet: added pending ki (" << out_key << ", " << td.m_key_image << ")"); - } - - // TODO additional clearing for watch-only wallet's data - - wo.store(path_to_save, password); -} -//---------------------------------------------------------------------------------------------------- -uint64_t wallet2::unlocked_balance() const -{ - uint64_t stub = 0; - uint64_t unlocked_balance = 0; - balance(unlocked_balance, stub, stub, stub); - return unlocked_balance; -} -//---------------------------------------------------------------------------------------------------- -uint64_t wallet2::balance(uint64_t& unloked) const -{ - uint64_t fake = 0; - return balance(unloked, fake, fake, fake); -} -//---------------------------------------------------------------------------------------------------- -uint64_t wallet2::balance(uint64_t& unlocked, uint64_t& awaiting_in, uint64_t& awaiting_out, uint64_t& mined, const crypto::public_key& asset_id /* = currency::native_coin_asset_id */) const -{ - uint64_t total = 0; - unlocked = 0; - awaiting_in = 0; - awaiting_out = 0; - mined = 0; - std::unordered_map balances; - balance(balances, mined); - auto it = balances.find(asset_id); - if (it != balances.end()) - { - total = it->second.total; - unlocked = it->second.unlocked; - awaiting_in = it->second.awaiting_in; - awaiting_out = it->second.awaiting_out; - } - return total; -} -//---------------------------------------------------------------------------------------------------- -uint64_t wallet2::balance(const crypto::public_key& asset_id, uint64_t& unlocked) const -{ - std::unordered_map balances; - uint64_t dummy; - balance(balances, dummy); - auto it = balances.find(asset_id); - if (it == balances.end()) - { - return 0; - } - unlocked = it->second.unlocked; - return it->second.total; -} -//---------------------------------------------------------------------------------------------------- -uint64_t wallet2::balance(const crypto::public_key& asset_id) const -{ - uint64_t dummy = 0; - return balance(asset_id, dummy); -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::balance(std::unordered_map& balances, uint64_t& mined) const -{ - mined = 0; - m_has_bare_unspent_outputs = false; - - for(auto& td : m_transfers) - { - if (td.is_spendable() || (td.is_reserved_for_escrow() && !td.is_spent())) - { - wallet_public::asset_balance_entry_base& e = balances[td.get_asset_id()]; - e.total += td.amount(); - if (is_transfer_unlocked(td)) - e.unlocked += td.amount(); - if (td.m_flags & WALLET_TRANSFER_DETAIL_FLAG_MINED_TRANSFER) - { - if (td.m_ptx_wallet_info->m_block_height == 0) - { - //for genesis block we add actual amounts - mined += td.amount(); - } - else { - mined += CURRENCY_BLOCK_REWARD; //this code would work only for cases where block reward is full. For reduced block rewards might need more flexible code (TODO) - } - } - - if (!td.is_zc()) - m_has_bare_unspent_outputs = true; - } - } - std::unordered_map subtransfers_by_assets_map; - for(auto& utx : m_unconfirmed_txs) - { - for (auto& subtransfer : utx.second.subtransfers) - { - wallet_public::asset_balance_entry_base& e = balances[subtransfer.asset_id]; - subtransfers_by_assets_map[subtransfer.asset_id] = subtransfer.is_income; - if (subtransfer.is_income) - { - e.total += subtransfer.amount; - e.awaiting_in += subtransfer.amount; - } - else - { - e.awaiting_out += subtransfer.amount; - if (subtransfer.asset_id == currency::native_coin_asset_id) - { - // this "if" present here only due to sophisticated checks in escrow_custom_test, which - // inaccuracy might be driven by tangled processing of sent transactions and unconfirmed - // transactions in pre-refactoring era (few weeks before this commit) - if (!(utx.second.contract.size() && utx.second.contract[0].state == wallet_public::escrow_contract_details_basic::contract_released_burned)) - { - e.awaiting_out -= currency::get_tx_fee(utx.second.tx); - } } } - } - - //has outgoing entries for each asset - //if (utx.second.has_outgoing_entries()) - //{ - //collect change to unconfirmed + + //has outgoing entries for each asset + //if (utx.second.has_outgoing_entries()) + //{ + //collect change to unconfirmed for (const auto& emp_entry : utx.second.employed_entries.receive) { auto it_employed_entry = subtransfers_by_assets_map.find(emp_entry.asset_id); @@ -3753,968 +3772,1105 @@ bool wallet2::balance(std::unordered_map& balances, uint64_t& mined) const -{ - load_whitelisted_tokens_if_not_loaded(); - balances.clear(); - std::unordered_map balances_map; - this->balance(balances_map, mined); - std::unordered_map custom_assets_local = m_custom_assets; - - for (auto& own_asset : m_own_asset_descriptors) - { - if (m_whitelisted_assets.find(own_asset.first) == m_whitelisted_assets.end()) - { - custom_assets_local[own_asset.first] = own_asset.second; } - } - asset_descriptor_base native_asset_info = AUTO_VAL_INIT(native_asset_info); - native_asset_info.full_name = CURRENCY_NAME_SHORT_BASE; - native_asset_info.ticker = CURRENCY_NAME_ABR; - native_asset_info.decimal_point = CURRENCY_DISPLAY_DECIMAL_POINT; - custom_assets_local[currency::native_coin_asset_id] = native_asset_info; - - for (const auto& item : balances_map) - { - asset_descriptor_base asset_info = AUTO_VAL_INIT(asset_info); - //check if asset is whitelisted or customly added - - //check if it custom asset - auto it_cust = custom_assets_local.find(item.first); - if(it_cust == custom_assets_local.end()) + if (m_concise_mode) { - if(!m_use_assets_whitelisting) - continue; + trim_transfers_and_history(items_to_remove); + } - auto it_local = m_whitelisted_assets.find(item.first); - if(it_local == m_whitelisted_assets.end()) + return true; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::trim_transfers_and_history(const std::list& items_to_remove) + { + //delete from m_transfers + for (auto item : items_to_remove) + { + auto it = m_transfers.find(item); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it != m_transfers.end(), + "internal error: item to delet " << item << " not found"); + m_transfers.erase(it); + } + + //delete from recent_history + if (m_transfer_history.size() > WALLET_CONCISE_MODE_MAX_HISTORY_SIZE) + { + m_transfer_history.erase(m_transfer_history.begin(), m_transfer_history.end() - WALLET_CONCISE_MODE_MAX_HISTORY_SIZE); + } + + return true; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::balance(std::list& balances, uint64_t& mined) const + { + load_whitelisted_tokens_if_not_loaded(); + balances.clear(); + std::unordered_map balances_map; + this->balance(balances_map, mined); + std::unordered_map custom_assets_local = m_custom_assets; + + for (auto& own_asset : m_own_asset_descriptors) + { + if (m_whitelisted_assets.find(own_asset.first) == m_whitelisted_assets.end()) { - WLT_LOG_YELLOW("WARNING: unknown asset " << item.first << " found and skipped; it's NOT included in balance", LOG_LEVEL_1); - continue; - } - else - { - asset_info = it_local->second; + custom_assets_local[own_asset.first] = own_asset.second; } } - else + + asset_descriptor_base native_asset_info = AUTO_VAL_INIT(native_asset_info); + native_asset_info.full_name = CURRENCY_NAME_SHORT_BASE; + native_asset_info.ticker = CURRENCY_NAME_ABR; + native_asset_info.decimal_point = CURRENCY_DISPLAY_DECIMAL_POINT; + custom_assets_local[currency::native_coin_asset_id] = native_asset_info; + + for (const auto& item : balances_map) + { + asset_descriptor_base asset_info = AUTO_VAL_INIT(asset_info); + //check if asset is whitelisted or customly added + + //check if it custom asset + auto it_cust = custom_assets_local.find(item.first); + if (it_cust == custom_assets_local.end()) + { + if (!m_use_assets_whitelisting) + continue; + + auto it_local = m_whitelisted_assets.find(item.first); + if (it_local == m_whitelisted_assets.end()) + { + WLT_LOG_YELLOW("WARNING: unknown asset " << item.first << " found and skipped; it's NOT included in balance", LOG_LEVEL_1); + continue; + } + else + { + asset_info = it_local->second; + } + } + else + { + asset_info = it_cust->second; + custom_assets_local.erase(it_cust); + } + + balances.push_back(wallet_public::asset_balance_entry()); + wallet_public::asset_balance_entry& new_item = balances.back(); + static_cast(new_item) = item.second; + new_item.asset_info.asset_id = item.first; + static_cast(new_item.asset_info) = asset_info; + } + //manually added assets should be always present, at least as zero balanced items + for (auto& asset : custom_assets_local) + { + balances.push_back(wallet_public::asset_balance_entry()); + wallet_public::asset_balance_entry& new_item = balances.back(); + new_item.asset_info.asset_id = asset.first; + static_cast(new_item.asset_info) = asset.second; + } + + return true; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::get_asset_info(const crypto::public_key& asset_id, currency::asset_descriptor_base& asset_info, uint32_t& asset_flags) const + { + asset_flags = aif_none; + if (asset_id == currency::native_coin_asset_id) + { + asset_info = currency::get_native_coin_asset_descriptor(); + asset_flags |= aif_whitelisted; + return true; + } + + // own asset? + auto it_own = m_own_asset_descriptors.find(asset_id); + if (it_own != m_own_asset_descriptors.end()) + { + asset_info = it_own->second; + asset_flags |= aif_own; + return true; + } + + // whitelisted? + auto it_white = m_whitelisted_assets.find(asset_id); + if (it_white != m_whitelisted_assets.end()) + { + asset_info = it_white->second; + asset_flags |= aif_whitelisted; + return true; + } + + // custom asset? + auto it_cust = m_custom_assets.find(asset_id); + if (it_cust != m_custom_assets.end()) { asset_info = it_cust->second; - custom_assets_local.erase(it_cust); + return true; } - - balances.push_back(wallet_public::asset_balance_entry()); - wallet_public::asset_balance_entry& new_item = balances.back(); - static_cast(new_item) = item.second; - new_item.asset_info.asset_id = item.first; - static_cast(new_item.asset_info) = asset_info; - } - //manually added assets should be always present, at least as zero balanced items - for (auto& asset : custom_assets_local) - { - balances.push_back(wallet_public::asset_balance_entry()); - wallet_public::asset_balance_entry& new_item = balances.back(); - new_item.asset_info.asset_id = asset.first; - static_cast(new_item.asset_info) = asset.second; - } - return true; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::get_asset_info(const crypto::public_key& asset_id, currency::asset_descriptor_base& asset_info, uint32_t& asset_flags) const -{ - asset_flags = aif_none; - if (asset_id == currency::native_coin_asset_id) - { - asset_info = currency::get_native_coin_asset_descriptor(); - asset_flags |= aif_whitelisted; - return true; - } - - // own asset? - auto it_own = m_own_asset_descriptors.find(asset_id); - if (it_own != m_own_asset_descriptors.end()) - { - asset_info = it_own->second; - asset_flags |= aif_own; - return true; - } - - // whitelisted? - auto it_white = m_whitelisted_assets.find(asset_id); - if (it_white != m_whitelisted_assets.end()) - { - asset_info = it_white->second; - asset_flags |= aif_whitelisted; - return true; - } - - // custom asset? - auto it_cust = m_custom_assets.find(asset_id); - if (it_cust != m_custom_assets.end()) - { - asset_info = it_cust->second; - return true; - } - - return false; -} -//---------------------------------------------------------------------------------------------------- -size_t wallet2::get_asset_decimal_point(const crypto::public_key& asset_id, size_t result_if_not_found /* = 0 */) const -{ - if (asset_id == currency::native_coin_asset_id) - return currency::get_native_coin_asset_descriptor().decimal_point; - - // whitelisted? - auto it_white = m_whitelisted_assets.find(asset_id); - if (it_white != m_whitelisted_assets.end()) - return it_white->second.decimal_point; - - // custom asset? - auto it_cust = m_custom_assets.find(asset_id); - if (it_cust != m_custom_assets.end()) - return it_cust->second.decimal_point; - - auto it_own = m_own_asset_descriptors.find(asset_id); - if (it_own != m_own_asset_descriptors.end()) - return it_own->second.decimal_point; - - return result_if_not_found; // if not overriden, use the 0 decimal point (raw numbers) as the default -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::get_asset_decimal_point(const crypto::public_key& asset_id, size_t* p_decimal_point_result) const -{ - size_t decimal_point = get_asset_decimal_point(asset_id, SIZE_MAX); - if (decimal_point == SIZE_MAX) return false; - if (p_decimal_point_result != nullptr) - *p_decimal_point_result = decimal_point; - return true; -} -//---------------------------------------------------------------------------------------------------- -uint64_t wallet2::balance() const -{ - uint64_t stub = 0; - return balance(stub, stub, stub, stub); -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::add_custom_asset_id(const crypto::public_key& asset_id, asset_descriptor_base& asset_descriptor) -{ - currency::COMMAND_RPC_GET_ASSET_INFO::request req = AUTO_VAL_INIT(req); - currency::COMMAND_RPC_GET_ASSET_INFO::response resp = AUTO_VAL_INIT(resp); - req.asset_id = asset_id; - - bool r = m_core_proxy->call_COMMAND_RPC_GET_ASSET_INFO(req, resp); - if (r && resp.status == API_RETURN_CODE_OK) - { - m_custom_assets[asset_id] = resp.asset_descriptor; - asset_descriptor = resp.asset_descriptor; - return true; } - return false; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::delete_custom_asset_id(const crypto::public_key& asset_id) -{ - const auto it = m_custom_assets.find(asset_id); - if (it != m_custom_assets.end()) - { - m_custom_assets.erase(it); - } - - return true; -} -//---------------------------------------------------------------------------------------------------- -const std::unordered_map& wallet2::get_local_whitelist() const -{ - return m_custom_assets; -} -//---------------------------------------------------------------------------------------------------- -const std::unordered_map& wallet2::get_global_whitelist() const -{ - return m_whitelisted_assets; -} -//---------------------------------------------------------------------------------------------------- -const std::unordered_map& wallet2::get_own_assets() const -{ - return m_own_asset_descriptors; -} //---------------------------------------------------------------------------------------------------- -bool wallet2::load_whitelisted_tokens() const -{ - if(!m_use_assets_whitelisting) - return true; - - m_whitelisted_assets.clear(); - std::string body; - wallet_public::assets_whitelist aw = AUTO_VAL_INIT(aw); - if (epee::net_utils::get_http_json_t(WALLET_ASSETS_WHITELIST_URL, aw)) + size_t wallet2::get_asset_decimal_point(const crypto::public_key& asset_id, size_t result_if_not_found /* = 0 */) const { - for (auto it = aw.assets.begin(); it != aw.assets.end(); it++) + if (asset_id == currency::native_coin_asset_id) + return currency::get_native_coin_asset_descriptor().decimal_point; + + // whitelisted? + auto it_white = m_whitelisted_assets.find(asset_id); + if (it_white != m_whitelisted_assets.end()) + return it_white->second.decimal_point; + + // custom asset? + auto it_cust = m_custom_assets.find(asset_id); + if (it_cust != m_custom_assets.end()) + return it_cust->second.decimal_point; + + auto it_own = m_own_asset_descriptors.find(asset_id); + if (it_own != m_own_asset_descriptors.end()) + return it_own->second.decimal_point; + + return result_if_not_found; // if not overriden, use the 0 decimal point (raw numbers) as the default + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::get_asset_decimal_point(const crypto::public_key& asset_id, size_t* p_decimal_point_result) const + { + size_t decimal_point = get_asset_decimal_point(asset_id, SIZE_MAX); + if (decimal_point == SIZE_MAX) + return false; + if (p_decimal_point_result != nullptr) + *p_decimal_point_result = decimal_point; + return true; + } + //---------------------------------------------------------------------------------------------------- + uint64_t wallet2::balance() const + { + uint64_t stub = 0; + return balance(stub, stub, stub, stub); + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::add_custom_asset_id(const crypto::public_key& asset_id, asset_descriptor_base& asset_descriptor) + { + currency::COMMAND_RPC_GET_ASSET_INFO::request req = AUTO_VAL_INIT(req); + currency::COMMAND_RPC_GET_ASSET_INFO::response resp = AUTO_VAL_INIT(resp); + req.asset_id = asset_id; + + bool r = m_core_proxy->call_COMMAND_RPC_GET_ASSET_INFO(req, resp); + if (r && resp.status == API_RETURN_CODE_OK) { - m_whitelisted_assets[it->asset_id] = static_cast(*it); + m_custom_assets[asset_id] = resp.asset_descriptor; + asset_descriptor = resp.asset_descriptor; + return true; } - return true; + return false; } - return false; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::load_whitelisted_tokens_if_not_loaded() const -{ - if (m_whitelist_updated) + //---------------------------------------------------------------------------------------------------- + bool wallet2::delete_custom_asset_id(const crypto::public_key& asset_id) { + const auto it = m_custom_assets.find(asset_id); + if (it != m_custom_assets.end()) + { + m_custom_assets.erase(it); + } + return true; } - if (!load_whitelisted_tokens()) - return false; - m_whitelist_updated = true; - return true; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::get_transfers(transfer_container& incoming_transfers) const -{ - incoming_transfers = m_transfers; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::generate_utxo_defragmentation_transaction_if_needed(currency::transaction& tx) -{ - if (!m_defragmentation_tx_enabled) + //---------------------------------------------------------------------------------------------------- + const std::unordered_map& wallet2::get_local_whitelist() const + { + return m_custom_assets; + } + //---------------------------------------------------------------------------------------------------- + const std::unordered_map& wallet2::get_global_whitelist() const + { + return m_whitelisted_assets; + } + //---------------------------------------------------------------------------------------------------- + const std::unordered_map& wallet2::get_own_assets() const + { + return m_own_asset_descriptors; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::load_whitelisted_tokens() const + { + if (!m_use_assets_whitelisting) + return true; + + m_whitelisted_assets.clear(); + std::string body; + wallet_public::assets_whitelist aw = AUTO_VAL_INIT(aw); + if (epee::net_utils::get_http_json_t(WALLET_ASSETS_WHITELIST_URL, aw)) + { + for (auto it = aw.assets.begin(); it != aw.assets.end(); it++) + { + m_whitelisted_assets[it->asset_id] = static_cast(*it); + } + return true; + } return false; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::load_whitelisted_tokens_if_not_loaded() const + { + if (m_whitelist_updated) + { + return true; + } + if (!load_whitelisted_tokens()) + return false; + m_whitelist_updated = true; + return true; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::get_transfers(transfer_container& incoming_transfers) const + { + incoming_transfers = m_transfers; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::generate_utxo_defragmentation_transaction_if_needed(currency::transaction& tx) + { + if (!m_defragmentation_tx_enabled) + return false; - construct_tx_param ctp = get_default_construct_tx_param(); - ctp.create_utxo_defragmentation_tx = true; - finalized_tx ftp{}; + construct_tx_param ctp = get_default_construct_tx_param(); + ctp.create_utxo_defragmentation_tx = true; + finalized_tx ftp{}; - transfer(ctp, ftp, false, nullptr); + transfer(ctp, ftp, false, nullptr); - if (ftp.was_not_prepared) + if (ftp.was_not_prepared) return false; // no such UTXO were found, not an error - tx = ftp.tx; - return true; -} -//---------------------------------------------------------------------------------------------------- -std::string wallet2::get_transfers_str(bool include_spent /*= true*/, bool include_unspent /*= true*/, bool show_only_unknown /*= false*/, const std::string& filter_asset_ticker /*= std::string{}*/) const -{ - static const char* header = " index amount ticker g_index flags block tx out# asset id"; - std::stringstream ss; - ss << header << ENDL; - size_t count = 0; - size_t unknown_assets_outs_count = 0; - for (size_t i = 0; i != m_transfers.size(); ++i) + tx = ftp.tx; + return true; + } + //---------------------------------------------------------------------------------------------------- + std::string wallet2::get_transfers_str(bool include_spent /*= true*/, bool include_unspent /*= true*/, bool show_only_unknown /*= false*/, const std::string& filter_asset_ticker /*= std::string{}*/) const { - const transfer_details& td = m_transfers[i]; - - if ((td.is_spent() && !include_spent) || (!td.is_spent() && !include_unspent)) - continue; - - bool is_locked = !is_transfer_unlocked(td); - bool native_coin = td.is_native_coin(); - asset_descriptor_base adb{}; - uint32_t asset_info_flags{}; - if (get_asset_info(td.get_asset_id(), adb, asset_info_flags) == show_only_unknown) + static const char* header = " index amount ticker g_index flags block tx out# asset id"; + std::stringstream ss; + ss << header << ENDL; + size_t count = 0; + size_t unknown_assets_outs_count = 0; + for (const auto& tr : m_transfers) { - if (!show_only_unknown) - ++unknown_assets_outs_count; - continue; + uint64_t i = tr.first; + const transfer_details& td = tr.second; + + if ((td.is_spent() && !include_spent) || (!td.is_spent() && !include_unspent)) + continue; + + bool is_locked = !is_transfer_unlocked(td); + bool native_coin = td.is_native_coin(); + asset_descriptor_base adb{}; + uint32_t asset_info_flags{}; + if (get_asset_info(td.get_asset_id(), adb, asset_info_flags) == show_only_unknown) + { + if (!show_only_unknown) + ++unknown_assets_outs_count; + continue; + } + + if (!filter_asset_ticker.empty() && adb.ticker != filter_asset_ticker) + continue; + + ss << std::right << (is_locked ? "*" : " ") << + std::setw(5) << i << " " << + std::setw(21) << print_asset_money(td.m_amount, adb.decimal_point) << " " << + std::setw(6) << std::left << (native_coin ? std::string(" ") : adb.ticker) << " " << std::right << + std::setw(7) << td.m_global_output_index << " " << + std::setw(2) << std::setfill('0') << td.m_flags << std::setfill(' ') << ":" << + std::setw(5) << transfer_flags_to_str(td.m_flags) << " " << + std::setw(7) << td.m_ptx_wallet_info->m_block_height << " " << + get_transaction_hash(td.m_ptx_wallet_info->m_tx) << " " << + std::setw(4) << td.m_internal_output_index << " "; + if (native_coin) + ss << " "; + else + ss << td.get_asset_id(); + + ss << ENDL; + + ++count; } - if (!filter_asset_ticker.empty() && adb.ticker != filter_asset_ticker) - continue; - - ss << std::right << (is_locked ? "*" : " ") << - std::setw(5) << i << " " << - std::setw(21) << print_asset_money(td.m_amount, adb.decimal_point) << " " << - std::setw(6) << std::left << (native_coin ? std::string(" ") : adb.ticker) << " " << std::right << - std::setw(7) << td.m_global_output_index << " " << - std::setw(2) << std::setfill('0') << td.m_flags << std::setfill(' ') << ":" << - std::setw(5) << transfer_flags_to_str(td.m_flags) << " " << - std::setw(7) << td.m_ptx_wallet_info->m_block_height << " " << - get_transaction_hash(td.m_ptx_wallet_info->m_tx) << " " << - std::setw(4) << td.m_internal_output_index << " "; - if (native_coin) - ss << " "; - else - ss << td.get_asset_id(); - - ss << ENDL; - - ++count; + ss << "printed " << count << " outputs of " << m_transfers.size() << " total" << ENDL; + if (unknown_assets_outs_count == 1) + ss << "(" << unknown_assets_outs_count << " output with unrecognized asset id is not shown, use 'list_outputs unknown' to see it)" << ENDL; + else if (unknown_assets_outs_count > 1) + ss << "(" << unknown_assets_outs_count << " outputs with unrecognized asset ids are not shown, use 'list_outputs unknown' to see them)" << ENDL; + return ss.str(); } - - ss << "printed " << count << " outputs of " << m_transfers.size() << " total" << ENDL; - if (unknown_assets_outs_count == 1) - ss << "(" << unknown_assets_outs_count << " output with unrecognized asset id is not shown, use 'list_outputs unknown' to see it)" << ENDL; - else if (unknown_assets_outs_count > 1) - ss << "(" << unknown_assets_outs_count << " outputs with unrecognized asset ids are not shown, use 'list_outputs unknown' to see them)" << ENDL; - return ss.str(); -} -//---------------------------------------------------------------------------------------------------- -std::string wallet2::get_balance_str() const -{ - // balance unlocked / [balance total] ticker asset id - // 0.21 / 98.51 DP2 a6974d5874e97e5f4ed5ad0a62f0975edbccb1bb55502fc75c7fe808f12f44d3 - // 190.123456789012 / 199.123456789012 ZANO d6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a - // 98.0 BGTVUW af2b12f3033337f9aea1845a6bc3fc966ed4d13227a3ace7706fca7dbcdaa7e2 - // 1000.034 DP3 d4aba1020f26927571771e04b585b4ffb211f52708d5e4c465bbdfa4a12e6271 - - static const char* header = " balance unlocked / [balance total] ticker asset id"; - std::stringstream ss; - ss << header << ENDL; - - std::list balances; - uint64_t mined = 0; - balance(balances, mined); - - auto native_coin_it = std::find_if(balances.begin(), balances.end(), [&](auto& v){ return v.asset_info.asset_id == currency::native_coin_asset_id; }); - if (native_coin_it != balances.end()) + //---------------------------------------------------------------------------------------------------- + std::string wallet2::get_balance_str() const { - balances.push_front(*native_coin_it); - balances.erase(native_coin_it); - } + // balance unlocked / [balance total] ticker asset id + // 0.21 / 98.51 DP2 a6974d5874e97e5f4ed5ad0a62f0975edbccb1bb55502fc75c7fe808f12f44d3 + // 190.123456789012 / 199.123456789012 ZANO d6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a + // 98.0 BGTVUW af2b12f3033337f9aea1845a6bc3fc966ed4d13227a3ace7706fca7dbcdaa7e2 + // 1000.034 DP3 d4aba1020f26927571771e04b585b4ffb211f52708d5e4c465bbdfa4a12e6271 - for (const tools::wallet_public::asset_balance_entry& b : balances) - { - ss << " " << std::left << std::setw(21) << print_fixed_decimal_point_with_trailing_spaces(b.unlocked, b.asset_info.decimal_point); - if (b.total == b.unlocked) - ss << std::string(21 + 3, ' '); - else - ss << " / " << std::setw(21) << print_fixed_decimal_point_with_trailing_spaces(b.total, b.asset_info.decimal_point); - ss << " " << std::setw(8) << std::left << b.asset_info.ticker << " " << b.asset_info.asset_id; - if (b.asset_info.asset_id == native_coin_asset_id) - ss << " NATIVE"; - ss << ENDL; - } + static const char* header = " balance unlocked / [balance total] ticker asset id"; + std::stringstream ss; + ss << header << ENDL; - return ss.str(); -} -//---------------------------------------------------------------------------------------------------- -std::string wallet2::get_balance_str_raw() const -{ - // balance unlocked / [balance total] DP asset id - // 0.21 / 98.51 2 a6974d5874e97e5f4ed5ad0a62f0975edbccb1bb55502fc75c7fe808f12f44d3 - // 190.123456789012 / 199.123456789012 12 d6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a - // 98.0 12 af2b12f3033337f9aea1845a6bc3fc966ed4d13227a3ace7706fca7dbcdaa7e2 - // 1000.034 3 d4aba1020f26927571771e04b585b4ffb211f52708d5e4c465bbdfa4a12e6271 - //WHITELIST: - // 7d3f348fbebfffc4e61a3686189cf870ea393e1c88b8f636acbfdacf9e4b2db2 CT - // ... + std::list balances; + uint64_t mined = 0; + balance(balances, mined); - static const char* header = " balance unlocked / [balance total] ticker asset id DP flags"; - std::stringstream ss; - ss << header << ENDL; - - uint64_t dummy = 0; - typedef std::unordered_map balances_map_t; - balances_map_t balances_map; - this->balance(balances_map, dummy); - - auto print_map = [&](const balances_map_t& map){ - for(const auto& entry : map) + auto native_coin_it = std::find_if(balances.begin(), balances.end(), [&](auto& v) { return v.asset_info.asset_id == currency::native_coin_asset_id; }); + if (native_coin_it != balances.end()) { - uint32_t asset_flags = 0; - asset_descriptor_base asset_info{}; - bool has_info = get_asset_info(entry.first, asset_info, asset_flags); - ss << " " << std::left << std::setw(21) << print_fixed_decimal_point_with_trailing_spaces(entry.second.unlocked, asset_info.decimal_point); - if(entry.second.total == entry.second.unlocked) + balances.push_front(*native_coin_it); + balances.erase(native_coin_it); + } + + for (const tools::wallet_public::asset_balance_entry& b : balances) + { + ss << " " << std::left << std::setw(21) << print_fixed_decimal_point_with_trailing_spaces(b.unlocked, b.asset_info.decimal_point); + if (b.total == b.unlocked) ss << std::string(21 + 3, ' '); else - ss << " / " << std::setw(21) << print_fixed_decimal_point_with_trailing_spaces(entry.second.total, asset_info.decimal_point); - - ss << " " << std::setw(8) << std::left << asset_info.ticker; - ss << " " << entry.first << " "; - - if (has_info) - ss << std::setw(2) << std::right << (int)asset_info.decimal_point; - else - ss << "??"; - - ss << " "; - - if (entry.first == native_coin_asset_id) - { - ss << "NATIVE"; - } - else if (asset_flags != aif_none) - { - if (asset_flags & aif_own) - ss << "own,"; - if (asset_flags & aif_whitelisted) - ss << "whitelisted,"; - ss.seekp(-1, ss.cur); // trim comma - } + ss << " / " << std::setw(21) << print_fixed_decimal_point_with_trailing_spaces(b.total, b.asset_info.decimal_point); + ss << " " << std::setw(8) << std::left << b.asset_info.ticker << " " << b.asset_info.asset_id; + if (b.asset_info.asset_id == native_coin_asset_id) + ss << " NATIVE"; ss << ENDL; } + + return ss.str(); + } + //---------------------------------------------------------------------------------------------------- + std::string wallet2::get_balance_str_raw() const + { + // balance unlocked / [balance total] DP asset id + // 0.21 / 98.51 2 a6974d5874e97e5f4ed5ad0a62f0975edbccb1bb55502fc75c7fe808f12f44d3 + // 190.123456789012 / 199.123456789012 12 d6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a + // 98.0 12 af2b12f3033337f9aea1845a6bc3fc966ed4d13227a3ace7706fca7dbcdaa7e2 + // 1000.034 3 d4aba1020f26927571771e04b585b4ffb211f52708d5e4c465bbdfa4a12e6271 + //WHITELIST: + // 7d3f348fbebfffc4e61a3686189cf870ea393e1c88b8f636acbfdacf9e4b2db2 CT + // ... + + static const char* header = " balance unlocked / [balance total] ticker asset id DP flags"; + std::stringstream ss; + ss << header << ENDL; + + uint64_t dummy = 0; + typedef std::unordered_map balances_map_t; + balances_map_t balances_map; + this->balance(balances_map, dummy); + + auto print_map = [&](const balances_map_t& map) { + for (const auto& entry : map) + { + uint32_t asset_flags = 0; + asset_descriptor_base asset_info{}; + bool has_info = get_asset_info(entry.first, asset_info, asset_flags); + ss << " " << std::left << std::setw(21) << print_fixed_decimal_point_with_trailing_spaces(entry.second.unlocked, asset_info.decimal_point); + if (entry.second.total == entry.second.unlocked) + ss << std::string(21 + 3, ' '); + else + ss << " / " << std::setw(21) << print_fixed_decimal_point_with_trailing_spaces(entry.second.total, asset_info.decimal_point); + + ss << " " << std::setw(8) << std::left << asset_info.ticker; + ss << " " << entry.first << " "; + + if (has_info) + ss << std::setw(2) << std::right << (int)asset_info.decimal_point; + else + ss << "??"; + + ss << " "; + + if (entry.first == native_coin_asset_id) + { + ss << "NATIVE"; + } + else if (asset_flags != aif_none) + { + if (asset_flags & aif_own) + ss << "own,"; + if (asset_flags & aif_whitelisted) + ss << "whitelisted,"; + ss.seekp(-1, ss.cur); // trim comma + } + ss << ENDL; + } + }; + + auto balances_map_it = balances_map.find(native_coin_asset_id); + if (balances_map_it != balances_map.end()) + { + balances_map_t native_coin_map; + native_coin_map.insert(*balances_map_it); + balances_map.erase(balances_map_it); + print_map(native_coin_map); + } + print_map(balances_map); + + + //print whitelist + ss << "WHITELIST: " << ENDL; + + for (const auto& entry : m_whitelisted_assets) + { + ss << " " << std::left << entry.first << " " << entry.second.ticker << ENDL; + } + + // print custom list + ss << "CUSTOM LIST: " << ENDL; + + for (const auto& entry : m_custom_assets) + { + ss << " " << std::left << entry.first << " " << entry.second.ticker << ENDL; + } + + // print own list + ss << "OWN DESCRIPTORS LIST: " << ENDL; + + for (const auto& entry : m_own_asset_descriptors) + { + ss << " " << std::left << entry.first << " " << entry.second.ticker << ENDL; + } + + return ss.str(); + } + //---------------------------------------------------------------------------------------------------- + void wallet2::get_payments(const std::string& payment_id, std::list& payments, uint64_t min_height) const + { + auto range = m_payments.equal_range(payment_id); + 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 + x + + // decrypt the blob + std::string decrypted_src_blob = crypto::chacha_crypt(tx_sources_blob, m_account.get_keys().view_secret_key); + + // deserialize args + currency::finalized_tx ft = AUTO_VAL_INIT(ft); + bool r = t_unserializable_object_from_blob(ft.ftp, decrypted_src_blob); + THROW_IF_FALSE_WALLET_EX(r, error::wallet_common_error, "Failed to decrypt tx sources blob"); + + // make sure unsigned tx was created with the same keys + THROW_IF_FALSE_WALLET_EX(ft.ftp.spend_pub_key == m_account.get_keys().account_address.spend_public_key, error::wallet_common_error, "The was created in a different wallet, keys missmatch"); + + finalize_transaction(ft.ftp, ft.tx, ft.one_time_key, false); + + // calculate key images for each change output + crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX( + crypto::generate_key_derivation( + m_account.get_keys().account_address.view_public_key, + ft.one_time_key, + derivation), + "internal error: sign_transfer: failed to generate key derivation(" + << m_account.get_keys().account_address.view_public_key + << ", view secret key: " << ft.one_time_key << ")"); + + for (size_t i = 0; i < ft.tx.vout.size(); ++i) + { + VARIANT_SWITCH_BEGIN(ft.tx.vout[i]); + VARIANT_CASE_CONST(tx_out_bare, out) + { + if (out.target.type() != typeid(txout_to_key)) + continue; + const txout_to_key& otk = boost::get(out.target); + + crypto::public_key ephemeral_pub = AUTO_VAL_INIT(ephemeral_pub); + if (!crypto::derive_public_key(derivation, i, m_account.get_keys().account_address.spend_public_key, ephemeral_pub)) + { + WLT_LOG_ERROR("derive_public_key failed for tx " << get_transaction_hash(ft.tx) << ", out # " << i); + } + + if (otk.key == ephemeral_pub) + { + // this is the output to the given keys + // derive secret key and calculate key image + crypto::secret_key ephemeral_sec = AUTO_VAL_INIT(ephemeral_sec); + crypto::derive_secret_key(derivation, i, m_account.get_keys().spend_secret_key, ephemeral_sec); + crypto::key_image ki = AUTO_VAL_INIT(ki); + crypto::generate_key_image(ephemeral_pub, ephemeral_sec, ki); + + ft.outs_key_images.push_back(make_serializable_pair(static_cast(i), ki)); + } + } + VARIANT_CASE_CONST(tx_out_zarcanum, o); + //@#@ + VARIANT_SWITCH_END(); + } + + // serialize and encrypt the result + signed_tx_blob = t_serializable_object_to_blob(ft); + crypto::chacha_crypt(signed_tx_blob, m_account.get_keys().view_secret_key); + + tx = ft.tx; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::sign_transfer_files(const std::string& tx_sources_file, const std::string& signed_tx_file, currency::transaction& tx) + { + std::string sources_blob; + bool r = epee::file_io_utils::load_file_to_string(tx_sources_file, sources_blob); + THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "failed to open file " << tx_sources_file); + + std::string signed_tx_blob; + sign_transfer(sources_blob, signed_tx_blob, tx); + + r = epee::file_io_utils::save_string_to_file(signed_tx_file, signed_tx_blob); + THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "failed to store signed tx to file " << signed_tx_file); + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::get_utxo_distribution(std::map& distribution) + { + //TODO@#@ + /* + prepare_free_transfers_cache(0); + for (auto ent : m_found_free_amounts) + { + distribution[ent.first] = ent.second.size(); + } + */ + + return false; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::submit_transfer(const std::string& signed_tx_blob, currency::transaction& tx) + { + // decrypt sources + std::string decrypted_src_blob = crypto::chacha_crypt(signed_tx_blob, m_account.get_keys().view_secret_key); + + // deserialize tx data + currency::finalized_tx ft = AUTO_VAL_INIT(ft); + bool r = t_unserializable_object_from_blob(ft, decrypted_src_blob); + THROW_IF_FALSE_WALLET_EX(r, error::wallet_common_error, "Failed to decrypt signed tx data"); + tx = ft.tx; + crypto::hash tx_hash = get_transaction_hash(tx); + + // foolproof + 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: " << tx_hash); + + try + { + send_transaction_to_network(tx); + } + catch (...) + { + // clear transfers flags if smth went wrong + uint32_t flag = WALLET_TRANSFER_DETAIL_FLAG_SPENT | WALLET_TRANSFER_DETAIL_FLAG_COLD_SIG_RESERVATION; + clear_transfers_from_flag(ft.ftp.selected_transfers, flag, "broadcasting tx " + epee::string_tools::pod_to_hex(tx_hash) + " was unsuccessful"); + throw; + } + + add_sent_tx_detailed_info(tx, ft.ftp.attachments, ft.ftp.prepared_destinations, ft.ftp.selected_transfers); + m_tx_keys.insert(std::make_pair(tx_hash, ft.one_time_key)); + + if (m_watch_only) + { + std::vector> pk_ki_to_be_added; + std::vector> tri_ki_to_be_added; + + for (auto& p : ft.outs_key_images) + { + THROW_IF_FALSE_WALLET_INT_ERR_EX(p.first < tx.vout.size(), "outs_key_images has invalid out index: " << p.first << ", tx.vout.size() = " << tx.vout.size()); + THROW_IF_FALSE_WALLET_INT_ERR_EX(tx.vout[p.first].type() == typeid(tx_out_bare), "Unexpected type in submit_transfer: " << tx.vout[p.first].type().name()); + auto& out = boost::get(tx.vout[p.first]); + THROW_IF_FALSE_WALLET_INT_ERR_EX(out.target.type() == typeid(txout_to_key), "outs_key_images has invalid out type, index: " << p.first); + const txout_to_key& otk = boost::get(out.target); + pk_ki_to_be_added.push_back(std::make_pair(otk.key, p.second)); + } + + THROW_IF_FALSE_WALLET_INT_ERR_EX(tx.vin.size() == ft.ftp.sources.size(), "tx.vin and ft.ftp.sources sizes missmatch"); + for (size_t i = 0; i < tx.vin.size(); ++i) + { + const txin_v& in = tx.vin[i]; + THROW_IF_FALSE_WALLET_CMN_ERR_EX(in.type() == typeid(txin_to_key), "tx " << tx_hash << " has a non txin_to_key input"); + const crypto::key_image& ki = boost::get(in).k_image; + + const auto& src = ft.ftp.sources[i]; + THROW_IF_FALSE_WALLET_INT_ERR_EX(src.real_output < src.outputs.size(), "src.real_output is out of bounds: " << src.real_output); + const crypto::public_key& out_key = src.outputs[src.real_output].stealth_address; + + tri_ki_to_be_added.push_back(std::make_pair(src.transfer_index, ki)); + pk_ki_to_be_added.push_back(std::make_pair(out_key, ki)); + } + + for (auto& p : pk_ki_to_be_added) + { + auto it = m_pending_key_images.find(p.first); + if (it != m_pending_key_images.end()) + { + LOG_PRINT_YELLOW("warning: for tx " << tx_hash << " out pub key " << p.first << " already exist in m_pending_key_images, ki: " << it->second << ", proposed new ki: " << p.second, LOG_LEVEL_0); + } + else + { + m_pending_key_images[p.first] = p.second; + m_pending_key_images_file_container.push_back(tools::out_key_to_ki{ p.first, p.second }); + LOG_PRINT_L2("for tx " << tx_hash << " pending key image added (" << p.first << ", " << p.second << ")"); + } + } + + for (auto& p : tri_ki_to_be_added) + { + //THROW_IF_FALSE_WALLET_INT_ERR_EX(p.first < m_transfers.size(), "incorrect transfer index: " << p.first); + auto& tr = m_transfers.at(p.first); + if (tr.m_key_image != currency::null_ki && tr.m_key_image != p.second) + { + 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; + LOG_PRINT_L2("for tx " << tx_hash << " key image " << p.second << " was associated with transfer # " << p.first); + } + } + + // TODO: print inputs' key images + print_tx_sent_message(tx, "(from submit_transfer)"); + } + //---------------------------------------------------------------------------------------------------- + void wallet2::submit_transfer_files(const std::string& signed_tx_file, currency::transaction& tx) + { + std::string signed_tx_blob; + bool r = epee::file_io_utils::load_file_to_string(signed_tx_file, signed_tx_blob); + THROW_IF_FALSE_WALLET_EX(r, error::wallet_common_error, std::string("failed to open file ") + signed_tx_file); + + submit_transfer(signed_tx_blob, tx); + } + //---------------------------------------------------------------------------------------------------- + uint64_t wallet2::get_recent_transfers_total_count() + { + return m_transfer_history.size(); + } + //---------------------------------------------------------------------------------------------------- + //uint64_t wallet2::get_transfer_entries_count() + //{ + // return m_transfers.size(); + //} + //---------------------------------------------------------------------------------------------------- + + template + bool enum_container(iterator_t it_begin, iterator_t it_end, callback_t cb) + { + for (iterator_t it = it_begin; it != it_end; it++) + { + if (!cb(*it, it - it_begin)) + return true; + } + return true; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::is_defragmentation_transaction(const wallet_public::wallet_transfer_info& wti) + { + if (wti.employed_entries.receive.size() && wti.employed_entries.spent.size() && wti.subtransfers.size() == 1) + { + if (wti.subtransfers[0].asset_id == currency::native_coin_asset_id && !wti.subtransfers[0].is_income && wti.subtransfers[0].amount == get_tx_fee(wti.tx)) + return true; + } + return false; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::get_recent_transfers_history(std::vector& trs, size_t offset, size_t count, uint64_t& total, uint64_t& last_item_index, bool exclude_mining_txs, bool start_from_end) + { + if (!count || offset >= m_transfer_history.size()) + return; + + auto cb = [&](wallet_public::wallet_transfer_info& wti, size_t local_offset) { + + if (exclude_mining_txs) + { + if (currency::is_coinbase(wti.tx) || is_defragmentation_transaction(wti)) + return true; + } + trs.push_back(wti); + load_wallet_transfer_info_flags(trs.back()); + last_item_index = offset + local_offset; + trs.back().transfer_internal_index = last_item_index; + + if (wti.remote_addresses.size() == 1) + { + wti.remote_aliases = get_aliases_for_address(wti.remote_addresses[0]); + } + + if (trs.size() >= count) + { + return false; + } + return true; + }; + + if (start_from_end) + enum_container(m_transfer_history.rbegin() + offset, m_transfer_history.rend(), cb); + else + enum_container(m_transfer_history.begin() + offset, m_transfer_history.end(), cb); + + total = m_transfer_history.size(); + + } + + void wallet2::wti_to_csv_entry(std::ostream& ss, const wallet_public::wallet_transfer_info& wti, size_t index) + { + for (auto& subtr : wti.subtransfers) + { + ss << index << ","; + ss << epee::misc_utils::get_time_str(wti.timestamp) << ","; + ss << print_money(subtr.amount) << ","; + ss << subtr.asset_id << ","; + ss << "\"" << wti.comment << "\","; + ss << "["; + std::copy(wti.remote_addresses.begin(), wti.remote_addresses.end(), std::ostream_iterator(ss, " ")); + ss << "]" << ","; + ss << wti.tx_hash << ","; + ss << wti.height << ","; + ss << wti.unlock_time << ","; + ss << wti.tx_blob_size << ","; + ss << epee::string_tools::buff_to_hex_nodelimer(wti.payment_id) << ","; + ss << "["; + std::copy(wti.remote_aliases.begin(), wti.remote_aliases.end(), std::ostream_iterator(ss, " ")); + ss << "]" << ","; + ss << (subtr.is_income ? "in" : "out") << ","; + ss << (wti.is_service ? "[SERVICE]" : "") << (wti.is_mixing ? "[MIXINS]" : "") << (wti.is_mining ? "[MINING]" : "") << ","; + ss << wti.tx_type << ","; + ss << print_money(wti.fee) << ENDL; + } + }; - auto balances_map_it = balances_map.find(native_coin_asset_id); - if (balances_map_it != balances_map.end()) + void wallet2::wti_to_txt_line(std::ostream& ss, const wallet_public::wallet_transfer_info& wti, size_t index) { - balances_map_t native_coin_map; - native_coin_map.insert(*balances_map_it); - balances_map.erase(balances_map_it); - print_map(native_coin_map); - } - print_map(balances_map); - - - //print whitelist - ss << "WHITELIST: " << ENDL; - - for(const auto& entry : m_whitelisted_assets) - { - ss << " " << std::left << entry.first << " " << entry.second.ticker << ENDL; - } - - // print custom list - ss << "CUSTOM LIST: " << ENDL; - - for(const auto& entry : m_custom_assets) - { - ss << " " << std::left << entry.first << " " << entry.second.ticker << ENDL; - } - - // print own list - ss << "OWN DESCRIPTORS LIST: " << ENDL; - - for(const auto& entry : m_own_asset_descriptors) - { - ss << " " << std::left << entry.first << " " << entry.second.ticker << ENDL; - } - - return ss.str(); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::get_payments(const std::string& payment_id, std::list& payments, uint64_t min_height) const -{ - auto range = m_payments.equal_range(payment_id); - std::for_each(range.first, range.second, [&payments, &min_height](const payment_container::value_type& x) - { - if (min_height <= x.second.m_block_height) + for (auto& subtr : wti.subtransfers) { - payments.push_back(x.second); + ss << (subtr.is_income ? "[INC]" : "[OUT]") << "\t" + << epee::misc_utils::get_time_str(wti.timestamp) << "\t" + << print_money(subtr.amount) << "\t" + << subtr.asset_id << "\t" + << print_money(wti.fee) << "\t" + << wti.remote_addresses << "\t" + << wti.comment << ENDL; } - }); -} -//---------------------------------------------------------------------------------------------------- -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().view_secret_key); - - // deserialize args - currency::finalized_tx ft = AUTO_VAL_INIT(ft); - bool r = t_unserializable_object_from_blob(ft.ftp, decrypted_src_blob); - THROW_IF_FALSE_WALLET_EX(r, error::wallet_common_error, "Failed to decrypt tx sources blob"); - - // make sure unsigned tx was created with the same keys - THROW_IF_FALSE_WALLET_EX(ft.ftp.spend_pub_key == m_account.get_keys().account_address.spend_public_key, error::wallet_common_error, "The was created in a different wallet, keys missmatch"); - - finalize_transaction(ft.ftp, ft.tx, ft.one_time_key, false); - - // calculate key images for each change output - crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX( - crypto::generate_key_derivation( - m_account.get_keys().account_address.view_public_key, - ft.one_time_key, - derivation), - "internal error: sign_transfer: failed to generate key derivation(" - << m_account.get_keys().account_address.view_public_key - << ", view secret key: " << ft.one_time_key << ")"); - - for (size_t i = 0; i < ft.tx.vout.size(); ++i) + void wallet2::wti_to_json_line(std::ostream& ss, const wallet_public::wallet_transfer_info& wti, size_t index) { - VARIANT_SWITCH_BEGIN(ft.tx.vout[i]); - VARIANT_CASE_CONST(tx_out_bare, out) + ss << epee::serialization::store_t_to_json(wti, 4) << ","; + }; + + //---------------------------------------------------------------------------------------------------- + void wallet2::set_connectivity_options(unsigned int timeout) + { + m_core_proxy->set_connectivity(timeout, WALLET_RCP_COUNT_ATTEMNTS); + } + //---------------------------------------------------------------------------------------------------- + void wallet2::export_transaction_history(std::ostream& ss, const std::string& format, bool include_pos_transactions) + { + //typedef int(*t_somefunc)(int, int); + typedef void(*playout_cb_type)(std::ostream&, const wallet_public::wallet_transfer_info&, size_t); + playout_cb_type cb_csv = &wallet2::wti_to_csv_entry; + playout_cb_type cb_json = &wallet2::wti_to_json_line; + playout_cb_type cb_plain_text = &wallet2::wti_to_txt_line; + + playout_cb_type cb = cb_csv; + if (format == "json") { - if (out.target.type() != typeid(txout_to_key)) + ss << "{ \"history\": ["; + cb = cb_json; + } + else if (format == "text") + { + cb = cb_plain_text; + } + else + { + //csv by default + ss << "N, Date, Amount, AssetID, Comment, Address, ID, Height, Unlock timestamp, Tx size, Alias, PaymentID, In/Out, Flags, Type, Fee" << ENDL; + } + + + enum_container(m_transfer_history.begin(), m_transfer_history.end(), [&](wallet_public::wallet_transfer_info& wti, size_t index) { + if (!include_pos_transactions) + { + if (currency::is_coinbase(wti.tx)) + return true; + } + wti.fee = currency::get_tx_fee(wti.tx); + cb(ss, wti, index); + return true; + }); + + if (format == "json") + { + ss << "{}]}"; + } + + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::get_transfer_address(const std::string& adr_str, currency::account_public_address& addr, std::string& payment_id) + { + return m_core_proxy->get_transfer_address(adr_str, addr, payment_id); + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::is_transfer_okay_for_pos(const transfer_details& tr, bool is_zarcanum_hf, uint64_t& stake_unlock_time) const + { + if (is_zarcanum_hf) + { + if (!tr.is_zc()) + return false; + + if (!tr.is_native_coin()) + return false; + } + + if (!tr.is_spendable()) + return false; + + //blockchain conditions + if (!is_transfer_unlocked(tr, true, stake_unlock_time)) + return false; + + //prevent staking of after-last-pow-coins + if (get_blockchain_current_size() - tr.m_ptx_wallet_info->m_block_height <= m_core_runtime_config.min_coinstake_age) + return false; + + if (tr.m_ptx_wallet_info->m_block_height > m_last_pow_block_h) + return false; + + return true; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::get_mining_history(wallet_public::mining_history& hist, uint64_t timestamp_from) + { + for (auto& tr : m_transfer_history) + { + if (currency::is_coinbase(tr.tx) && tr.tx.vin.size() == 2 && tr.timestamp > timestamp_from) + { + tools::wallet_public::mining_history_entry mhe = AUTO_VAL_INIT(mhe); + mhe.a = tr.get_native_income_amount(); + mhe.t = tr.timestamp; + mhe.h = tr.height; + hist.mined_entries.push_back(mhe); + } + } + } + //---------------------------------------------------------------------------------------------------- + size_t wallet2::get_pos_entries_count() + { + bool is_zarcanum_hf = is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM); + size_t counter = 0; + + for (const auto& tr : m_transfers) + { + auto& tr = tr.second; + + uint64_t stake_unlock_time = 0; + if (!is_transfer_okay_for_pos(tr, is_zarcanum_hf, stake_unlock_time)) continue; - const txout_to_key& otk = boost::get(out.target); - crypto::public_key ephemeral_pub = AUTO_VAL_INIT(ephemeral_pub); - if (!crypto::derive_public_key(derivation, i, m_account.get_keys().account_address.spend_public_key, ephemeral_pub)) - { - WLT_LOG_ERROR("derive_public_key failed for tx " << get_transaction_hash(ft.tx) << ", out # " << i); - } - - if (otk.key == ephemeral_pub) - { - // this is the output to the given keys - // derive secret key and calculate key image - crypto::secret_key ephemeral_sec = AUTO_VAL_INIT(ephemeral_sec); - crypto::derive_secret_key(derivation, i, m_account.get_keys().spend_secret_key, ephemeral_sec); - crypto::key_image ki = AUTO_VAL_INIT(ki); - crypto::generate_key_image(ephemeral_pub, ephemeral_sec, ki); - - ft.outs_key_images.push_back(make_serializable_pair(static_cast(i), ki)); - } - } - VARIANT_CASE_CONST(tx_out_zarcanum, o); - //@#@ - VARIANT_SWITCH_END(); - } - - // serialize and encrypt the result - signed_tx_blob = t_serializable_object_to_blob(ft); - crypto::chacha_crypt(signed_tx_blob, m_account.get_keys().view_secret_key); - - tx = ft.tx; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::sign_transfer_files(const std::string& tx_sources_file, const std::string& signed_tx_file, currency::transaction& tx) -{ - std::string sources_blob; - bool r = epee::file_io_utils::load_file_to_string(tx_sources_file, sources_blob); - THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "failed to open file " << tx_sources_file); - - std::string signed_tx_blob; - sign_transfer(sources_blob, signed_tx_blob, tx); - - r = epee::file_io_utils::save_string_to_file(signed_tx_file, signed_tx_blob); - THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "failed to store signed tx to file " << signed_tx_file); -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::get_utxo_distribution(std::map& distribution) -{ - //TODO@#@ - /* - prepare_free_transfers_cache(0); - for (auto ent : m_found_free_amounts) - { - distribution[ent.first] = ent.second.size(); - } - */ - - return false; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::submit_transfer(const std::string& signed_tx_blob, currency::transaction& tx) -{ - // decrypt sources - std::string decrypted_src_blob = crypto::chacha_crypt(signed_tx_blob, m_account.get_keys().view_secret_key); - - // deserialize tx data - currency::finalized_tx ft = AUTO_VAL_INIT(ft); - bool r = t_unserializable_object_from_blob(ft, decrypted_src_blob); - THROW_IF_FALSE_WALLET_EX(r, error::wallet_common_error, "Failed to decrypt signed tx data"); - tx = ft.tx; - crypto::hash tx_hash = get_transaction_hash(tx); - - // foolproof - 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: " << tx_hash); - - try - { - send_transaction_to_network(tx); - } - catch (...) - { - // clear transfers flags if smth went wrong - uint32_t flag = WALLET_TRANSFER_DETAIL_FLAG_SPENT | WALLET_TRANSFER_DETAIL_FLAG_COLD_SIG_RESERVATION; - clear_transfers_from_flag(ft.ftp.selected_transfers, flag, "broadcasting tx " + epee::string_tools::pod_to_hex(tx_hash) + " was unsuccessful"); - throw; - } - - add_sent_tx_detailed_info(tx, ft.ftp.attachments, ft.ftp.prepared_destinations, ft.ftp.selected_transfers); - m_tx_keys.insert(std::make_pair(tx_hash, ft.one_time_key)); - - if (m_watch_only) - { - std::vector> pk_ki_to_be_added; - std::vector> tri_ki_to_be_added; - - for (auto& p : ft.outs_key_images) - { - THROW_IF_FALSE_WALLET_INT_ERR_EX(p.first < tx.vout.size(), "outs_key_images has invalid out index: " << p.first << ", tx.vout.size() = " << tx.vout.size()); - THROW_IF_FALSE_WALLET_INT_ERR_EX(tx.vout[p.first].type() == typeid(tx_out_bare), "Unexpected type in submit_transfer: " << tx.vout[p.first].type().name()); - auto& out = boost::get(tx.vout[p.first]); - THROW_IF_FALSE_WALLET_INT_ERR_EX(out.target.type() == typeid(txout_to_key), "outs_key_images has invalid out type, index: " << p.first); - const txout_to_key& otk = boost::get(out.target); - pk_ki_to_be_added.push_back(std::make_pair(otk.key, p.second)); + ++counter; } - THROW_IF_FALSE_WALLET_INT_ERR_EX(tx.vin.size() == ft.ftp.sources.size(), "tx.vin and ft.ftp.sources sizes missmatch"); - for (size_t i = 0; i < tx.vin.size(); ++i) + return counter; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::get_pos_entries(std::vector& entries) + { + bool is_zarcanum_hf = is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM); + for (const auto& tr : m_transfers) { - const txin_v& in = tx.vin[i]; - THROW_IF_FALSE_WALLET_CMN_ERR_EX(in.type() == typeid(txin_to_key), "tx " << tx_hash << " has a non txin_to_key input"); - const crypto::key_image& ki = boost::get(in).k_image; + uint64_t i = tr.first; + auto& tr = tr.second; - const auto& src = ft.ftp.sources[i]; - THROW_IF_FALSE_WALLET_INT_ERR_EX(src.real_output < src.outputs.size(), "src.real_output is out of bounds: " << src.real_output); - const crypto::public_key& out_key = src.outputs[src.real_output].stealth_address; + uint64_t stake_unlock_time = 0; + if (!is_transfer_okay_for_pos(tr, is_zarcanum_hf, stake_unlock_time)) + continue; - tri_ki_to_be_added.push_back(std::make_pair(src.transfer_index, ki)); - pk_ki_to_be_added.push_back(std::make_pair(out_key, ki)); + pos_entry pe = AUTO_VAL_INIT(pe); + pe.amount = tr.amount(); + pe.g_index = tr.m_global_output_index; + pe.keyimage = tr.m_key_image; + pe.wallet_index = i; + pe.stake_unlock_time = stake_unlock_time; + pe.block_timestamp = tr.m_ptx_wallet_info->m_block_timestamp; + entries.push_back(pe); } - for (auto& p : pk_ki_to_be_added) + return true; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::is_in_hardfork_zone(uint64_t hardfork_index) const + { + return m_core_runtime_config.is_hardfork_active_for_height(hardfork_index, get_blockchain_current_size()); + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::proxy_to_daemon(const std::string& uri, const std::string& body, int& response_code, std::string& response_body) + { + return m_core_proxy->call_COMMAND_RPC_INVOKE(uri, body, response_code, response_body); + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::prepare_and_sign_pos_block(const mining_context& cxt, uint64_t full_block_reward, const currency::pos_entry& pe, currency::tx_generation_context& miner_tx_tgc, currency::block& b) const + { + bool r = false; + //WLT_CHECK_AND_ASSERT_MES(pe.wallet_index < m_transfers.size(), false, "invalid pe.wallet_index: " << pe.wallet_index); + const transfer_details& td = m_transfers.at(pe.wallet_index); + const transaction& source_tx = td.m_ptx_wallet_info->m_tx; + const crypto::public_key source_tx_pub_key = get_tx_pub_key_from_extra(source_tx); + WLT_CHECK_AND_ASSERT_MES(pe.tx_out_index < source_tx.vout.size(), false, "invalid pe.tx_out_index: " << pe.tx_out_index); + const currency::tx_out_v& stake_out_v = source_tx.vout[pe.tx_out_index]; + + // calculate stake_out_derivation and secret_x (derived ephemeral secret key) + crypto::key_derivation stake_out_derivation = AUTO_VAL_INIT(stake_out_derivation); + r = crypto::generate_key_derivation(source_tx_pub_key, m_account.get_keys().view_secret_key, stake_out_derivation); // d = 8 * v * R + WLT_CHECK_AND_ASSERT_MES(r, false, "generate_key_derivation failed, tid: " << pe.wallet_index << ", pe.tx_id: " << pe.tx_id); + crypto::secret_key secret_x = AUTO_VAL_INIT(secret_x); + crypto::derive_secret_key(stake_out_derivation, pe.tx_out_index, m_account.get_keys().spend_secret_key, secret_x); // x = Hs(8 * v * R, i) + s + + if (!cxt.zarcanum) { - auto it = m_pending_key_images.find(p.first); - if (it != m_pending_key_images.end()) + // old PoS with non-hidden amounts + WLT_CHECK_AND_ASSERT_MES(b.miner_tx.vin[0].type() == typeid(currency::txin_gen), false, "Wrong input 0 type in transaction: " << b.miner_tx.vin[0].type().name()); + WLT_CHECK_AND_ASSERT_MES(b.miner_tx.vin[1].type() == typeid(currency::txin_to_key), false, "Wrong input 1 type in transaction: " << b.miner_tx.vin[1].type().name()); + WLT_CHECK_AND_ASSERT_MES(b.miner_tx.signatures.size() == 1 && b.miner_tx.signatures[0].type() == typeid(NLSAG_sig), false, "wrong sig prepared in a PoS block"); + WLT_CHECK_AND_ASSERT_MES(stake_out_v.type() == typeid(tx_out_bare), false, "unexpected stake output type: " << stake_out_v.type().name() << ", expected: tx_out_bare"); + const tx_out_bare& stake_out = boost::get(stake_out_v); + WLT_CHECK_AND_ASSERT_MES(stake_out.target.type() == typeid(txout_to_key), false, "unexpected stake output target type: " << stake_out.target.type().name() << ", expected: txout_to_key"); + + NLSAG_sig& sig = boost::get(b.miner_tx.signatures[0]); + txin_to_key& stake_input = boost::get(b.miner_tx.vin[1]); + const txout_to_key& stake_out_target = boost::get(stake_out.target); + + // partially fill stake input + stake_input.k_image = pe.keyimage; + stake_input.amount = pe.amount; + + // get decoys outputs and construct miner tx + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response decoys_resp = AUTO_VAL_INIT(decoys_resp); + std::vector ring; + uint64_t secret_index = 0; // index of the real stake output + if (m_required_decoys_count > 0 && !is_auditable()) { - LOG_PRINT_YELLOW("warning: for tx " << tx_hash << " out pub key " << p.first << " already exist in m_pending_key_images, ki: " << it->second << ", proposed new ki: " << p.second, LOG_LEVEL_0); + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request decoys_req = AUTO_VAL_INIT(decoys_req); + decoys_req.height_upper_limit = std::min(m_last_pow_block_h, m_last_known_daemon_height > m_core_runtime_config.min_coinstake_age ? m_last_known_daemon_height - m_core_runtime_config.min_coinstake_age : m_last_pow_block_h); + decoys_req.use_forced_mix_outs = false; + decoys_req.decoys_count = m_required_decoys_count + 1; // one more to be able to skip a decoy in case it hits the real output + decoys_req.amounts.push_back(pe.amount); // request one batch of decoys + + r = m_core_proxy->call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS(decoys_req, decoys_resp); + // TODO @#@# do we need these exceptions? + THROW_IF_FALSE_WALLET_EX(r, error::no_connection_to_daemon, "getrandom_outs1.bin"); + THROW_IF_FALSE_WALLET_EX(decoys_resp.status != API_RETURN_CODE_BUSY, error::daemon_busy, "getrandom_outs1.bin"); + THROW_IF_FALSE_WALLET_EX(decoys_resp.status == API_RETURN_CODE_OK, error::get_random_outs_error, decoys_resp.status); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(decoys_resp.outs.size() == 1, "got wrong number of decoys batches: " << decoys_resp.outs.size()); + + // we expect that less decoys can be returned than requested, we will use them all anyway + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(decoys_resp.outs[0].outs.size() <= m_required_decoys_count + 1, "for PoS stake tx got greater decoys to mix than requested: " << decoys_resp.outs[0].outs.size() << " < " << m_required_decoys_count + 1); + + auto& ring_candidates = decoys_resp.outs[0].outs; + ring_candidates.emplace_front(td.m_global_output_index, stake_out_target.key); + + std::unordered_set used_gindices; + size_t good_outs_count = 0; + for (auto it = ring_candidates.begin(); it != ring_candidates.end(); ) + { + if (used_gindices.count(it->global_amount_index) != 0) + { + it = ring_candidates.erase(it); + continue; + } + used_gindices.insert(it->global_amount_index); + if (++good_outs_count == m_required_decoys_count + 1) + { + ring_candidates.erase(++it, ring_candidates.end()); + break; + } + ++it; + } + + // won't assert that ring_candidates.size() == m_required_decoys_count + 1 here as we will use all the decoys anyway + if (ring_candidates.size() < m_required_decoys_count + 1) + LOG_PRINT_YELLOW("PoS: using " << ring_candidates.size() - 1 << " decoys for mining tx, while " << m_required_decoys_count << " are required", LOG_LEVEL_1); + + ring_candidates.sort([](auto& l, auto& r) { return l.global_amount_index < r.global_amount_index; }); // sort them now (note absolute_sorted_output_offsets_to_relative_in_place() below) + + uint64_t i = 0; + for (auto& el : ring_candidates) + { + uint64_t gindex = el.global_amount_index; + if (gindex == td.m_global_output_index) + secret_index = i; + ++i; + ring.emplace_back(&el.stealth_address); + stake_input.key_offsets.push_back(el.global_amount_index); + } + r = absolute_sorted_output_offsets_to_relative_in_place(stake_input.key_offsets); + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "absolute_sorted_output_offsets_to_relative_in_place failed"); } else { - m_pending_key_images[p.first] = p.second; - m_pending_key_images_file_container.push_back(tools::out_key_to_ki{ p.first, p.second }); - LOG_PRINT_L2("for tx " << tx_hash << " pending key image added (" << p.first << ", " << p.second << ")"); + // no decoys, the ring consist of one element -- the real stake output + ring.emplace_back(&stake_out_target.key); + stake_input.key_offsets.push_back(td.m_global_output_index); } - } - for (auto& p : tri_ki_to_be_added) - { - THROW_IF_FALSE_WALLET_INT_ERR_EX(p.first < m_transfers.size(), "incorrect transfer index: " << p.first); - auto& tr = m_transfers[p.first]; - if (tr.m_key_image != currency::null_ki && tr.m_key_image != p.second) + // sign block actually in coinbase transaction + crypto::hash block_hash = currency::get_block_hash(b); + + // generate sring signature + sig.s.resize(ring.size()); + crypto::generate_ring_signature(block_hash, stake_input.k_image, ring, secret_x, secret_index, sig.s.data()); + + if (epee::log_space::get_set_log_detalisation_level() >= LOG_LEVEL_4) { - 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); + std::stringstream ss; + ss << "GENERATED RING SIGNATURE for PoS block coinbase:" << ENDL << + " block hash: " << block_hash << ENDL << + " key image: " << stake_input.k_image << ENDL << + " ring:" << ENDL; + for (auto el : ring) + ss << " " << *el << ENDL; + ss << " signature:" << ENDL; + for (auto el : sig.s) + ss << " " << el << ENDL; + WLT_LOG_L4(ss.str()); } - tr.m_key_image = p.second; - m_key_images[p.second] = p.first; - LOG_PRINT_L2("for tx " << tx_hash << " key image " << p.second << " was associated with transfer # " << p.first); - } - } - // TODO: print inputs' key images - print_tx_sent_message(tx, "(from submit_transfer)"); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::submit_transfer_files(const std::string& signed_tx_file, currency::transaction& tx) -{ - std::string signed_tx_blob; - bool r = epee::file_io_utils::load_file_to_string(signed_tx_file, signed_tx_blob); - THROW_IF_FALSE_WALLET_EX(r, error::wallet_common_error, std::string("failed to open file ") + signed_tx_file); - - submit_transfer(signed_tx_blob, tx); -} -//---------------------------------------------------------------------------------------------------- -uint64_t wallet2::get_recent_transfers_total_count() -{ - return m_transfer_history.size(); -} -//---------------------------------------------------------------------------------------------------- -uint64_t wallet2::get_transfer_entries_count() -{ - return m_transfers.size(); -} -//---------------------------------------------------------------------------------------------------- - -template -bool enum_container(iterator_t it_begin, iterator_t it_end, callback_t cb) -{ - for (iterator_t it = it_begin; it != it_end; it++) - { - if (!cb(*it, it - it_begin)) return true; - } - return true; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::is_defragmentation_transaction(const wallet_public::wallet_transfer_info& wti) -{ - if (wti.employed_entries.receive.size() && wti.employed_entries.spent.size() && wti.subtransfers.size() == 1) - { - if (wti.subtransfers[0].asset_id == currency::native_coin_asset_id && !wti.subtransfers[0].is_income && wti.subtransfers[0].amount == get_tx_fee(wti.tx)) - return true; - } - return false; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::get_recent_transfers_history(std::vector& trs, size_t offset, size_t count, uint64_t& total, uint64_t& last_item_index, bool exclude_mining_txs, bool start_from_end) -{ - if (!count || offset >= m_transfer_history.size()) - return; - - auto cb = [&](wallet_public::wallet_transfer_info& wti, size_t local_offset) { - - if (exclude_mining_txs) - { - if (currency::is_coinbase(wti.tx) || is_defragmentation_transaction(wti)) - return true; - } - trs.push_back(wti); - load_wallet_transfer_info_flags(trs.back()); - last_item_index = offset + local_offset; - trs.back().transfer_internal_index = last_item_index; - - if (wti.remote_addresses.size() == 1) - { - wti.remote_aliases = get_aliases_for_address(wti.remote_addresses[0]); } - if (trs.size() >= count) - { - return false; - } - return true; - }; - - if(start_from_end) - enum_container(m_transfer_history.rbegin() + offset, m_transfer_history.rend(), cb); - else - enum_container(m_transfer_history.begin() + offset, m_transfer_history.end(), cb); + // Zarcanum - total = m_transfer_history.size(); - -} - -void wallet2::wti_to_csv_entry(std::ostream& ss, const wallet_public::wallet_transfer_info& wti, size_t index) -{ - for(auto& subtr: wti.subtransfers) - { - ss << index << ","; - ss << epee::misc_utils::get_time_str(wti.timestamp) << ","; - ss << print_money(subtr.amount) << ","; - ss << subtr.asset_id << ","; - ss << "\"" << wti.comment << "\","; - ss << "["; - std::copy(wti.remote_addresses.begin(), wti.remote_addresses.end(), std::ostream_iterator(ss, " ")); - ss << "]" << ","; - ss << wti.tx_hash << ","; - ss << wti.height << ","; - ss << wti.unlock_time << ","; - ss << wti.tx_blob_size << ","; - ss << epee::string_tools::buff_to_hex_nodelimer(wti.payment_id) << ","; - ss << "["; - std::copy(wti.remote_aliases.begin(), wti.remote_aliases.end(), std::ostream_iterator(ss, " ")); - ss << "]" << ","; - ss << (subtr.is_income ? "in" : "out") << ","; - ss << (wti.is_service ? "[SERVICE]" : "") << (wti.is_mixing ? "[MIXINS]" : "") << (wti.is_mining ? "[MINING]" : "") << ","; - ss << wti.tx_type << ","; - ss << print_money(wti.fee) << ENDL; - } - -}; - -void wallet2::wti_to_txt_line(std::ostream& ss, const wallet_public::wallet_transfer_info& wti, size_t index) -{ - for (auto& subtr : wti.subtransfers) - { - ss << (subtr.is_income ? "[INC]" : "[OUT]") << "\t" - << epee::misc_utils::get_time_str(wti.timestamp) << "\t" - << print_money(subtr.amount) << "\t" - << subtr.asset_id << "\t" - << print_money(wti.fee) << "\t" - << wti.remote_addresses << "\t" - << wti.comment << ENDL; - } -}; - -void wallet2::wti_to_json_line(std::ostream& ss, const wallet_public::wallet_transfer_info& wti, size_t index) -{ - ss << epee::serialization::store_t_to_json(wti, 4) << ","; -}; - -//---------------------------------------------------------------------------------------------------- -void wallet2::set_connectivity_options(unsigned int timeout) -{ - m_core_proxy->set_connectivity(timeout, WALLET_RCP_COUNT_ATTEMNTS); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::export_transaction_history(std::ostream& ss, const std::string& format, bool include_pos_transactions) -{ - //typedef int(*t_somefunc)(int, int); - typedef void(*playout_cb_type)(std::ostream&, const wallet_public::wallet_transfer_info&, size_t); - playout_cb_type cb_csv = &wallet2::wti_to_csv_entry; - playout_cb_type cb_json = &wallet2::wti_to_json_line; - playout_cb_type cb_plain_text = &wallet2::wti_to_txt_line; - - playout_cb_type cb = cb_csv; - if (format == "json") - { - ss << "{ \"history\": ["; - cb = cb_json; - } - else if (format == "text") - { - cb = cb_plain_text; - } - else - { - //csv by default - ss << "N, Date, Amount, AssetID, Comment, Address, ID, Height, Unlock timestamp, Tx size, Alias, PaymentID, In/Out, Flags, Type, Fee" << ENDL; - } - - - enum_container(m_transfer_history.begin(), m_transfer_history.end(), [&](wallet_public::wallet_transfer_info& wti, size_t index) { - if (!include_pos_transactions) - { - if (currency::is_coinbase(wti.tx)) - return true; - } - wti.fee = currency::get_tx_fee(wti.tx); - cb(ss, wti, index); - return true; - }); - - if (format == "json") - { - ss << "{}]}"; - } - -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::get_transfer_address(const std::string& adr_str, currency::account_public_address& addr, std::string& payment_id) -{ - return m_core_proxy->get_transfer_address(adr_str, addr, payment_id); -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::is_transfer_okay_for_pos(const transfer_details& tr, bool is_zarcanum_hf, uint64_t& stake_unlock_time) const -{ - if (is_zarcanum_hf) - { - if (!tr.is_zc()) - return false; - - if (!tr.is_native_coin()) - return false; - } - - if (!tr.is_spendable()) - return false; - - //blockchain conditions - if (!is_transfer_unlocked(tr, true, stake_unlock_time)) - return false; - - //prevent staking of after-last-pow-coins - if (get_blockchain_current_size() - tr.m_ptx_wallet_info->m_block_height <= m_core_runtime_config.min_coinstake_age) - return false; - - if (tr.m_ptx_wallet_info->m_block_height > m_last_pow_block_h) - return false; - - return true; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::get_mining_history(wallet_public::mining_history& hist, uint64_t timestamp_from) -{ - for (auto& tr : m_transfer_history) - { - if (currency::is_coinbase(tr.tx) && tr.tx.vin.size() == 2 && tr.timestamp > timestamp_from) - { - tools::wallet_public::mining_history_entry mhe = AUTO_VAL_INIT(mhe); - mhe.a = tr.get_native_income_amount(); - mhe.t = tr.timestamp; - mhe.h = tr.height; - hist.mined_entries.push_back(mhe); - } - } -} -//---------------------------------------------------------------------------------------------------- -size_t wallet2::get_pos_entries_count() -{ - bool is_zarcanum_hf = is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM); - size_t counter = 0; - - for (size_t i = 0, size = m_transfers.size(); i < size; i++) - { - auto& tr = m_transfers[i]; - - uint64_t stake_unlock_time = 0; - if (!is_transfer_okay_for_pos(tr, is_zarcanum_hf, stake_unlock_time)) - continue; - - ++counter; - } - - return counter; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::get_pos_entries(std::vector& entries) -{ - bool is_zarcanum_hf = is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM); - for (size_t i = 0; i != m_transfers.size(); i++) - { - auto& tr = m_transfers[i]; - - uint64_t stake_unlock_time = 0; - if (!is_transfer_okay_for_pos(tr, is_zarcanum_hf, stake_unlock_time)) - continue; - - pos_entry pe = AUTO_VAL_INIT(pe); - pe.amount = tr.amount(); - pe.g_index = tr.m_global_output_index; - pe.keyimage = tr.m_key_image; - pe.wallet_index = i; - pe.stake_unlock_time = stake_unlock_time; - pe.block_timestamp = tr.m_ptx_wallet_info->m_block_timestamp; - entries.push_back(pe); - } - - return true; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::is_in_hardfork_zone(uint64_t hardfork_index) const -{ - return m_core_runtime_config.is_hardfork_active_for_height(hardfork_index, get_blockchain_current_size()); -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::proxy_to_daemon(const std::string& uri, const std::string& body, int& response_code, std::string& response_body) -{ - return m_core_proxy->call_COMMAND_RPC_INVOKE(uri, body, response_code, response_body); -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::prepare_and_sign_pos_block(const mining_context& cxt, uint64_t full_block_reward, const currency::pos_entry& pe, currency::tx_generation_context& miner_tx_tgc, currency::block& b) const -{ - bool r = false; - WLT_CHECK_AND_ASSERT_MES(pe.wallet_index < m_transfers.size(), false, "invalid pe.wallet_index: " << pe.wallet_index); - const transfer_details& td = m_transfers[pe.wallet_index]; - const transaction& source_tx = td.m_ptx_wallet_info->m_tx; - const crypto::public_key source_tx_pub_key = get_tx_pub_key_from_extra(source_tx); - WLT_CHECK_AND_ASSERT_MES(pe.tx_out_index < source_tx.vout.size(), false, "invalid pe.tx_out_index: " << pe.tx_out_index); - const currency::tx_out_v& stake_out_v = source_tx.vout[pe.tx_out_index]; - - // calculate stake_out_derivation and secret_x (derived ephemeral secret key) - crypto::key_derivation stake_out_derivation = AUTO_VAL_INIT(stake_out_derivation); - r = crypto::generate_key_derivation(source_tx_pub_key, m_account.get_keys().view_secret_key, stake_out_derivation); // d = 8 * v * R - WLT_CHECK_AND_ASSERT_MES(r, false, "generate_key_derivation failed, tid: " << pe.wallet_index << ", pe.tx_id: " << pe.tx_id); - crypto::secret_key secret_x = AUTO_VAL_INIT(secret_x); - crypto::derive_secret_key(stake_out_derivation, pe.tx_out_index, m_account.get_keys().spend_secret_key, secret_x); // x = Hs(8 * v * R, i) + s - - if (!cxt.zarcanum) - { - // old PoS with non-hidden amounts + WLT_CHECK_AND_ASSERT_MES(td.is_zc(), false, "the transfer [" << pe.wallet_index << "] is not zc type, which is required for zarcanum"); WLT_CHECK_AND_ASSERT_MES(b.miner_tx.vin[0].type() == typeid(currency::txin_gen), false, "Wrong input 0 type in transaction: " << b.miner_tx.vin[0].type().name()); - WLT_CHECK_AND_ASSERT_MES(b.miner_tx.vin[1].type() == typeid(currency::txin_to_key), false, "Wrong input 1 type in transaction: " << b.miner_tx.vin[1].type().name()); - WLT_CHECK_AND_ASSERT_MES(b.miner_tx.signatures.size() == 1 && b.miner_tx.signatures[0].type() == typeid(NLSAG_sig), false, "wrong sig prepared in a PoS block"); - WLT_CHECK_AND_ASSERT_MES(stake_out_v.type() == typeid(tx_out_bare), false, "unexpected stake output type: " << stake_out_v.type().name() << ", expected: tx_out_bare"); - const tx_out_bare& stake_out = boost::get(stake_out_v); - WLT_CHECK_AND_ASSERT_MES(stake_out.target.type() == typeid(txout_to_key), false, "unexpected stake output target type: " << stake_out.target.type().name() << ", expected: txout_to_key"); - - NLSAG_sig& sig = boost::get(b.miner_tx.signatures[0]); - txin_to_key& stake_input = boost::get(b.miner_tx.vin[1]); - const txout_to_key& stake_out_target = boost::get(stake_out.target); - - // partially fill stake input - stake_input.k_image = pe.keyimage; - stake_input.amount = pe.amount; + WLT_CHECK_AND_ASSERT_MES(b.miner_tx.vin[1].type() == typeid(currency::txin_zc_input), false, "Wrong input 1 type in transaction: " << b.miner_tx.vin[1].type().name()); + WLT_CHECK_AND_ASSERT_MES(b.miner_tx.signatures.size() == 1 && b.miner_tx.signatures[0].type() == typeid(zarcanum_sig), false, "wrong sig prepared in a PoS block"); + WLT_CHECK_AND_ASSERT_MES(stake_out_v.type() == typeid(tx_out_zarcanum), false, "unexpected stake output type: " << stake_out_v.type().name() << ", expected: tx_out_zarcanum"); + WLT_CHECK_AND_ASSERT_MES(td.m_zc_info_ptr->asset_id == currency::native_coin_asset_id, false, "attempted to stake an output with a non-native asset id"); + + zarcanum_sig& sig = boost::get(b.miner_tx.signatures[0]); + txin_zc_input& stake_input = boost::get(b.miner_tx.vin[1]); + const tx_out_zarcanum& stake_out = boost::get(stake_out_v); + + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response decoys_resp = AUTO_VAL_INIT(decoys_resp); + std::vector ring; + uint64_t secret_index = 0; // index of the real stake output // get decoys outputs and construct miner tx - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response decoys_resp = AUTO_VAL_INIT(decoys_resp); - std::vector ring; - uint64_t secret_index = 0; // index of the real stake output - if (m_required_decoys_count > 0 && !is_auditable()) + const size_t required_decoys_count = m_core_runtime_config.hf4_minimum_mixins == 0 ? 4 /* <-- for tests */ : m_core_runtime_config.hf4_minimum_mixins; + static bool use_only_forced_to_mix = false; // TODO @#@# set them somewhere else + if (required_decoys_count > 0 && !is_auditable()) { COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request decoys_req = AUTO_VAL_INIT(decoys_req); - decoys_req.height_upper_limit = std::min(m_last_pow_block_h, m_last_known_daemon_height > m_core_runtime_config.min_coinstake_age ? m_last_known_daemon_height - m_core_runtime_config.min_coinstake_age : m_last_pow_block_h); - decoys_req.use_forced_mix_outs = false; - decoys_req.decoys_count = m_required_decoys_count + 1; // one more to be able to skip a decoy in case it hits the real output - decoys_req.amounts.push_back(pe.amount); // request one batch of decoys + decoys_req.height_upper_limit = m_last_pow_block_h; // request decoys to be either older than, or the same age as stake output's height + decoys_req.use_forced_mix_outs = use_only_forced_to_mix; + decoys_req.decoys_count = required_decoys_count + 1; // one more to be able to skip a decoy in case it hits the real output + decoys_req.amounts.push_back(0); // request one batch of decoys for hidden amounts r = m_core_proxy->call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS(decoys_req, decoys_resp); // TODO @#@# do we need these exceptions? @@ -4722,45 +4878,40 @@ bool wallet2::prepare_and_sign_pos_block(const mining_context& cxt, uint64_t ful THROW_IF_FALSE_WALLET_EX(decoys_resp.status != API_RETURN_CODE_BUSY, error::daemon_busy, "getrandom_outs1.bin"); THROW_IF_FALSE_WALLET_EX(decoys_resp.status == API_RETURN_CODE_OK, error::get_random_outs_error, decoys_resp.status); WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(decoys_resp.outs.size() == 1, "got wrong number of decoys batches: " << decoys_resp.outs.size()); - - // we expect that less decoys can be returned than requested, we will use them all anyway - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(decoys_resp.outs[0].outs.size() <= m_required_decoys_count + 1, "for PoS stake tx got greater decoys to mix than requested: " << decoys_resp.outs[0].outs.size() << " < " << m_required_decoys_count + 1); + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(decoys_resp.outs[0].outs.size() == required_decoys_count + 1, "for PoS stake tx got less decoys to mix than requested: " << decoys_resp.outs[0].outs.size() << " < " << required_decoys_count + 1); - auto& ring_candidates = decoys_resp.outs[0].outs; - ring_candidates.emplace_front(td.m_global_output_index, stake_out_target.key); + auto& decoys = decoys_resp.outs[0].outs; + decoys.emplace_front(td.m_global_output_index, stake_out.stealth_address, stake_out.amount_commitment, stake_out.concealing_point, stake_out.blinded_asset_id); std::unordered_set used_gindices; size_t good_outs_count = 0; - for(auto it = ring_candidates.begin(); it != ring_candidates.end(); ) + for (auto it = decoys.begin(); it != decoys.end(); ) { if (used_gindices.count(it->global_amount_index) != 0) { - it = ring_candidates.erase(it); + it = decoys.erase(it); continue; } used_gindices.insert(it->global_amount_index); - if (++good_outs_count == m_required_decoys_count + 1) + if (++good_outs_count == required_decoys_count + 1) { - ring_candidates.erase(++it, ring_candidates.end()); + decoys.erase(++it, decoys.end()); break; } ++it; } - - // won't assert that ring_candidates.size() == m_required_decoys_count + 1 here as we will use all the decoys anyway - if (ring_candidates.size() < m_required_decoys_count + 1) - LOG_PRINT_YELLOW("PoS: using " << ring_candidates.size() - 1 << " decoys for mining tx, while " << m_required_decoys_count << " are required", LOG_LEVEL_1); + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(decoys.size() == required_decoys_count + 1, "for PoS stake got less good decoys than required: " << decoys.size() << " < " << required_decoys_count); - ring_candidates.sort([](auto& l, auto& r){ return l.global_amount_index < r.global_amount_index; }); // sort them now (note absolute_sorted_output_offsets_to_relative_in_place() below) + decoys.sort([](auto& l, auto& r) { return l.global_amount_index < r.global_amount_index; }); // sort them now (note absolute_sorted_output_offsets_to_relative_in_place() below) uint64_t i = 0; - for(auto& el : ring_candidates) + for (auto& el : decoys) { uint64_t gindex = el.global_amount_index; if (gindex == td.m_global_output_index) secret_index = i; ++i; - ring.emplace_back(&el.stealth_address); + ring.emplace_back(el.stealth_address, el.amount_commitment, el.blinded_asset_id, el.concealing_point); stake_input.key_offsets.push_back(el.global_amount_index); } r = absolute_sorted_output_offsets_to_relative_in_place(stake_input.key_offsets); @@ -4769,2247 +4920,2148 @@ bool wallet2::prepare_and_sign_pos_block(const mining_context& cxt, uint64_t ful else { // no decoys, the ring consist of one element -- the real stake output - ring.emplace_back(&stake_out_target.key); + ring.emplace_back(stake_out.stealth_address, stake_out.amount_commitment, stake_out.blinded_asset_id, stake_out.concealing_point); stake_input.key_offsets.push_back(td.m_global_output_index); } + stake_input.k_image = pe.keyimage; - // sign block actually in coinbase transaction - crypto::hash block_hash = currency::get_block_hash(b); - - // generate sring signature - sig.s.resize(ring.size()); - crypto::generate_ring_signature(block_hash, stake_input.k_image, ring, secret_x, secret_index, sig.s.data()); - - if (epee::log_space::get_set_log_detalisation_level() >= LOG_LEVEL_4) + crypto::point_t stake_out_blinded_asset_id_pt = currency::native_coin_asset_id_pt + td.m_zc_info_ptr->asset_id_blinding_mask * crypto::c_point_X; +#ifndef NDEBUG { - std::stringstream ss; - ss << "GENERATED RING SIGNATURE for PoS block coinbase:" << ENDL << - " block hash: " << block_hash << ENDL << - " key image: " << stake_input.k_image << ENDL << - " ring:" << ENDL; - for(auto el: ring) - ss << " " << *el << ENDL; - ss << " signature:" << ENDL; - for(auto el: sig.s) - ss << " " << el << ENDL; - WLT_LOG_L4(ss.str()); + crypto::point_t source_amount_commitment = crypto::c_scalar_1div8 * td.m_amount * stake_out_blinded_asset_id_pt + crypto::c_scalar_1div8 * td.m_zc_info_ptr->amount_blinding_mask * crypto::c_point_G; + WLT_CHECK_AND_ASSERT_MES(stake_out.amount_commitment == source_amount_commitment.to_public_key(), false, "real output amount commitment check failed"); + WLT_CHECK_AND_ASSERT_MES(ring[secret_index].amount_commitment == stake_out.amount_commitment, false, "ring secret member doesn't match with the stake output"); + WLT_CHECK_AND_ASSERT_MES(cxt.stake_amount == td.m_amount, false, "stake_amount missmatch"); } +#endif + + crypto::hash hash_for_zarcanum_sig = get_block_hash(b); + + WLT_CHECK_AND_ASSERT_MES(miner_tx_tgc.pseudo_out_amount_blinding_masks_sum.is_zero(), false, "pseudo_out_amount_blinding_masks_sum is nonzero"); // it should be zero because there's only one ZC input (stake), and thus only one pseudo out (the sum is non-zero iff POC > 1) + crypto::scalar_t pseudo_out_amount_blinding_mask = miner_tx_tgc.amount_blinding_masks_sum; // sum of outputs' amount blinding masks + + miner_tx_tgc.pseudo_outs_blinded_asset_ids.emplace_back(currency::native_coin_asset_id_pt); // for Zarcanum stake inputs pseudo outputs commitments has explicit native asset id + miner_tx_tgc.pseudo_outs_plus_real_out_blinding_masks.emplace_back(0); + miner_tx_tgc.real_zc_ins_asset_ids.emplace_back(td.m_zc_info_ptr->asset_id); + // TODO @#@# [architecture] the same value is calculated in zarcanum_generate_proof(), consider an impovement + miner_tx_tgc.pseudo_out_amount_commitments_sum += cxt.stake_amount * stake_out_blinded_asset_id_pt + pseudo_out_amount_blinding_mask * crypto::c_point_G; + miner_tx_tgc.real_in_asset_id_blinding_mask_x_amount_sum += td.m_zc_info_ptr->asset_id_blinding_mask * cxt.stake_amount; + + uint8_t err = 0; + r = crypto::zarcanum_generate_proof(hash_for_zarcanum_sig, cxt.kernel_hash, ring, cxt.last_pow_block_id_hashed, cxt.sk.kimage, + secret_x, cxt.secret_q, secret_index, cxt.stake_amount, td.m_zc_info_ptr->asset_id_blinding_mask, cxt.stake_out_amount_blinding_mask, pseudo_out_amount_blinding_mask, + static_cast(sig), &err); + WLT_CHECK_AND_ASSERT_MES(r, false, "zarcanum_generate_proof failed, err: " << (int)err); + + // + // The miner tx prefix should be sealed by now, and the tx hash should be defined. + // Any changes made below should only affect the signatures/proofs and should not impact the prefix hash calculation. + // + crypto::hash miner_tx_id = get_transaction_hash(b.miner_tx); + + // proofs for miner_tx + + // asset surjection proof + currency::zc_asset_surjection_proof asp{}; + r = generate_asset_surjection_proof(miner_tx_id, false, miner_tx_tgc, asp); // has_non_zc_inputs == false because after the HF4 PoS mining is only allowed for ZC stakes inputs + WLT_CHECK_AND_ASSERT_MES(r, false, "generete_asset_surjection_proof failed"); + b.miner_tx.proofs.emplace_back(std::move(asp)); + + // range proofs + currency::zc_outs_range_proof range_proofs{}; + r = generate_zc_outs_range_proof(miner_tx_id, 0, miner_tx_tgc, b.miner_tx.vout, range_proofs); + WLT_CHECK_AND_ASSERT_MES(r, false, "Failed to generate zc_outs_range_proof()"); + b.miner_tx.proofs.emplace_back(std::move(range_proofs)); + + // balance proof + currency::zc_balance_proof balance_proof{}; + r = generate_tx_balance_proof(b.miner_tx, miner_tx_id, miner_tx_tgc, full_block_reward, balance_proof); + WLT_CHECK_AND_ASSERT_MES(r, false, "generate_tx_balance_proof failed"); + b.miner_tx.proofs.emplace_back(std::move(balance_proof)); + + // the following line are for debugging when necessary -- sowle + //err = 0; + //r = crypto::zarcanum_verify_proof(hash_for_zarcanum_sig, cxt.kernel_hash, ring, cxt.last_pow_block_id_hashed, cxt.sk.kimage, cxt.basic_diff, sig, &err); + //WLT_CHECK_AND_ASSERT_MES(r, false, "zarcanum_verify_proof failed with code " << (int)err); return true; } - - // Zarcanum - - WLT_CHECK_AND_ASSERT_MES(td.is_zc(), false, "the transfer [" << pe.wallet_index << "] is not zc type, which is required for zarcanum"); - WLT_CHECK_AND_ASSERT_MES(b.miner_tx.vin[0].type() == typeid(currency::txin_gen), false, "Wrong input 0 type in transaction: " << b.miner_tx.vin[0].type().name()); - WLT_CHECK_AND_ASSERT_MES(b.miner_tx.vin[1].type() == typeid(currency::txin_zc_input), false, "Wrong input 1 type in transaction: " << b.miner_tx.vin[1].type().name()); - WLT_CHECK_AND_ASSERT_MES(b.miner_tx.signatures.size() == 1 && b.miner_tx.signatures[0].type() == typeid(zarcanum_sig), false, "wrong sig prepared in a PoS block"); - WLT_CHECK_AND_ASSERT_MES(stake_out_v.type() == typeid(tx_out_zarcanum), false, "unexpected stake output type: " << stake_out_v.type().name() << ", expected: tx_out_zarcanum"); - WLT_CHECK_AND_ASSERT_MES(td.m_zc_info_ptr->asset_id == currency::native_coin_asset_id, false, "attempted to stake an output with a non-native asset id"); - - zarcanum_sig& sig = boost::get(b.miner_tx.signatures[0]); - txin_zc_input& stake_input = boost::get(b.miner_tx.vin[1]); - const tx_out_zarcanum& stake_out = boost::get(stake_out_v); - - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response decoys_resp = AUTO_VAL_INIT(decoys_resp); - std::vector ring; - uint64_t secret_index = 0; // index of the real stake output - - // get decoys outputs and construct miner tx - const size_t required_decoys_count = m_core_runtime_config.hf4_minimum_mixins == 0 ? 4 /* <-- for tests */ : m_core_runtime_config.hf4_minimum_mixins; - static bool use_only_forced_to_mix = false; // TODO @#@# set them somewhere else - if (required_decoys_count > 0 && !is_auditable()) + //------------------------------------------------------------------ + bool wallet2::fill_mining_context(mining_context& ctx) { - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request decoys_req = AUTO_VAL_INIT(decoys_req); - decoys_req.height_upper_limit = m_last_pow_block_h; // request decoys to be either older than, or the same age as stake output's height - decoys_req.use_forced_mix_outs = use_only_forced_to_mix; - decoys_req.decoys_count = required_decoys_count + 1; // one more to be able to skip a decoy in case it hits the real output - decoys_req.amounts.push_back(0); // request one batch of decoys for hidden amounts + currency::COMMAND_RPC_GET_POS_MINING_DETAILS::request pos_details_req = AUTO_VAL_INIT(pos_details_req); + currency::COMMAND_RPC_GET_POS_MINING_DETAILS::response pos_details_resp = AUTO_VAL_INIT(pos_details_resp); + m_core_proxy->call_COMMAND_RPC_GET_POS_MINING_DETAILS(pos_details_req, pos_details_resp); + if (pos_details_resp.status != API_RETURN_CODE_OK) + return false; - r = m_core_proxy->call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS(decoys_req, decoys_resp); - // TODO @#@# do we need these exceptions? - THROW_IF_FALSE_WALLET_EX(r, error::no_connection_to_daemon, "getrandom_outs1.bin"); - THROW_IF_FALSE_WALLET_EX(decoys_resp.status != API_RETURN_CODE_BUSY, error::daemon_busy, "getrandom_outs1.bin"); - THROW_IF_FALSE_WALLET_EX(decoys_resp.status == API_RETURN_CODE_OK, error::get_random_outs_error, decoys_resp.status); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(decoys_resp.outs.size() == 1, "got wrong number of decoys batches: " << decoys_resp.outs.size()); - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(decoys_resp.outs[0].outs.size() == required_decoys_count + 1, "for PoS stake tx got less decoys to mix than requested: " << decoys_resp.outs[0].outs.size() << " < " << required_decoys_count + 1); + ctx = mining_context{}; + ctx.init(wide_difficulty_type(pos_details_resp.pos_basic_difficulty), pos_details_resp.sm, is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM)); - auto& decoys = decoys_resp.outs[0].outs; - decoys.emplace_front(td.m_global_output_index, stake_out.stealth_address, stake_out.amount_commitment, stake_out.concealing_point, stake_out.blinded_asset_id); + ctx.last_block_hash = pos_details_resp.last_block_hash; + ctx.is_pos_allowed = pos_details_resp.pos_mining_allowed; + ctx.is_pos_sequence_factor_good = pos_details_resp.pos_sequence_factor_is_good; + ctx.starter_timestamp = pos_details_resp.starter_timestamp; + ctx.status = API_RETURN_CODE_NOT_FOUND; + return true; + } + //------------------------------------------------------------------ + bool wallet2::try_mint_pos() + { + return try_mint_pos(m_account.get_public_address()); + } + //------------------------------------------------------------------ + bool wallet2::try_mint_pos(const currency::account_public_address& miner_address) + { + TIME_MEASURE_START_MS(mining_duration_ms); + mining_context ctx = AUTO_VAL_INIT(ctx); + WLT_LOG_L2("Starting PoS mining iteration"); + fill_mining_context(ctx); - std::unordered_set used_gindices; - size_t good_outs_count = 0; - for(auto it = decoys.begin(); it != decoys.end(); ) + if (!ctx.is_pos_allowed) { - if (used_gindices.count(it->global_amount_index) != 0) + WLT_LOG_YELLOW("POS MINING NOT ALLOWED YET", LOG_LEVEL_0); + return true; + } + + if (!ctx.is_pos_sequence_factor_good) + { + WLT_LOG_YELLOW("PoS sequence factor is too high, waiting for a PoW block...", LOG_LEVEL_0); + return true; + } + + std::atomic stop(false); + scan_pos(ctx, stop, [this]() { + size_t blocks_fetched; + refresh(blocks_fetched); + if (blocks_fetched) + { + WLT_LOG_L0("Detected new block, minting interrupted"); + return false; + } + return true; + }, m_core_runtime_config); + + bool res = true; + if (ctx.status == API_RETURN_CODE_OK) + { + res = build_minted_block(ctx, miner_address); + } + TIME_MEASURE_FINISH_MS(mining_duration_ms); + + WLT_LOG_L0("PoS mining: " << ctx.iterations_processed << " iterations finished (" << std::fixed << std::setprecision(2) << (mining_duration_ms / 1000.0f) << "s), status: " << ctx.status << ", " << ctx.total_items_checked << " entries with total amount: " << print_money_brief(ctx.total_amount_checked)); + + return res; + } + //------------------------------------------------------------------ + void wallet2::do_pos_mining_prepare_entry(mining_context& context, const transfer_details& td) + { + //CHECK_AND_ASSERT_MES_NO_RET(transfer_index < m_transfers.size(), "transfer_index is out of bounds: " << transfer_index); + //const transfer_details& td = m_transfers.at(transfer_index); + + crypto::scalar_t amount_blinding_mask{}; + if (td.is_zc()) + amount_blinding_mask = td.m_zc_info_ptr->amount_blinding_mask; + + context.prepare_entry(td.amount(), td.m_key_image, get_tx_pub_key_from_extra(td.m_ptx_wallet_info->m_tx), td.m_internal_output_index, + amount_blinding_mask, m_account.get_keys().view_secret_key); + } + //------------------------------------------------------------------ + bool wallet2::do_pos_mining_iteration(mining_context& context, size_t transfer_index, uint64_t ts) + { + return context.do_iteration(ts); + } + //------------------------------- + bool wallet2::reset_history() + { + std::string pass = m_password; + std::wstring file_path = m_wallet_file; + account_base acc_tmp = m_account; + clear(); + m_account = acc_tmp; + m_password = pass; + prepare_file_names(file_path); + return true; + } + //------------------------------- + bool wallet2::build_minted_block(const mining_context& cxt) + { + return build_minted_block(cxt, m_account.get_public_address()); + } + //------------------------------------------------------------------ + std::string wallet2::get_extra_text_for_block(uint64_t new_block_expected_height) + { + size_t entries_voted = 0; + std::string extra_text = "{"; + for (const auto& e : m_votes_config.entries) + { + if (e.h_start <= new_block_expected_height && e.h_end >= new_block_expected_height) + { + //do vote for/against this + if (entries_voted != 0) + extra_text += ","; + extra_text += "\""; + extra_text += e.proposal_id; + extra_text += "\":"; + extra_text += e.vote ? "1" : "0"; + entries_voted++; + } + } + extra_text += "}"; + if (!entries_voted) + extra_text = ""; + return extra_text; + } + //------------------------------------------------------------------ + bool wallet2::build_minted_block(const mining_context& cxt, const currency::account_public_address& miner_address) + { + //found a block, construct it, sign and push to daemon + WLT_LOG_GREEN("Found kernel, constructing block", LOG_LEVEL_0); + + //WLT_CHECK_AND_ASSERT_MES(cxt.index < m_transfers.size(), false, "cxt.index = " << cxt.index << " is out of bounds"); + const transfer_details& td = m_transfers.at(cxt.index); + + currency::COMMAND_RPC_GETBLOCKTEMPLATE::request tmpl_req = AUTO_VAL_INIT(tmpl_req); + currency::COMMAND_RPC_GETBLOCKTEMPLATE::response tmpl_rsp = AUTO_VAL_INIT(tmpl_rsp); + tmpl_req.wallet_address = get_account_address_as_str(miner_address); + tmpl_req.stakeholder_address = get_account_address_as_str(m_account.get_public_address()); + tmpl_req.pos_block = true; + tmpl_req.extra_text = get_extra_text_for_block(m_chain.get_top_block_height()); + + tmpl_req.pe = AUTO_VAL_INIT(tmpl_req.pe); + tmpl_req.pe.amount = td.amount(); + tmpl_req.pe.block_timestamp = td.m_ptx_wallet_info->m_block_timestamp; + tmpl_req.pe.g_index = td.m_global_output_index; + tmpl_req.pe.keyimage = td.m_key_image; + tmpl_req.pe.stake_unlock_time = cxt.stake_unlock_time; + tmpl_req.pe.tx_id = td.tx_hash(); + tmpl_req.pe.tx_out_index = td.m_internal_output_index; + tmpl_req.pe.wallet_index = cxt.index; + + // mark stake source as spent and make sure it will be restored in case of error + const std::vector stake_transfer_idx_vec{ cxt.index }; + mark_transfers_as_spent(stake_transfer_idx_vec, "stake source"); + bool gracefull_leaving = false; + auto stake_transfer_spent_flag_restorer = epee::misc_utils::create_scope_leave_handler([&]() { + if (!gracefull_leaving) + clear_transfers_from_flag(stake_transfer_idx_vec, WALLET_TRANSFER_DETAIL_FLAG_SPENT, "stake source"); + }); + + // generate UTXO Defragmentation Transaction - to reduce the UTXO set size + transaction udtx{}; + if (generate_utxo_defragmentation_transaction_if_needed(udtx)) + { + tx_to_blob(udtx, tmpl_req.explicit_transaction); + WLT_LOG_GREEN("Note: " << udtx.vin.size() << " inputs were aggregated into UTXO defragmentation tx " << get_transaction_hash(udtx), LOG_LEVEL_0); + } + m_core_proxy->call_COMMAND_RPC_GETBLOCKTEMPLATE(tmpl_req, tmpl_rsp); + WLT_CHECK_AND_ASSERT_MES(tmpl_rsp.status == API_RETURN_CODE_OK, false, "Failed to create block template after kernel hash found! Status: " << tmpl_rsp.status); + + currency::block b = AUTO_VAL_INIT(b); + currency::blobdata block_blob; + bool res = epee::string_tools::parse_hexstr_to_binbuff(tmpl_rsp.blocktemplate_blob, block_blob); + WLT_CHECK_AND_ASSERT_MES(res, false, "parse_hexstr_to_binbuff() failed after kernel hash found!"); + res = parse_and_validate_block_from_blob(block_blob, b); + WLT_CHECK_AND_ASSERT_MES(res, false, "parse_and_validate_block_from_blob() failed after kernel hash found!"); + + if (cxt.last_block_hash != b.prev_id) + { + WLT_LOG_YELLOW("Kernel was found but block is behindhand, b.prev_id=" << b.prev_id << ", last_block_hash=" << cxt.last_block_hash, LOG_LEVEL_0); + return false; + } + + // set the timestamp from stake kernel + b.timestamp = cxt.sk.block_timestamp; + uint64_t current_timestamp = m_core_runtime_config.get_core_time(); + set_block_datetime(current_timestamp, b); + WLT_LOG_MAGENTA("Applying actual timestamp: " << current_timestamp, LOG_LEVEL_2); + + res = prepare_and_sign_pos_block(cxt, tmpl_rsp.block_reward, tmpl_req.pe, tmpl_rsp.miner_tx_tgc, b); + WLT_CHECK_AND_ASSERT_MES(res, false, "Failed to prepare_and_sign_pos_block"); + + crypto::hash block_hash = get_block_hash(b); + WLT_LOG_GREEN("Block " << print16(block_hash) << " @ " << get_block_height(b) << " has been constructed, sending to core...", LOG_LEVEL_0); + + currency::COMMAND_RPC_SUBMITBLOCK2::request subm_req = AUTO_VAL_INIT(subm_req); + currency::COMMAND_RPC_SUBMITBLOCK2::response subm_rsp = AUTO_VAL_INIT(subm_rsp); + subm_req.b = t_serializable_object_to_blob(b); + if (tmpl_req.explicit_transaction.size()) + subm_req.explicit_txs.push_back(hexemizer{ tmpl_req.explicit_transaction }); + + m_core_proxy->call_COMMAND_RPC_SUBMITBLOCK2(subm_req, subm_rsp); + if (subm_rsp.status != API_RETURN_CODE_OK) + { + WLT_LOG_ERROR("Constructed block " << print16(block_hash) << " was rejected by the core, status: " << subm_rsp.status); + return false; + } + WLT_LOG_GREEN("PoS block " << print16(block_hash) << " generated and accepted, congrats!", LOG_LEVEL_0); + m_wcallback->on_pos_block_found(b); + + gracefull_leaving = true; // to prevent source transfer flags be cleared in scope leave handler + return true; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::get_unconfirmed_transfers(std::vector& trs, bool exclude_mining_txs) + { + for (auto& u : m_unconfirmed_txs) + { + if (exclude_mining_txs && currency::is_coinbase(u.second.tx)) { - it = decoys.erase(it); continue; } - used_gindices.insert(it->global_amount_index); - if (++good_outs_count == required_decoys_count + 1) + trs.push_back(u.second); + load_wallet_transfer_info_flags(trs.back()); + } + } + //---------------------------------------------------------------------------------------------------- + void wallet2::set_core_runtime_config(const currency::core_runtime_config& pc) + { + m_core_runtime_config = pc; + } + //---------------------------------------------------------------------------------------------------- + currency::core_runtime_config& wallet2::get_core_runtime_config() + { + return m_core_runtime_config; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::is_transfer_unlocked(const transfer_details& td) const + { + uint64_t stub = 0; + return is_transfer_unlocked(td, false, stub); + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::is_transfer_unlocked(const transfer_details& td, bool for_pos_mining, uint64_t& stake_lock_time) const + { + if (td.m_flags & WALLET_TRANSFER_DETAIL_FLAG_BLOCKED) + return false; + + if (td.m_ptx_wallet_info->m_block_height + WALLET_DEFAULT_TX_SPENDABLE_AGE > get_blockchain_current_size()) + return false; + + + + uint64_t unlock_time = get_tx_unlock_time(td.m_ptx_wallet_info->m_tx, td.m_internal_output_index); + if (for_pos_mining && m_core_runtime_config.is_hardfork_active_for_height(1, get_blockchain_current_size())) + { + //allowed of staking locked coins with + stake_lock_time = unlock_time; + } + else + { + if (!currency::is_tx_spendtime_unlocked(unlock_time, get_blockchain_current_size(), m_core_runtime_config.get_core_time())) + return false; + } + return true; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::push_offer(const bc_services::offer_details_ex& od, currency::transaction& res_tx) + { + currency::tx_destination_entry tx_dest; + tx_dest.addr.push_back(m_account.get_keys().account_address); + tx_dest.amount = m_core_runtime_config.tx_default_fee; + std::vector destinations; + std::vector extra; + std::vector attachments; + + bc_services::put_offer_into_attachment(static_cast(od), attachments); + + destinations.push_back(tx_dest); + transfer(destinations, 0, 0, od.fee, extra, attachments, get_current_split_strategy(), tx_dust_policy(DEFAULT_DUST_THRESHOLD), res_tx); + } + //---------------------------------------------------------------------------------------------------- + const transaction& wallet2::get_transaction_by_id(const crypto::hash& tx_hash) + { + for (auto it = m_transfer_history.rbegin(); it != m_transfer_history.rend(); it++) + { + if (it->tx_hash == tx_hash) + return it->tx; + } + ASSERT_MES_AND_THROW("Tx " << tx_hash << " not found in wallet"); + } + //---------------------------------------------------------------------------------------------------- + void wallet2::cancel_offer_by_id(const crypto::hash& tx_id, uint64_t of_ind, uint64_t fee, currency::transaction& res_tx) + { + std::vector extra; + std::vector attachments; + bc_services::cancel_offer co = AUTO_VAL_INIT(co); + co.offer_index = of_ind; + co.tx_id = tx_id; + const transaction& related_tx = get_transaction_by_id(tx_id); + crypto::public_key related_tx_pub_key = get_tx_pub_key_from_extra(related_tx); + currency::keypair ephemeral = AUTO_VAL_INIT(ephemeral); + bool r = currency::derive_ephemeral_key_helper(m_account.get_keys(), related_tx_pub_key, of_ind, ephemeral); + CHECK_AND_ASSERT_THROW_MES(r, "derive_ephemeral_key_helper failed, tx_id: " << tx_id << ", of_ind:" << of_ind); + + blobdata sig_blob = bc_services::make_offer_sig_blob(co); + crypto::generate_signature(crypto::cn_fast_hash(sig_blob.data(), sig_blob.size()), ephemeral.pub, ephemeral.sec, co.sig); + bc_services::put_offer_into_attachment(co, attachments); + + transfer(std::vector(), 0, 0, fee, extra, attachments, get_current_split_strategy(), 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) + { + currency::tx_destination_entry tx_dest; + tx_dest.addr.push_back(m_account.get_keys().account_address); + tx_dest.amount = m_core_runtime_config.tx_default_fee; + std::vector destinations; + std::vector extra; + std::vector attachments; + bc_services::update_offer uo = AUTO_VAL_INIT(uo); + uo.offer_index = of_ind; + uo.tx_id = tx_id; + uo.of = od; + const transaction& related_tx = get_transaction_by_id(tx_id); + crypto::public_key related_tx_pub_key = get_tx_pub_key_from_extra(related_tx); + currency::keypair ephemeral; + bool r = currency::derive_ephemeral_key_helper(m_account.get_keys(), related_tx_pub_key, of_ind, ephemeral); + CHECK_AND_ASSERT_THROW_MES(r, "Failed to derive_ephemeral_key_helper" << tx_id); + + blobdata sig_blob = bc_services::make_offer_sig_blob(uo); + crypto::generate_signature(crypto::cn_fast_hash(sig_blob.data(), sig_blob.size()), ephemeral.pub, ephemeral.sec, uo.sig); + bc_services::put_offer_into_attachment(uo, attachments); + + destinations.push_back(tx_dest); + transfer(destinations, 0, 0, od.fee, extra, attachments, get_current_split_strategy(), tx_dust_policy(DEFAULT_DUST_THRESHOLD), res_tx); + } + //---------------------------------------------------------------------------------------------------- + void wallet2::push_alias_info_to_extra_according_to_hf_status(const currency::extra_alias_entry& ai, std::vector& extra) + { + if (m_core_runtime_config.is_hardfork_active_for_height(2, get_top_block_height())) + { + // after HF2 + extra.push_back(ai); + } + else + { + // before HF2 + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(!ai.m_address.is_auditable(), "auditable addresses are not supported in aliases prior to HF2"); + extra.push_back(ai.to_old()); + } + } + //---------------------------------------------------------------------------------------------------- + uint64_t wallet2::get_alias_cost(const std::string& alias) + { + currency::COMMAND_RPC_GET_ALIAS_REWARD::request req = AUTO_VAL_INIT(req); + currency::COMMAND_RPC_GET_ALIAS_REWARD::response rsp = AUTO_VAL_INIT(rsp); + req.alias = alias; + if (!m_core_proxy->call_COMMAND_RPC_GET_ALIAS_REWARD(req, rsp)) + { + throw std::runtime_error(std::string("Failed to get alias cost")); + } + + return rsp.reward; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::request_alias_registration(currency::extra_alias_entry& ai, currency::transaction& res_tx, uint64_t fee, uint64_t reward, const crypto::secret_key& authority_key) + { + if (!validate_alias_name(ai.m_alias)) + { + throw std::runtime_error(std::string("wrong alias characters: ") + ai.m_alias); + } + + if (ai.m_alias.size() < ALIAS_MINIMUM_PUBLIC_SHORT_NAME_ALLOWED) + { + if (authority_key == currency::null_skey) { - decoys.erase(++it, decoys.end()); - break; + throw std::runtime_error(std::string("Short aliases is not allowed without authority key: ") + ALIAS_SHORT_NAMES_VALIDATION_PUB_KEY); } - ++it; + crypto::public_key authority_pub = AUTO_VAL_INIT(authority_pub); + bool r = crypto::secret_key_to_public_key(authority_key, authority_pub); + CHECK_AND_ASSERT_THROW_MES(r, "Failed to generate pub key from secrete authority key"); + + if (string_tools::pod_to_hex(authority_pub) != ALIAS_SHORT_NAMES_VALIDATION_PUB_KEY) + { + throw std::runtime_error(std::string("Short aliases is not allowed to register by this authority key")); + } + r = currency::sign_extra_alias_entry(ai, authority_pub, authority_key); + CHECK_AND_ASSERT_THROW_MES(r, "Failed to sign alias update"); + WLT_LOG_L2("Generated update alias info: " << ENDL + << "alias: " << ai.m_alias << ENDL + << "signature: " << currency::print_t_array(ai.m_sign) << ENDL + << "signed(owner) pub key: " << m_account.get_keys().account_address.spend_public_key << ENDL + << "to address: " << get_account_address_as_str(ai.m_address) << ENDL + << "sign_buff_hash: " << currency::get_sign_buff_hash_for_alias_update(ai) + ); } - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(decoys.size() == required_decoys_count + 1, "for PoS stake got less good decoys than required: " << decoys.size() << " < " << required_decoys_count); - decoys.sort([](auto& l, auto& r){ return l.global_amount_index < r.global_amount_index; }); // sort them now (note absolute_sorted_output_offsets_to_relative_in_place() below) - - uint64_t i = 0; - for(auto& el : decoys) + if (!reward) { - uint64_t gindex = el.global_amount_index; - if (gindex == td.m_global_output_index) - secret_index = i; - ++i; - ring.emplace_back(el.stealth_address, el.amount_commitment, el.blinded_asset_id, el.concealing_point); - stake_input.key_offsets.push_back(el.global_amount_index); + reward = get_alias_cost(ai.m_alias); } - r = absolute_sorted_output_offsets_to_relative_in_place(stake_input.key_offsets); - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "absolute_sorted_output_offsets_to_relative_in_place failed"); + + std::vector destinations; + std::vector extra; + std::vector attachments; + + push_alias_info_to_extra_according_to_hf_status(ai, extra); + + currency::tx_destination_entry tx_dest_alias_reward; + tx_dest_alias_reward.addr.resize(1); + get_aliases_reward_account(tx_dest_alias_reward.addr.back()); + tx_dest_alias_reward.amount = reward; + tx_dest_alias_reward.flags |= tx_destination_entry_flags::tdef_explicit_native_asset_id | tx_destination_entry_flags::tdef_zero_amount_blinding_mask; + destinations.push_back(tx_dest_alias_reward); + + transfer(destinations, 0, 0, fee, extra, attachments, get_current_split_strategy(), tx_dust_policy(DEFAULT_DUST_THRESHOLD), res_tx, CURRENCY_TO_KEY_OUT_RELAXED, false); } - else + //---------------------------------------------------------------------------------------------------- + void wallet2::deploy_new_asset(const currency::asset_descriptor_base& asset_info, const std::vector& destinations, currency::transaction& result_tx, crypto::public_key& new_asset_id) { - // no decoys, the ring consist of one element -- the real stake output - ring.emplace_back(stake_out.stealth_address, stake_out.amount_commitment, stake_out.blinded_asset_id, stake_out.concealing_point); - stake_input.key_offsets.push_back(td.m_global_output_index); + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(asset_info.decimal_point <= 18, "too big decimal point: " << asset_info.decimal_point); + + asset_descriptor_operation asset_reg_info = AUTO_VAL_INIT(asset_reg_info); + asset_reg_info.descriptor = asset_info; + asset_reg_info.operation_type = ASSET_DESCRIPTOR_OPERATION_REGISTER; + construct_tx_param ctp = get_default_construct_tx_param(); + ctp.dsts = destinations; + ctp.extra.push_back(asset_reg_info); + ctp.need_at_least_1_zc = true; + + finalized_tx ft = AUTO_VAL_INIT(ft); + this->transfer(ctp, ft, true, nullptr); + result_tx = ft.tx; + //get generated asset id + currency::asset_descriptor_operation ado = AUTO_VAL_INIT(ado); + bool r = get_type_in_variant_container(result_tx.extra, ado); + CHECK_AND_ASSERT_THROW_MES(r, "Failed find asset info in tx"); + CHECK_AND_ASSERT_THROW_MES(get_or_calculate_asset_id(ado, nullptr, &new_asset_id), "get_or_calculate_asset_id failed"); + + m_custom_assets[new_asset_id] = ado.descriptor; } - stake_input.k_image = pe.keyimage; - - crypto::point_t stake_out_blinded_asset_id_pt = currency::native_coin_asset_id_pt + td.m_zc_info_ptr->asset_id_blinding_mask * crypto::c_point_X; -#ifndef NDEBUG + //---------------------------------------------------------------------------------------------------- + void wallet2::emit_asset(const crypto::public_key asset_id, std::vector& destinations, currency::transaction& result_tx) { - crypto::point_t source_amount_commitment = crypto::c_scalar_1div8 * td.m_amount * stake_out_blinded_asset_id_pt + crypto::c_scalar_1div8 * td.m_zc_info_ptr->amount_blinding_mask * crypto::c_point_G; - WLT_CHECK_AND_ASSERT_MES(stake_out.amount_commitment == source_amount_commitment.to_public_key(), false, "real output amount commitment check failed"); - WLT_CHECK_AND_ASSERT_MES(ring[secret_index].amount_commitment == stake_out.amount_commitment, false, "ring secret member doesn't match with the stake output"); - WLT_CHECK_AND_ASSERT_MES(cxt.stake_amount == td.m_amount, false, "stake_amount missmatch"); + + auto own_asset_entry_it = m_own_asset_descriptors.find(asset_id); + CHECK_AND_ASSERT_THROW_MES(own_asset_entry_it != m_own_asset_descriptors.end(), "Failed find asset_id " << asset_id << " in own assets list"); + COMMAND_RPC_GET_ASSET_INFO::request req; + req.asset_id = asset_id; + COMMAND_RPC_GET_ASSET_INFO::response rsp; + bool r = m_core_proxy->call_COMMAND_RPC_GET_ASSET_INFO(req, rsp); + CHECK_AND_ASSERT_THROW_MES(r, "Failed to call_COMMAND_RPC_GET_ASSET_INFO"); + + asset_descriptor_operation asset_emmit_info = AUTO_VAL_INIT(asset_emmit_info); + asset_emmit_info.descriptor = rsp.asset_descriptor; + asset_emmit_info.operation_type = ASSET_DESCRIPTOR_OPERATION_EMIT; + asset_emmit_info.opt_asset_id = asset_id; + construct_tx_param ctp = get_default_construct_tx_param(); + ctp.dsts = destinations; + ctp.extra.push_back(asset_emmit_info); + ctp.need_at_least_1_zc = true; + ctp.ado_current_asset_owner = rsp.asset_descriptor.owner; + //ctp.asset_deploy_control_key = own_asset_entry_it->second.control_key; + + for (auto& dst : ctp.dsts) + { + dst.asset_id = null_pkey; // emit operation requires null_pkey for emitting asset outputs, fix it ad-hoc here + } + + finalized_tx ft = AUTO_VAL_INIT(ft); + this->transfer(ctp, ft, true, nullptr); + result_tx = ft.tx; } - #endif - - crypto::hash hash_for_zarcanum_sig = get_block_hash(b); - - WLT_CHECK_AND_ASSERT_MES(miner_tx_tgc.pseudo_out_amount_blinding_masks_sum.is_zero(), false, "pseudo_out_amount_blinding_masks_sum is nonzero"); // it should be zero because there's only one ZC input (stake), and thus only one pseudo out (the sum is non-zero iff POC > 1) - crypto::scalar_t pseudo_out_amount_blinding_mask = miner_tx_tgc.amount_blinding_masks_sum; // sum of outputs' amount blinding masks - - miner_tx_tgc.pseudo_outs_blinded_asset_ids.emplace_back(currency::native_coin_asset_id_pt); // for Zarcanum stake inputs pseudo outputs commitments has explicit native asset id - miner_tx_tgc.pseudo_outs_plus_real_out_blinding_masks.emplace_back(0); - miner_tx_tgc.real_zc_ins_asset_ids.emplace_back(td.m_zc_info_ptr->asset_id); - // TODO @#@# [architecture] the same value is calculated in zarcanum_generate_proof(), consider an impovement - miner_tx_tgc.pseudo_out_amount_commitments_sum += cxt.stake_amount * stake_out_blinded_asset_id_pt + pseudo_out_amount_blinding_mask * crypto::c_point_G; - miner_tx_tgc.real_in_asset_id_blinding_mask_x_amount_sum += td.m_zc_info_ptr->asset_id_blinding_mask * cxt.stake_amount; - - uint8_t err = 0; - r = crypto::zarcanum_generate_proof(hash_for_zarcanum_sig, cxt.kernel_hash, ring, cxt.last_pow_block_id_hashed, cxt.sk.kimage, - secret_x, cxt.secret_q, secret_index, cxt.stake_amount, td.m_zc_info_ptr->asset_id_blinding_mask, cxt.stake_out_amount_blinding_mask, pseudo_out_amount_blinding_mask, - static_cast(sig), &err); - WLT_CHECK_AND_ASSERT_MES(r, false, "zarcanum_generate_proof failed, err: " << (int)err); - - // - // The miner tx prefix should be sealed by now, and the tx hash should be defined. - // Any changes made below should only affect the signatures/proofs and should not impact the prefix hash calculation. - // - crypto::hash miner_tx_id = get_transaction_hash(b.miner_tx); - - // proofs for miner_tx - - // asset surjection proof - currency::zc_asset_surjection_proof asp{}; - r = generate_asset_surjection_proof(miner_tx_id, false, miner_tx_tgc, asp); // has_non_zc_inputs == false because after the HF4 PoS mining is only allowed for ZC stakes inputs - WLT_CHECK_AND_ASSERT_MES(r, false, "generete_asset_surjection_proof failed"); - b.miner_tx.proofs.emplace_back(std::move(asp)); - - // range proofs - currency::zc_outs_range_proof range_proofs{}; - r = generate_zc_outs_range_proof(miner_tx_id, 0, miner_tx_tgc, b.miner_tx.vout, range_proofs); - WLT_CHECK_AND_ASSERT_MES(r, false, "Failed to generate zc_outs_range_proof()"); - b.miner_tx.proofs.emplace_back(std::move(range_proofs)); - - // balance proof - currency::zc_balance_proof balance_proof{}; - r = generate_tx_balance_proof(b.miner_tx, miner_tx_id, miner_tx_tgc, full_block_reward, balance_proof); - WLT_CHECK_AND_ASSERT_MES(r, false, "generate_tx_balance_proof failed"); - b.miner_tx.proofs.emplace_back(std::move(balance_proof)); - - // the following line are for debugging when necessary -- sowle - //err = 0; - //r = crypto::zarcanum_verify_proof(hash_for_zarcanum_sig, cxt.kernel_hash, ring, cxt.last_pow_block_id_hashed, cxt.sk.kimage, cxt.basic_diff, sig, &err); - //WLT_CHECK_AND_ASSERT_MES(r, false, "zarcanum_verify_proof failed with code " << (int)err); - - return true; -} -//------------------------------------------------------------------ -bool wallet2::fill_mining_context(mining_context& ctx) -{ - currency::COMMAND_RPC_GET_POS_MINING_DETAILS::request pos_details_req = AUTO_VAL_INIT(pos_details_req); - currency::COMMAND_RPC_GET_POS_MINING_DETAILS::response pos_details_resp = AUTO_VAL_INIT(pos_details_resp); - m_core_proxy->call_COMMAND_RPC_GET_POS_MINING_DETAILS(pos_details_req, pos_details_resp); - if (pos_details_resp.status != API_RETURN_CODE_OK) - return false; - - ctx = mining_context{}; - ctx.init(wide_difficulty_type(pos_details_resp.pos_basic_difficulty), pos_details_resp.sm, is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM)); - - ctx.last_block_hash = pos_details_resp.last_block_hash; - ctx.is_pos_allowed = pos_details_resp.pos_mining_allowed; - ctx.is_pos_sequence_factor_good = pos_details_resp.pos_sequence_factor_is_good; - ctx.starter_timestamp = pos_details_resp.starter_timestamp; - ctx.status = API_RETURN_CODE_NOT_FOUND; - return true; -} -//------------------------------------------------------------------ -bool wallet2::try_mint_pos() -{ - return try_mint_pos(m_account.get_public_address()); -} -//------------------------------------------------------------------ -bool wallet2::try_mint_pos(const currency::account_public_address& miner_address) -{ - TIME_MEASURE_START_MS(mining_duration_ms); - mining_context ctx = AUTO_VAL_INIT(ctx); - WLT_LOG_L2("Starting PoS mining iteration"); - fill_mining_context(ctx); - - if (!ctx.is_pos_allowed) + //---------------------------------------------------------------------------------------------------- + void wallet2::update_asset(const crypto::public_key asset_id, const currency::asset_descriptor_base new_descriptor, currency::transaction& result_tx) { - WLT_LOG_YELLOW("POS MINING NOT ALLOWED YET", LOG_LEVEL_0); + auto own_asset_entry_it = m_own_asset_descriptors.find(asset_id); + CHECK_AND_ASSERT_THROW_MES(own_asset_entry_it != m_own_asset_descriptors.end(), "Failed find asset_id " << asset_id << " in own assets list"); + + asset_descriptor_operation asset_update_info = AUTO_VAL_INIT(asset_update_info); + asset_update_info.descriptor = new_descriptor; + asset_update_info.operation_type = ASSET_DESCRIPTOR_OPERATION_UPDATE; + asset_update_info.opt_asset_id = asset_id; + construct_tx_param ctp = get_default_construct_tx_param(); + ctp.extra.push_back(asset_update_info); + ctp.need_at_least_1_zc = true; + currency::asset_descriptor_base adb = AUTO_VAL_INIT(adb); + bool r = this->daemon_get_asset_info(asset_id, adb); + CHECK_AND_ASSERT_THROW_MES(r, "Failed to get asset info from daemon"); + ctp.ado_current_asset_owner = adb.owner; + + finalized_tx ft = AUTO_VAL_INIT(ft); + this->transfer(ctp, ft, true, nullptr); + result_tx = ft.tx; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::transfer_asset_ownership(const crypto::public_key asset_id, const crypto::public_key& new_owner, currency::transaction& result_tx) + { + auto own_asset_entry_it = m_own_asset_descriptors.find(asset_id); + CHECK_AND_ASSERT_THROW_MES(own_asset_entry_it != m_own_asset_descriptors.end(), "Failed find asset_id " << asset_id << " in own assets list"); + + currency::asset_descriptor_base adb = AUTO_VAL_INIT(adb); + bool r = this->daemon_get_asset_info(asset_id, adb); + CHECK_AND_ASSERT_THROW_MES(r, "Failed to get asset info from daemon"); + + asset_descriptor_operation asset_update_info = AUTO_VAL_INIT(asset_update_info); + asset_update_info.descriptor = adb; + asset_update_info.operation_type = ASSET_DESCRIPTOR_OPERATION_UPDATE; + asset_update_info.opt_asset_id = asset_id; + asset_update_info.descriptor.owner = new_owner; + construct_tx_param ctp = get_default_construct_tx_param(); + ctp.ado_current_asset_owner = adb.owner; + ctp.extra.push_back(asset_update_info); + + finalized_tx ft = AUTO_VAL_INIT(ft); + this->transfer(ctp, ft, true, nullptr); + result_tx = ft.tx; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::burn_asset(const crypto::public_key asset_id, uint64_t amount_to_burn, currency::transaction& result_tx) + { + //auto own_asset_entry_it = m_own_asset_descriptors.find(asset_id); + //CHECK_AND_ASSERT_THROW_MES(own_asset_entry_it != m_own_asset_descriptors.end(), "Failed find asset_id " << asset_id << " in own assets list"); + COMMAND_RPC_GET_ASSET_INFO::request req; + req.asset_id = asset_id; + COMMAND_RPC_GET_ASSET_INFO::response rsp; + bool r = m_core_proxy->call_COMMAND_RPC_GET_ASSET_INFO(req, rsp); + CHECK_AND_ASSERT_THROW_MES(r, "Failed to call_COMMAND_RPC_GET_ASSET_INFO"); + + + + asset_descriptor_operation asset_burn_info = AUTO_VAL_INIT(asset_burn_info); + asset_burn_info.descriptor = rsp.asset_descriptor; + + CHECK_AND_ASSERT_THROW_MES(asset_burn_info.descriptor.current_supply >= amount_to_burn, "Wrong amount to burn (current_supply" << asset_burn_info.descriptor.current_supply << " is less then " << amount_to_burn << ")"); + + currency::tx_destination_entry dst_to_burn = AUTO_VAL_INIT(dst_to_burn); + dst_to_burn.amount = amount_to_burn; + dst_to_burn.asset_id = asset_id; + + asset_burn_info.operation_type = ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN; + asset_burn_info.opt_asset_id = asset_id; + construct_tx_param ctp = get_default_construct_tx_param(); + ctp.extra.push_back(asset_burn_info); + ctp.need_at_least_1_zc = true; + ctp.ado_current_asset_owner = rsp.asset_descriptor.owner; + ctp.dsts.push_back(dst_to_burn); + + finalized_tx ft = AUTO_VAL_INIT(ft); + this->transfer(ctp, ft, true, nullptr); + result_tx = ft.tx; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::daemon_get_asset_info(const crypto::public_key& asset_id, currency::asset_descriptor_base& adb) + { + COMMAND_RPC_GET_ASSET_INFO::request req; + req.asset_id = asset_id; + COMMAND_RPC_GET_ASSET_INFO::response rsp; + bool r = m_core_proxy->call_COMMAND_RPC_GET_ASSET_INFO(req, rsp); + CHECK_AND_ASSERT_MES(r, false, "Failed to call_COMMAND_RPC_GET_ASSET_INFO"); + adb = rsp.asset_descriptor; return true; } - - if (!ctx.is_pos_sequence_factor_good) + //---------------------------------------------------------------------------------------------------- + void wallet2::request_alias_update(currency::extra_alias_entry& ai, currency::transaction& res_tx, uint64_t fee) { - WLT_LOG_YELLOW("PoS sequence factor is too high, waiting for a PoW block...", LOG_LEVEL_0); - return true; - } + COMMAND_RPC_GET_ALIAS_DETAILS::request req; + req.alias = ai.m_alias; + COMMAND_RPC_GET_ALIAS_DETAILS::response rsp = AUTO_VAL_INIT(rsp); + bool r = m_core_proxy->call_COMMAND_RPC_GET_ALIAS_DETAILS(req, rsp); + CHECK_AND_ASSERT_THROW_MES(r, "Failed to call_COMMAND_RPC_GET_ALIAS_DETAILS"); - std::atomic stop(false); - scan_pos(ctx, stop, [this](){ - size_t blocks_fetched; - refresh(blocks_fetched); - if (blocks_fetched) + CHECK_AND_ASSERT_THROW_MES(rsp.status == API_RETURN_CODE_OK, "call_COMMAND_RPC_GET_ALIAS_DETAILS response: " << rsp.status); + + + currency::account_public_address addr = AUTO_VAL_INIT(addr); + currency::get_account_address_from_str(addr, rsp.alias_details.address); + + CHECK_AND_ASSERT_THROW_MES(m_account.get_public_address().spend_public_key == addr.spend_public_key && + m_account.get_public_address().view_public_key == addr.view_public_key, "call_COMMAND_RPC_GET_ALIAS_DETAILS: ownership is not confirmed"); + + if (!validate_alias_name(ai.m_alias)) { - WLT_LOG_L0("Detected new block, minting interrupted"); - return false; + throw std::runtime_error(std::string("wrong alias characters: ") + ai.m_alias); } - return true; - }, m_core_runtime_config); - - bool res = true; - if (ctx.status == API_RETURN_CODE_OK) - { - res = build_minted_block(ctx, miner_address); - } - TIME_MEASURE_FINISH_MS(mining_duration_ms); - - WLT_LOG_L0("PoS mining: " << ctx.iterations_processed << " iterations finished (" << std::fixed << std::setprecision(2) << (mining_duration_ms / 1000.0f) << "s), status: " << ctx.status << ", " << ctx.total_items_checked << " entries with total amount: " << print_money_brief(ctx.total_amount_checked)); - - return res; -} -//------------------------------------------------------------------ -void wallet2::do_pos_mining_prepare_entry(mining_context& context, size_t transfer_index) -{ - CHECK_AND_ASSERT_MES_NO_RET(transfer_index < m_transfers.size(), "transfer_index is out of bounds: " << transfer_index); - const transfer_details& td = m_transfers[transfer_index]; - - crypto::scalar_t amount_blinding_mask{}; - if (td.is_zc()) - amount_blinding_mask = td.m_zc_info_ptr->amount_blinding_mask; - - context.prepare_entry(td.amount(), td.m_key_image, get_tx_pub_key_from_extra(td.m_ptx_wallet_info->m_tx), td.m_internal_output_index, - amount_blinding_mask, m_account.get_keys().view_secret_key); -} -//------------------------------------------------------------------ -bool wallet2::do_pos_mining_iteration(mining_context& context, size_t transfer_index, uint64_t ts) -{ - return context.do_iteration(ts); -} -//------------------------------- -bool wallet2::reset_history() -{ - std::string pass = m_password; - std::wstring file_path = m_wallet_file; - account_base acc_tmp = m_account; - clear(); - m_account = acc_tmp; - m_password = pass; - prepare_file_names(file_path); - return true; -} -//------------------------------- -bool wallet2::build_minted_block(const mining_context& cxt) -{ - return build_minted_block(cxt, m_account.get_public_address()); -} -//------------------------------------------------------------------ -std::string wallet2::get_extra_text_for_block(uint64_t new_block_expected_height) -{ - size_t entries_voted = 0; - std::string extra_text = "{"; - for (const auto& e : m_votes_config.entries) - { - if (e.h_start <= new_block_expected_height && e.h_end >= new_block_expected_height) - { - //do vote for/against this - if (entries_voted != 0) - extra_text += ","; - extra_text += "\""; - extra_text += e.proposal_id; - extra_text += "\":"; - extra_text += e.vote ? "1" : "0"; - entries_voted++; - } - } - extra_text += "}"; - if (!entries_voted) - extra_text = ""; - return extra_text; -} -//------------------------------------------------------------------ -bool wallet2::build_minted_block(const mining_context& cxt, const currency::account_public_address& miner_address) -{ - //found a block, construct it, sign and push to daemon - WLT_LOG_GREEN("Found kernel, constructing block", LOG_LEVEL_0); - - WLT_CHECK_AND_ASSERT_MES(cxt.index < m_transfers.size(), false, "cxt.index = " << cxt.index << " is out of bounds"); - const transfer_details& td = m_transfers[cxt.index]; - - currency::COMMAND_RPC_GETBLOCKTEMPLATE::request tmpl_req = AUTO_VAL_INIT(tmpl_req); - currency::COMMAND_RPC_GETBLOCKTEMPLATE::response tmpl_rsp = AUTO_VAL_INIT(tmpl_rsp); - tmpl_req.wallet_address = get_account_address_as_str(miner_address); - tmpl_req.stakeholder_address = get_account_address_as_str(m_account.get_public_address()); - tmpl_req.pos_block = true; - tmpl_req.extra_text = get_extra_text_for_block(m_chain.get_top_block_height()); - - tmpl_req.pe = AUTO_VAL_INIT(tmpl_req.pe); - tmpl_req.pe.amount = td.amount(); - tmpl_req.pe.block_timestamp = td.m_ptx_wallet_info->m_block_timestamp; - tmpl_req.pe.g_index = td.m_global_output_index; - tmpl_req.pe.keyimage = td.m_key_image; - tmpl_req.pe.stake_unlock_time = cxt.stake_unlock_time; - tmpl_req.pe.tx_id = td.tx_hash(); - tmpl_req.pe.tx_out_index = td.m_internal_output_index; - tmpl_req.pe.wallet_index = cxt.index; - - // mark stake source as spent and make sure it will be restored in case of error - const std::vector stake_transfer_idx_vec{ cxt.index }; - mark_transfers_as_spent(stake_transfer_idx_vec, "stake source"); - bool gracefull_leaving = false; - auto stake_transfer_spent_flag_restorer = epee::misc_utils::create_scope_leave_handler([&](){ - if (!gracefull_leaving) - clear_transfers_from_flag(stake_transfer_idx_vec, WALLET_TRANSFER_DETAIL_FLAG_SPENT, "stake source"); - }); - - // generate UTXO Defragmentation Transaction - to reduce the UTXO set size - transaction udtx{}; - if (generate_utxo_defragmentation_transaction_if_needed(udtx)) - { - tx_to_blob(udtx, tmpl_req.explicit_transaction); - WLT_LOG_GREEN("Note: " << udtx.vin.size() << " inputs were aggregated into UTXO defragmentation tx " << get_transaction_hash(udtx), LOG_LEVEL_0); - } - m_core_proxy->call_COMMAND_RPC_GETBLOCKTEMPLATE(tmpl_req, tmpl_rsp); - WLT_CHECK_AND_ASSERT_MES(tmpl_rsp.status == API_RETURN_CODE_OK, false, "Failed to create block template after kernel hash found! Status: " << tmpl_rsp.status); - - currency::block b = AUTO_VAL_INIT(b); - currency::blobdata block_blob; - bool res = epee::string_tools::parse_hexstr_to_binbuff(tmpl_rsp.blocktemplate_blob, block_blob); - WLT_CHECK_AND_ASSERT_MES(res, false, "parse_hexstr_to_binbuff() failed after kernel hash found!"); - res = parse_and_validate_block_from_blob(block_blob, b); - WLT_CHECK_AND_ASSERT_MES(res, false, "parse_and_validate_block_from_blob() failed after kernel hash found!"); - - if (cxt.last_block_hash != b.prev_id) - { - WLT_LOG_YELLOW("Kernel was found but block is behindhand, b.prev_id=" << b.prev_id << ", last_block_hash=" << cxt.last_block_hash, LOG_LEVEL_0); - return false; - } - - // set the timestamp from stake kernel - b.timestamp = cxt.sk.block_timestamp; - uint64_t current_timestamp = m_core_runtime_config.get_core_time(); - set_block_datetime(current_timestamp, b); - WLT_LOG_MAGENTA("Applying actual timestamp: " << current_timestamp, LOG_LEVEL_2); - - res = prepare_and_sign_pos_block(cxt, tmpl_rsp.block_reward, tmpl_req.pe, tmpl_rsp.miner_tx_tgc, b); - WLT_CHECK_AND_ASSERT_MES(res, false, "Failed to prepare_and_sign_pos_block"); - - crypto::hash block_hash = get_block_hash(b); - WLT_LOG_GREEN("Block " << print16(block_hash) << " @ " << get_block_height(b) << " has been constructed, sending to core...", LOG_LEVEL_0); - - currency::COMMAND_RPC_SUBMITBLOCK2::request subm_req = AUTO_VAL_INIT(subm_req); - currency::COMMAND_RPC_SUBMITBLOCK2::response subm_rsp = AUTO_VAL_INIT(subm_rsp); - subm_req.b = t_serializable_object_to_blob(b); - if (tmpl_req.explicit_transaction.size()) - subm_req.explicit_txs.push_back(hexemizer{ tmpl_req.explicit_transaction }); - - m_core_proxy->call_COMMAND_RPC_SUBMITBLOCK2(subm_req, subm_rsp); - if (subm_rsp.status != API_RETURN_CODE_OK) - { - WLT_LOG_ERROR("Constructed block " << print16(block_hash) << " was rejected by the core, status: " << subm_rsp.status); - return false; - } - WLT_LOG_GREEN("PoS block " << print16(block_hash) << " generated and accepted, congrats!", LOG_LEVEL_0); - m_wcallback->on_pos_block_found(b); - - gracefull_leaving = true; // to prevent source transfer flags be cleared in scope leave handler - return true; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::get_unconfirmed_transfers(std::vector& trs, bool exclude_mining_txs) -{ - for (auto& u : m_unconfirmed_txs) - { - if (exclude_mining_txs && currency::is_coinbase(u.second.tx)) - { - continue; - } - trs.push_back(u.second); - load_wallet_transfer_info_flags(trs.back()); - } -} -//---------------------------------------------------------------------------------------------------- -void wallet2::set_core_runtime_config(const currency::core_runtime_config& pc) -{ - m_core_runtime_config = pc; -} -//---------------------------------------------------------------------------------------------------- -currency::core_runtime_config& wallet2::get_core_runtime_config() -{ - return m_core_runtime_config; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::is_transfer_unlocked(const transfer_details& td) const -{ - uint64_t stub = 0; - return is_transfer_unlocked(td, false, stub); -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::is_transfer_unlocked(const transfer_details& td, bool for_pos_mining, uint64_t& stake_lock_time) const -{ - if (td.m_flags&WALLET_TRANSFER_DETAIL_FLAG_BLOCKED) - return false; - - if (td.m_ptx_wallet_info->m_block_height + WALLET_DEFAULT_TX_SPENDABLE_AGE > get_blockchain_current_size()) - return false; - - - - uint64_t unlock_time = get_tx_unlock_time(td.m_ptx_wallet_info->m_tx, td.m_internal_output_index); - if (for_pos_mining && m_core_runtime_config.is_hardfork_active_for_height(1, get_blockchain_current_size())) - { - //allowed of staking locked coins with - stake_lock_time = unlock_time; - } - else - { - if (!currency::is_tx_spendtime_unlocked(unlock_time, get_blockchain_current_size(), m_core_runtime_config.get_core_time())) - return false; - } - return true; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::push_offer(const bc_services::offer_details_ex& od, currency::transaction& res_tx) -{ - currency::tx_destination_entry tx_dest; - tx_dest.addr.push_back(m_account.get_keys().account_address); - tx_dest.amount = m_core_runtime_config.tx_default_fee; - std::vector destinations; - std::vector extra; - std::vector attachments; - - bc_services::put_offer_into_attachment(static_cast(od), attachments); - - destinations.push_back(tx_dest); - transfer(destinations, 0, 0, od.fee, extra, attachments, get_current_split_strategy(), tx_dust_policy(DEFAULT_DUST_THRESHOLD), res_tx); -} -//---------------------------------------------------------------------------------------------------- -const transaction& wallet2::get_transaction_by_id(const crypto::hash& tx_hash) -{ - for (auto it = m_transfer_history.rbegin(); it != m_transfer_history.rend(); it++) - { - if (it->tx_hash == tx_hash) - return it->tx; - } - ASSERT_MES_AND_THROW("Tx " << tx_hash << " not found in wallet"); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::cancel_offer_by_id(const crypto::hash& tx_id, uint64_t of_ind, uint64_t fee, currency::transaction& res_tx) -{ - std::vector extra; - std::vector attachments; - bc_services::cancel_offer co = AUTO_VAL_INIT(co); - co.offer_index = of_ind; - co.tx_id = tx_id; - const transaction& related_tx = get_transaction_by_id(tx_id); - crypto::public_key related_tx_pub_key = get_tx_pub_key_from_extra(related_tx); - currency::keypair ephemeral = AUTO_VAL_INIT(ephemeral); - bool r = currency::derive_ephemeral_key_helper(m_account.get_keys(), related_tx_pub_key, of_ind, ephemeral); - CHECK_AND_ASSERT_THROW_MES(r, "derive_ephemeral_key_helper failed, tx_id: " << tx_id << ", of_ind:" << of_ind); - - blobdata sig_blob = bc_services::make_offer_sig_blob(co); - crypto::generate_signature(crypto::cn_fast_hash(sig_blob.data(), sig_blob.size()), ephemeral.pub, ephemeral.sec, co.sig); - bc_services::put_offer_into_attachment(co, attachments); - - transfer(std::vector(), 0, 0, fee, extra, attachments, get_current_split_strategy(), 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) -{ - currency::tx_destination_entry tx_dest; - tx_dest.addr.push_back(m_account.get_keys().account_address); - tx_dest.amount = m_core_runtime_config.tx_default_fee; - std::vector destinations; - std::vector extra; - std::vector attachments; - bc_services::update_offer uo = AUTO_VAL_INIT(uo); - uo.offer_index = of_ind; - uo.tx_id = tx_id; - uo.of = od; - const transaction& related_tx = get_transaction_by_id(tx_id); - crypto::public_key related_tx_pub_key = get_tx_pub_key_from_extra(related_tx); - currency::keypair ephemeral; - bool r = currency::derive_ephemeral_key_helper(m_account.get_keys(), related_tx_pub_key, of_ind, ephemeral); - CHECK_AND_ASSERT_THROW_MES(r, "Failed to derive_ephemeral_key_helper" << tx_id); - - blobdata sig_blob = bc_services::make_offer_sig_blob(uo); - crypto::generate_signature(crypto::cn_fast_hash(sig_blob.data(), sig_blob.size()), ephemeral.pub, ephemeral.sec, uo.sig); - bc_services::put_offer_into_attachment(uo, attachments); - - destinations.push_back(tx_dest); - transfer(destinations, 0, 0, od.fee, extra, attachments, get_current_split_strategy(), tx_dust_policy(DEFAULT_DUST_THRESHOLD), res_tx); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::push_alias_info_to_extra_according_to_hf_status(const currency::extra_alias_entry& ai, std::vector& extra) -{ - if (m_core_runtime_config.is_hardfork_active_for_height(2, get_top_block_height())) - { - // after HF2 - extra.push_back(ai); - } - else - { - // before HF2 - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(!ai.m_address.is_auditable(), "auditable addresses are not supported in aliases prior to HF2"); - extra.push_back(ai.to_old()); - } -} -//---------------------------------------------------------------------------------------------------- -uint64_t wallet2::get_alias_cost(const std::string& alias) -{ - currency::COMMAND_RPC_GET_ALIAS_REWARD::request req = AUTO_VAL_INIT(req); - currency::COMMAND_RPC_GET_ALIAS_REWARD::response rsp = AUTO_VAL_INIT(rsp); - req.alias = alias; - if (!m_core_proxy->call_COMMAND_RPC_GET_ALIAS_REWARD(req, rsp)) - { - throw std::runtime_error(std::string("Failed to get alias cost")); - } - - return rsp.reward; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::request_alias_registration(currency::extra_alias_entry& ai, currency::transaction& res_tx, uint64_t fee, uint64_t reward, const crypto::secret_key& authority_key) -{ - if (!validate_alias_name(ai.m_alias)) - { - throw std::runtime_error(std::string("wrong alias characters: ") + ai.m_alias); - } - - if (ai.m_alias.size() < ALIAS_MINIMUM_PUBLIC_SHORT_NAME_ALLOWED) - { - if (authority_key == currency::null_skey) - { - throw std::runtime_error(std::string("Short aliases is not allowed without authority key: ") + ALIAS_SHORT_NAMES_VALIDATION_PUB_KEY); - } - crypto::public_key authority_pub = AUTO_VAL_INIT(authority_pub); - bool r = crypto::secret_key_to_public_key(authority_key, authority_pub); - CHECK_AND_ASSERT_THROW_MES(r, "Failed to generate pub key from secrete authority key"); - - if (string_tools::pod_to_hex(authority_pub) != ALIAS_SHORT_NAMES_VALIDATION_PUB_KEY) - { - throw std::runtime_error(std::string("Short aliases is not allowed to register by this authority key")); - } - r = currency::sign_extra_alias_entry(ai, authority_pub, authority_key); + r = currency::sign_extra_alias_entry(ai, m_account.get_keys().account_address.spend_public_key, m_account.get_keys().spend_secret_key); CHECK_AND_ASSERT_THROW_MES(r, "Failed to sign alias update"); WLT_LOG_L2("Generated update alias info: " << ENDL << "alias: " << ai.m_alias << ENDL << "signature: " << currency::print_t_array(ai.m_sign) << ENDL << "signed(owner) pub key: " << m_account.get_keys().account_address.spend_public_key << ENDL - << "to address: " << get_account_address_as_str(ai.m_address) << ENDL + << "transfered to address: " << get_account_address_as_str(ai.m_address) << ENDL << "sign_buff_hash: " << currency::get_sign_buff_hash_for_alias_update(ai) ); + + std::vector destinations; + std::vector extra; + std::vector attachments; + + push_alias_info_to_extra_according_to_hf_status(ai, extra); + + transfer(destinations, 0, 0, fee, extra, attachments, get_current_split_strategy(), tx_dust_policy(DEFAULT_DUST_THRESHOLD), res_tx, CURRENCY_TO_KEY_OUT_RELAXED, false); } - - if (!reward) + //---------------------------------------------------------------------------------------------------- + bool wallet2::check_available_sources(std::list& amounts) { - reward = get_alias_cost(ai.m_alias); - } - - std::vector destinations; - std::vector extra; - std::vector attachments; - - push_alias_info_to_extra_according_to_hf_status(ai, extra); - - currency::tx_destination_entry tx_dest_alias_reward; - tx_dest_alias_reward.addr.resize(1); - get_aliases_reward_account(tx_dest_alias_reward.addr.back()); - tx_dest_alias_reward.amount = reward; - tx_dest_alias_reward.flags |= tx_destination_entry_flags::tdef_explicit_native_asset_id | tx_destination_entry_flags::tdef_zero_amount_blinding_mask; - destinations.push_back(tx_dest_alias_reward); - - transfer(destinations, 0, 0, fee, extra, attachments, get_current_split_strategy(), tx_dust_policy(DEFAULT_DUST_THRESHOLD), res_tx, CURRENCY_TO_KEY_OUT_RELAXED, false); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::deploy_new_asset(const currency::asset_descriptor_base& asset_info, const std::vector& destinations, currency::transaction& result_tx, crypto::public_key& new_asset_id) -{ - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(asset_info.decimal_point <= 18, "too big decimal point: " << asset_info.decimal_point); - - asset_descriptor_operation asset_reg_info = AUTO_VAL_INIT(asset_reg_info); - asset_reg_info.descriptor = asset_info; - asset_reg_info.operation_type = ASSET_DESCRIPTOR_OPERATION_REGISTER; - construct_tx_param ctp = get_default_construct_tx_param(); - ctp.dsts = destinations; - ctp.extra.push_back(asset_reg_info); - ctp.need_at_least_1_zc = true; - - finalized_tx ft = AUTO_VAL_INIT(ft); - this->transfer(ctp, ft, true, nullptr); - result_tx = ft.tx; - //get generated asset id - currency::asset_descriptor_operation ado = AUTO_VAL_INIT(ado); - bool r = get_type_in_variant_container(result_tx.extra, ado); - CHECK_AND_ASSERT_THROW_MES(r, "Failed find asset info in tx"); - CHECK_AND_ASSERT_THROW_MES(get_or_calculate_asset_id(ado, nullptr, &new_asset_id), "get_or_calculate_asset_id failed"); - - m_custom_assets[new_asset_id] = ado.descriptor; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::emit_asset(const crypto::public_key asset_id, std::vector& destinations, currency::transaction& result_tx) -{ - - auto own_asset_entry_it = m_own_asset_descriptors.find(asset_id); - CHECK_AND_ASSERT_THROW_MES(own_asset_entry_it != m_own_asset_descriptors.end(), "Failed find asset_id " << asset_id << " in own assets list"); - COMMAND_RPC_GET_ASSET_INFO::request req; - req.asset_id = asset_id; - COMMAND_RPC_GET_ASSET_INFO::response rsp; - bool r = m_core_proxy->call_COMMAND_RPC_GET_ASSET_INFO(req, rsp); - CHECK_AND_ASSERT_THROW_MES(r, "Failed to call_COMMAND_RPC_GET_ASSET_INFO"); - - asset_descriptor_operation asset_emmit_info = AUTO_VAL_INIT(asset_emmit_info); - asset_emmit_info.descriptor = rsp.asset_descriptor; - asset_emmit_info.operation_type = ASSET_DESCRIPTOR_OPERATION_EMIT; - asset_emmit_info.opt_asset_id = asset_id; - construct_tx_param ctp = get_default_construct_tx_param(); - ctp.dsts = destinations; - ctp.extra.push_back(asset_emmit_info); - ctp.need_at_least_1_zc = true; - ctp.ado_current_asset_owner = rsp.asset_descriptor.owner; - //ctp.asset_deploy_control_key = own_asset_entry_it->second.control_key; - - for(auto& dst : ctp.dsts) - { - dst.asset_id = null_pkey; // emit operation requires null_pkey for emitting asset outputs, fix it ad-hoc here - } - - finalized_tx ft = AUTO_VAL_INIT(ft); - this->transfer(ctp, ft, true, nullptr); - result_tx = ft.tx; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::update_asset(const crypto::public_key asset_id, const currency::asset_descriptor_base new_descriptor, currency::transaction& result_tx) -{ - auto own_asset_entry_it = m_own_asset_descriptors.find(asset_id); - CHECK_AND_ASSERT_THROW_MES(own_asset_entry_it != m_own_asset_descriptors.end(), "Failed find asset_id " << asset_id << " in own assets list"); - - asset_descriptor_operation asset_update_info = AUTO_VAL_INIT(asset_update_info); - asset_update_info.descriptor = new_descriptor; - asset_update_info.operation_type = ASSET_DESCRIPTOR_OPERATION_UPDATE; - asset_update_info.opt_asset_id = asset_id; - construct_tx_param ctp = get_default_construct_tx_param(); - ctp.extra.push_back(asset_update_info); - ctp.need_at_least_1_zc = true; - currency::asset_descriptor_base adb = AUTO_VAL_INIT(adb); - bool r = this->daemon_get_asset_info(asset_id, adb); - CHECK_AND_ASSERT_THROW_MES(r, "Failed to get asset info from daemon"); - ctp.ado_current_asset_owner = adb.owner; - - finalized_tx ft = AUTO_VAL_INIT(ft); - this->transfer(ctp, ft, true, nullptr); - result_tx = ft.tx; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::transfer_asset_ownership(const crypto::public_key asset_id, const crypto::public_key& new_owner, currency::transaction& result_tx) -{ - auto own_asset_entry_it = m_own_asset_descriptors.find(asset_id); - CHECK_AND_ASSERT_THROW_MES(own_asset_entry_it != m_own_asset_descriptors.end(), "Failed find asset_id " << asset_id << " in own assets list"); - - currency::asset_descriptor_base adb = AUTO_VAL_INIT(adb); - bool r = this->daemon_get_asset_info(asset_id, adb); - CHECK_AND_ASSERT_THROW_MES(r, "Failed to get asset info from daemon"); - - asset_descriptor_operation asset_update_info = AUTO_VAL_INIT(asset_update_info); - asset_update_info.descriptor = adb; - asset_update_info.operation_type = ASSET_DESCRIPTOR_OPERATION_UPDATE; - asset_update_info.opt_asset_id = asset_id; - asset_update_info.descriptor.owner = new_owner; - construct_tx_param ctp = get_default_construct_tx_param(); - ctp.ado_current_asset_owner = adb.owner; - ctp.extra.push_back(asset_update_info); - - finalized_tx ft = AUTO_VAL_INIT(ft); - this->transfer(ctp, ft, true, nullptr); - result_tx = ft.tx; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::burn_asset(const crypto::public_key asset_id, uint64_t amount_to_burn, currency::transaction& result_tx) -{ - //auto own_asset_entry_it = m_own_asset_descriptors.find(asset_id); - //CHECK_AND_ASSERT_THROW_MES(own_asset_entry_it != m_own_asset_descriptors.end(), "Failed find asset_id " << asset_id << " in own assets list"); - COMMAND_RPC_GET_ASSET_INFO::request req; - req.asset_id = asset_id; - COMMAND_RPC_GET_ASSET_INFO::response rsp; - bool r = m_core_proxy->call_COMMAND_RPC_GET_ASSET_INFO(req, rsp); - CHECK_AND_ASSERT_THROW_MES(r, "Failed to call_COMMAND_RPC_GET_ASSET_INFO"); - - - - asset_descriptor_operation asset_burn_info = AUTO_VAL_INIT(asset_burn_info); - asset_burn_info.descriptor = rsp.asset_descriptor; - - CHECK_AND_ASSERT_THROW_MES(asset_burn_info.descriptor.current_supply >= amount_to_burn, "Wrong amount to burn (current_supply" << asset_burn_info.descriptor.current_supply << " is less then " << amount_to_burn << ")"); - - currency::tx_destination_entry dst_to_burn = AUTO_VAL_INIT(dst_to_burn); - dst_to_burn.amount = amount_to_burn; - dst_to_burn.asset_id = asset_id; - - asset_burn_info.operation_type = ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN; - asset_burn_info.opt_asset_id = asset_id; - construct_tx_param ctp = get_default_construct_tx_param(); - ctp.extra.push_back(asset_burn_info); - ctp.need_at_least_1_zc = true; - ctp.ado_current_asset_owner = rsp.asset_descriptor.owner; - ctp.dsts.push_back(dst_to_burn); - - finalized_tx ft = AUTO_VAL_INIT(ft); - this->transfer(ctp, ft, true, nullptr); - result_tx = ft.tx; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::daemon_get_asset_info(const crypto::public_key& asset_id, currency::asset_descriptor_base& adb) -{ - COMMAND_RPC_GET_ASSET_INFO::request req; - req.asset_id = asset_id; - COMMAND_RPC_GET_ASSET_INFO::response rsp; - bool r = m_core_proxy->call_COMMAND_RPC_GET_ASSET_INFO(req, rsp); - CHECK_AND_ASSERT_MES(r, false, "Failed to call_COMMAND_RPC_GET_ASSET_INFO"); - adb = rsp.asset_descriptor; - return true; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::request_alias_update(currency::extra_alias_entry& ai, currency::transaction& res_tx, uint64_t fee) -{ - COMMAND_RPC_GET_ALIAS_DETAILS::request req; - req.alias = ai.m_alias; - COMMAND_RPC_GET_ALIAS_DETAILS::response rsp = AUTO_VAL_INIT(rsp); - bool r = m_core_proxy->call_COMMAND_RPC_GET_ALIAS_DETAILS(req, rsp); - CHECK_AND_ASSERT_THROW_MES(r, "Failed to call_COMMAND_RPC_GET_ALIAS_DETAILS"); - - CHECK_AND_ASSERT_THROW_MES(rsp.status == API_RETURN_CODE_OK, "call_COMMAND_RPC_GET_ALIAS_DETAILS response: " << rsp.status); - - - currency::account_public_address addr = AUTO_VAL_INIT(addr); - currency::get_account_address_from_str(addr, rsp.alias_details.address); - - CHECK_AND_ASSERT_THROW_MES(m_account.get_public_address().spend_public_key == addr.spend_public_key && - m_account.get_public_address().view_public_key == addr.view_public_key, "call_COMMAND_RPC_GET_ALIAS_DETAILS: ownership is not confirmed"); - - if (!validate_alias_name(ai.m_alias)) - { - throw std::runtime_error(std::string("wrong alias characters: ") + ai.m_alias); - } - r = currency::sign_extra_alias_entry(ai, m_account.get_keys().account_address.spend_public_key, m_account.get_keys().spend_secret_key); - CHECK_AND_ASSERT_THROW_MES(r, "Failed to sign alias update"); - WLT_LOG_L2("Generated update alias info: " << ENDL - << "alias: " << ai.m_alias << ENDL - << "signature: " << currency::print_t_array(ai.m_sign) << ENDL - << "signed(owner) pub key: " << m_account.get_keys().account_address.spend_public_key << ENDL - << "transfered to address: " << get_account_address_as_str(ai.m_address) << ENDL - << "sign_buff_hash: " << currency::get_sign_buff_hash_for_alias_update(ai) - ); - - std::vector destinations; - std::vector extra; - std::vector attachments; - - push_alias_info_to_extra_according_to_hf_status(ai, extra); - - transfer(destinations, 0, 0, fee, extra, attachments, get_current_split_strategy(), tx_dust_policy(DEFAULT_DUST_THRESHOLD), res_tx, CURRENCY_TO_KEY_OUT_RELAXED, false); -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::check_available_sources(std::list& amounts) -{ - /* - std::list > holds; - amounts.sort(); - bool res = true; - for (uint64_t am : amounts) - { - holds.push_back(std::vector()); - std::vector& current_holds = holds.back(); - uint64_t found = select_transfers(am, 0, DEFAULT_DUST_THRESHOLD, current_holds); - if (found < am) + /* + std::list > holds; + amounts.sort(); + bool res = true; + for (uint64_t am : amounts) { - res = false; - break; - } - mark_transfers_with_flag(current_holds, WALLET_TRANSFER_DETAIL_FLAG_BLOCKED, "check_available_sources"); - } - - for (auto& h: holds) - { - clear_transfers_from_flag(h, WALLET_TRANSFER_DETAIL_FLAG_BLOCKED, "check_available_sources"); - add_transfers_to_transfers_cache(h); - } - - - WLT_LOG_MAGENTA("[CHECK_AVAILABLE_SOURCES]: " << amounts << " res: " << res << ENDL <<" holds: " << holds, LOG_LEVEL_0); - return res; - */ - return false; -} -//---------------------------------------------------------------------------------------------------- -std::string get_random_rext(size_t len) -{ - std::string buff(len / 2, 0); - crypto::generate_random_bytes(len / 2, (void*)buff.data()); - return string_tools::buff_to_hex_nodelimer(buff); -} -//---------------------------------------------------------------------------------------------------- - -// local_transfers_struct - structure to avoid copying the whole m_transfers -struct local_transfers_struct -{ - local_transfers_struct(transfer_container& tf) :l_transfers_ref(tf) - {} - - transfer_container& l_transfers_ref; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(l_transfers_ref) - END_KV_SERIALIZE_MAP() -}; - -void wallet2::dump_trunsfers(std::stringstream& ss, bool verbose) const -{ - if (verbose) - { - std::string res_buff; - local_transfers_struct lt(const_cast(m_transfers)); - epee::serialization::store_t_to_json(lt, res_buff); - ss << res_buff; - } - else - { - boost::io::ios_flags_saver ifs(ss); - ss << "index amount spent_h g_index block block_ts flg tx out# key image" << ENDL; - for (size_t i = 0; i != m_transfers.size(); i++) - { - const transfer_details& td = m_transfers[i]; - ss << std::right << - std::setw(5) << i << " " << - std::setw(21) << print_money(td.amount()) << " " << - std::setw(7) << td.m_spent_height << " " << - std::setw(7) << td.m_global_output_index << " " << - std::setw(6) << td.m_ptx_wallet_info->m_block_height << " " << - std::setw(10) << td.m_ptx_wallet_info->m_block_timestamp << " " << - std::setw(4) << td.m_flags << " " << - get_transaction_hash(td.m_ptx_wallet_info->m_tx) << " " << - std::setw(4) << td.m_internal_output_index << " " << - td.m_key_image << ENDL; - } - } -} -//---------------------------------------------------------------------------------------------------- -std::string wallet2::dump_trunsfers(bool verbose) const -{ - std::stringstream ss; - dump_trunsfers(ss, verbose); - return ss.str(); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::dump_key_images(std::stringstream& ss) -{ - for (auto& ki: m_key_images) - { - ss << "[" << ki.first << "]: " << ki.second << ENDL; - } -} -void wallet2::get_multisig_transfers(multisig_transfer_container& ms_transfers) -{ - ms_transfers = m_multisig_transfers; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::get_contracts(escrow_contracts_container& contracts) -{ - contracts = m_contracts; - return true; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::build_escrow_release_templates(crypto::hash multisig_id, - uint64_t fee, - currency::transaction& tx_release_template, - currency::transaction& tx_burn_template, - const bc_services::contract_private_details& ecrow_details) -{ - construct_tx_param construct_params = AUTO_VAL_INIT(construct_params); - construct_params.fee = fee; - construct_params.multisig_id = multisig_id; - construct_params.split_strategy_id = get_current_split_strategy(); - construct_params.dsts.resize(2); - //0 - addr_a - //1 - addr_b - construct_params.dsts[0].addr.push_back(ecrow_details.a_addr); - construct_params.dsts[1].addr.push_back(ecrow_details.b_addr); - - - //generate normal escrow release - construct_params.dsts[0].amount = ecrow_details.amount_a_pledge; - construct_params.dsts[1].amount = ecrow_details.amount_b_pledge + ecrow_details.amount_to_pay; - - //in case of ecrow_details.amount_a_pledge == 0 then exclude a - if (construct_params.dsts[0].amount == 0) - construct_params.dsts.erase(construct_params.dsts.begin()); - - - tx_service_attachment tsa = AUTO_VAL_INIT(tsa); - tsa.service_id = BC_ESCROW_SERVICE_ID; - tsa.instruction = BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_NORMAL; - construct_params.extra.push_back(tsa); - { - currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); - ftp.tx_version = this->get_current_tx_version(); - prepare_transaction(construct_params, ftp); - crypto::secret_key sk = AUTO_VAL_INIT(sk); - finalize_transaction(ftp, tx_release_template, sk, false); - } - - //generate burn escrow - construct_params.dsts.resize(1); - construct_params.dsts[0].addr.clear(); - construct_params.dsts[0].addr.push_back(null_pub_addr); - construct_params.dsts[0].amount = ecrow_details.amount_a_pledge + ecrow_details.amount_b_pledge + ecrow_details.amount_to_pay; - - construct_params.extra.clear(); - tsa.instruction = BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_BURN; - construct_params.extra.push_back(tsa); - { - currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); - ftp.tx_version = this->get_current_tx_version(); - prepare_transaction(construct_params, ftp); - crypto::secret_key sk = AUTO_VAL_INIT(sk); - finalize_transaction(ftp, tx_burn_template, sk, false); - } -} -//---------------------------------------------------------------------------------------------------- -void wallet2::build_escrow_cancel_template(crypto::hash multisig_id, - uint64_t expiration_period, - currency::transaction& tx_cancel_template, - const bc_services::contract_private_details& ecrow_details) -{ - auto it = m_multisig_transfers.find(multisig_id); - THROW_IF_FALSE_WALLET_EX(it != m_multisig_transfers.end(), error::wallet_internal_error, - "unable to find multisig id"); - - THROW_IF_FALSE_WALLET_EX(it->second.amount() > (ecrow_details.amount_a_pledge + ecrow_details.amount_to_pay + ecrow_details.amount_b_pledge), error::wallet_internal_error, - "multisig id out amount no more than escrow total amount"); - - construct_tx_param construct_params = AUTO_VAL_INIT(construct_params); - currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); - ftp.tx_version = this->get_current_tx_version(); - 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 = get_current_split_strategy(); - construct_params.dsts.resize(2); - //0 - addr_a - //1 - addr_b - construct_params.dsts[0].addr.push_back(ecrow_details.a_addr); - construct_params.dsts[1].addr.push_back(ecrow_details.b_addr); - - - //generate cancel escrow proposal - construct_params.dsts[0].amount = ecrow_details.amount_a_pledge + ecrow_details.amount_to_pay; - construct_params.dsts[1].amount = ecrow_details.amount_b_pledge; - - if (construct_params.dsts[0].amount == 0) - construct_params.dsts.erase(construct_params.dsts.begin()); - else if (construct_params.dsts[1].amount == 0) - construct_params.dsts.erase(construct_params.dsts.begin() + 1); - - tx_service_attachment tsa = AUTO_VAL_INIT(tsa); - tsa.service_id = BC_ESCROW_SERVICE_ID; - tsa.instruction = BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_CANCEL; - construct_params.extra.push_back(tsa); - currency::etc_tx_details_expiration_time expir = AUTO_VAL_INIT(expir); - expir.v = m_core_runtime_config.get_core_time() + expiration_period; - construct_params.extra.push_back(expir); - - prepare_transaction(construct_params, ftp); - crypto::secret_key sk = AUTO_VAL_INIT(sk); - finalize_transaction(ftp, tx_cancel_template, sk, false); -} - -//---------------------------------------------------------------------------------------------------- -void wallet2::build_escrow_template(const bc_services::contract_private_details& ecrow_details, - size_t fake_outputs_count, - uint64_t unlock_time, - uint64_t expiration_time, - uint64_t b_release_fee, - const std::string& payment_id, - currency::transaction& tx, - std::vector& selected_transfers, - 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 = get_current_split_strategy(); - ctp.tx_outs_attr = CURRENCY_TO_KEY_OUT_RELAXED; - ctp.unlock_time = unlock_time; - - etc_tx_details_expiration_time t = AUTO_VAL_INIT(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; - ctp.extra.push_back(att); - - 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; - ctp.attachments.push_back(att); - } - - currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); - ftp.tx_version = this->get_current_tx_version(); - prepare_transaction(ctp, ftp); - - 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, const std::vector& received, uint64_t expiration, const crypto::hash& related_tx_id) -{ - // check all elements in selected_transfers for being already mentioned in m_money_expirations - std::vector selected_transfers_local; - for(auto transfer_index : selected_transfers) - { - bool found = false; - for(auto it = m_money_expirations.begin(); !found && it != m_money_expirations.end(); ++it) - { - auto& st = it->selected_transfers; - found = std::find(st.begin(), st.end(), transfer_index) != st.end(); - } - if (!found) - selected_transfers_local.push_back(transfer_index); // populate selected_transfers_local only with indices, not containing in m_money_expirations - } - - if (selected_transfers_local.empty()) - return; - - m_money_expirations.push_back(AUTO_VAL_INIT(expiration_entry_info())); - m_money_expirations.back().expiration_time = expiration; - m_money_expirations.back().selected_transfers = selected_transfers_local; - m_money_expirations.back().related_tx_id = related_tx_id; - m_money_expirations.back().receved = received; - - std::stringstream ss; - for (auto tr_ind : m_money_expirations.back().selected_transfers) - { - THROW_IF_FALSE_WALLET_INT_ERR_EX(tr_ind < m_transfers.size(), "invalid transfer index"); - uint32_t flags_before = m_transfers[tr_ind].m_flags; - m_transfers[tr_ind].m_flags |= WALLET_TRANSFER_DETAIL_FLAG_BLOCKED; - m_transfers[tr_ind].m_flags |= WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION; - ss << " " << std::right << std::setw(4) << tr_ind << " " << std::setw(21) << print_money(m_transfers[tr_ind].amount()) << " " - << std::setw(2) << std::left << flags_before << " -> " << std::setw(2) << std::left << m_transfers[tr_ind].m_flags << " " - << get_transaction_hash(m_transfers[tr_ind].m_ptx_wallet_info->m_tx) << std::endl; - } - WLT_LOG_GREEN(m_money_expirations.back().selected_transfers.size() << " transfer(s) added to expiration list:" << ENDL << - "index amount flags tx hash" << ENDL << - ss.str() << ", expire(s) at: " << expiration, LOG_LEVEL_0); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::remove_transfer_from_expiration_list(uint64_t transfer_index) -{ - THROW_IF_FALSE_WALLET_INT_ERR_EX(transfer_index < m_transfers.size(), "invalid transfer index"); - for(auto it = m_money_expirations.begin(); it != m_money_expirations.end(); /* nothing */) - { - auto& st = it->selected_transfers; - auto jt = std::find(st.begin(), st.end(), transfer_index); - if (jt != st.end()) - { - WLT_LOG_GREEN("Transfer [" << transfer_index << "], amount: " << print_money(m_transfers[transfer_index].amount()) << ", tx: " << get_transaction_hash(m_transfers[transfer_index].m_ptx_wallet_info->m_tx) << - " was removed from the expiration list", LOG_LEVEL_0); - st.erase(jt); - if (st.empty()) + holds.push_back(std::vector()); + std::vector& current_holds = holds.back(); + uint64_t found = select_transfers(am, 0, DEFAULT_DUST_THRESHOLD, current_holds); + if (found < am) { - it = m_money_expirations.erase(it); - continue; + res = false; + break; } + mark_transfers_with_flag(current_holds, WALLET_TRANSFER_DETAIL_FLAG_BLOCKED, "check_available_sources"); } - ++it; - } - // clear proposal reservation flag and blocked flag - uint32_t flags_before = m_transfers[transfer_index].m_flags; - m_transfers[transfer_index].m_flags &= ~WALLET_TRANSFER_DETAIL_FLAG_BLOCKED; - m_transfers[transfer_index].m_flags &= ~WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION; - if (flags_before != m_transfers[transfer_index].m_flags) - { - WLT_LOG_BLUE("Transfer [" << transfer_index << "] was cleared from escrow proposal reservation, flags: " << flags_before << " -> " << m_transfers[transfer_index].m_flags << ", reason: intentional removing from expiration list", LOG_LEVEL_0); - } - - // (don't change m_spent flag, because transfer status is unclear - the caller should take care of it) -} -//---------------------------------------------------------------------------------------------------- -void wallet2::send_escrow_proposal(const wallet_public::create_proposal_param& wp, - currency::transaction &proposal_tx, - currency::transaction &escrow_template_tx) -{ - return send_escrow_proposal(wp.details, wp.fake_outputs_count, wp.unlock_time, wp.expiration_period, wp.fee, wp.b_fee, wp.payment_id, proposal_tx, escrow_template_tx); -} -//---------------------------------------------------------------------------------------------------- -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, - uint64_t fee, - uint64_t b_release_fee, - const std::string& payment_id, - currency::transaction &tx, - currency::transaction &template_tx) -{ - if (!is_connected_to_net()) - { - THROW_IF_TRUE_WALLET_EX(true, error::wallet_internal_error, - "Transfer attempt while daemon offline"); - } - 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; - 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, get_multisig_out_index(template_tx.vout)); - - 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, contract: " + epee::string_tools::pod_to_hex(ms_id)); - - 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; - 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); - - 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 = get_current_split_strategy(); - ctp.tx_outs_attr = CURRENCY_TO_KEY_OUT_RELAXED; - ctp.unlock_time = unlock_time; - - currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); - ftp.tx_version = this->get_current_tx_version(); - try - { - prepare_transaction(ctp, ftp); - crypto::secret_key sk = AUTO_VAL_INIT(sk); - finalize_transaction(ftp, tx, sk, false); - } - catch (...) - { - clear_transfers_from_flag(selected_transfers_for_template, mask_to_mark_escrow_template_locked_transfers, "undo prepared escrow template tx"); // don't forget to unlock template transfers if smth went wrong - add_transfers_to_transfers_cache(selected_transfers_for_template); - throw; - } - - send_transaction_to_network(tx); - - 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.attachments, ftp.prepared_destinations, ftp.selected_transfers); - - print_tx_sent_message(tx, "(from multisig)", fee); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::create_htlc_proposal(uint64_t amount, const currency::account_public_address& addr, uint64_t lock_blocks_count, currency::transaction &tx, const crypto::hash& htlc_hash, std::string &origin) -{ - construct_tx_param ctp = get_default_construct_tx_param(); - ctp.fee = TX_DEFAULT_FEE; - ctp.dsts.resize(1); - ctp.dsts.back().addr.push_back(addr); - ctp.dsts.back().amount = amount; - destination_option_htlc_out& htlc_option = ctp.dsts.back().htlc_options; - htlc_option.expiration = lock_blocks_count; //about 12 hours - htlc_option.htlc_hash = htlc_hash; - - currency::create_and_add_tx_payer_to_container_from_address(ctp.extra, - get_account().get_keys().account_address, get_top_block_height(), get_core_runtime_config()); - - finalized_tx ft = AUTO_VAL_INIT(ft); - this->transfer(ctp, ft, true, nullptr); - origin = ft.htlc_origin; - tx = ft.tx; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::get_list_of_active_htlc(std::list& htlcs, bool only_redeem_txs) -{ - for (auto htlc_entry : m_active_htlcs_txid) - { - const transfer_details& td = m_transfers[htlc_entry.second]; - if (only_redeem_txs && !(td.m_flags&WALLET_TRANSFER_DETAIL_FLAG_HTLC_REDEEM)) + for (auto& h: holds) { - continue; + clear_transfers_from_flag(h, WALLET_TRANSFER_DETAIL_FLAG_BLOCKED, "check_available_sources"); + add_transfers_to_transfers_cache(h); } - wallet_public::htlc_entry_info entry = AUTO_VAL_INIT(entry); - entry.tx_id = htlc_entry.first; - if (td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].type() != typeid(tx_out_bare)) - { - //@#@ - LOG_ERROR("Unexpected output type in get_list_of_active_htlc:" << td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].type().name()); - continue; - } - const tx_out_bare out_b = boost::get(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]); - entry.amount = out_b.amount; - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(out_b.target.type() == typeid(txout_htlc), - "[get_list_of_active_htlc]Internal error: unexpected type of out"); - const txout_htlc& htlc = boost::get(out_b.target); - entry.sha256_hash = htlc.htlc_hash; - - currency::tx_payer payer = AUTO_VAL_INIT(payer); - if (currency::get_type_in_variant_container(td.varian_options, payer)) - entry.counterparty_address = payer.acc_addr; - entry.is_redeem = td.m_flags&WALLET_TRANSFER_DETAIL_FLAG_HTLC_REDEEM ? true : false; - htlcs.push_back(entry); - } -} -//---------------------------------------------------------------------------------------------------- -void wallet2::redeem_htlc(const crypto::hash& htlc_tx_id, const std::string& origin) -{ - currency::transaction result_tx = AUTO_VAL_INIT(result_tx); - return redeem_htlc(htlc_tx_id, origin, result_tx); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::redeem_htlc(const crypto::hash& htlc_tx_id, const std::string& origin, currency::transaction& result_tx) -{ - construct_tx_param ctp = get_default_construct_tx_param(); - ctp.fee = TX_DEFAULT_FEE; - ctp.htlc_tx_id = htlc_tx_id; - ctp.htlc_origin = origin; - ctp.dsts.resize(1); - ctp.dsts.back().addr.push_back(m_account.get_keys().account_address); - - auto it = m_active_htlcs_txid.find(htlc_tx_id); - WLT_THROW_IF_FALSE_WITH_CODE(it != m_active_htlcs_txid.end(), - "htlc not found with tx_id = " << htlc_tx_id, API_RETURN_CODE_NOT_FOUND); - - ctp.dsts.back().amount = m_transfers[it->second].amount() - ctp.fee; - this->transfer(ctp, result_tx, true, nullptr); -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::check_htlc_redeemed(const crypto::hash& htlc_tx_id, std::string& origin, crypto::hash& redeem_tx_id) -{ - auto it = m_active_htlcs_txid.find(htlc_tx_id); - - WLT_THROW_IF_FALSE_WITH_CODE(it != m_active_htlcs_txid.end(), - "htlc not found with tx_id = " << htlc_tx_id, API_RETURN_CODE_NOT_FOUND); - - transfer_details_extra_option_htlc_info htlc_options = AUTO_VAL_INIT(htlc_options); - if (!currency::get_type_in_variant_container(m_transfers[it->second].varian_options, htlc_options)) - { + WLT_LOG_MAGENTA("[CHECK_AVAILABLE_SOURCES]: " << amounts << " res: " << res << ENDL <<" holds: " << holds, LOG_LEVEL_0); + return res; + */ return false; } - if (htlc_options.origin.size()) + //---------------------------------------------------------------------------------------------------- + std::string get_random_rext(size_t len) { - origin = htlc_options.origin; - redeem_tx_id = htlc_options.redeem_tx_id; - return true; + std::string buff(len / 2, 0); + crypto::generate_random_bytes(len / 2, (void*)buff.data()); + return string_tools::buff_to_hex_nodelimer(buff); } - return false; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::create_ionic_swap_proposal(const wallet_public::ionic_swap_proposal_info& proposal_details, const currency::account_public_address& destination_addr, wallet_public::ionic_swap_proposal& proposal) -{ - std::vector selected_transfers_for_template; - - return build_ionic_swap_template(proposal_details, destination_addr, proposal, selected_transfers_for_template); + //---------------------------------------------------------------------------------------------------- - //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 ionic_swap"); - //return true; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::build_ionic_swap_template(const wallet_public::ionic_swap_proposal_info& proposal_detais, const currency::account_public_address& destination_addr, - wallet_public::ionic_swap_proposal& proposal, - std::vector& selected_transfers) -{ - WLT_THROW_IF_FALSE_WITH_CODE(proposal_detais.fee_paid_by_a >= get_current_minimum_network_fee(), "Error at build_ionic_swap_template, ", API_RETURN_CODE_WALLET_FEE_TOO_LOW); - - construct_tx_param ctp = get_default_construct_tx_param(); - - //ctp.fake_outputs_count = proposal_detais.mixins; - ctp.fee = proposal_detais.fee_paid_by_a; - ctp.flags = TX_FLAG_SIGNATURE_MODE_SEPARATE; - ctp.mark_tx_as_complete = false; - ctp.crypt_address = destination_addr; - - ctp.dsts.resize(proposal_detais.to_finalizer.size() + proposal_detais.to_initiator.size()); - size_t i = 0; - // Here is an proposed for exchange funds - for (; i != proposal_detais.to_finalizer.size(); i++) + // local_transfers_struct - structure to avoid copying the whole m_transfers + struct local_transfers_struct { - ctp.dsts[i].amount = proposal_detais.to_finalizer[i].amount; - ctp.dsts[i].amount_to_provide = proposal_detais.to_finalizer[i].amount; - ctp.dsts[i].flags |= tx_destination_entry_flags::tdef_explicit_amount_to_provide; - ctp.dsts[i].addr.push_back(destination_addr); - ctp.dsts[i].asset_id = proposal_detais.to_finalizer[i].asset_id; - } - // Here is an expected in return funds - std::vector for_expiration_list; - for (size_t j = 0; j != proposal_detais.to_initiator.size(); j++, i++) + local_transfers_struct(transfer_container& tf) :l_transfers_ref(tf) + {} + + transfer_container& l_transfers_ref; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(l_transfers_ref) + END_KV_SERIALIZE_MAP() + }; + + void wallet2::dump_trunsfers(std::stringstream& ss, bool verbose) const { - ctp.dsts[i].amount = proposal_detais.to_initiator[j].amount; - ctp.dsts[i].amount_to_provide = 0; - ctp.dsts[i].flags |= tx_destination_entry_flags::tdef_explicit_amount_to_provide; - ctp.dsts[i].addr.push_back(m_account.get_public_address()); - ctp.dsts[i].asset_id = proposal_detais.to_initiator[j].asset_id; - for_expiration_list.push_back(payment_details_subtransfer{ ctp.dsts[i].asset_id, ctp.dsts[i].amount}); - } - - currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); - ftp.mode_separate_fee = ctp.fee; - ftp.tx_version = this->get_current_tx_version(); - prepare_transaction(ctp, ftp); - - selected_transfers = ftp.selected_transfers; - currency::finalized_tx finalize_result = AUTO_VAL_INIT(finalize_result); - finalize_transaction(ftp, finalize_result, false); - for(uint64_t i: selected_transfers) - m_transfers[i].m_flags &= ~WALLET_TRANSFER_DETAIL_FLAG_BLOCKED; - - //add_transfers_to_expiration_list(selected_transfers, for_expiration_list, this->get_core_runtime_config().get_core_time() + proposal_detais.expiration_time, currency::null_hash); - - //wrap it all - proposal.tx_template = finalize_result.tx; - wallet_public::ionic_swap_proposal_context ispc = AUTO_VAL_INIT(ispc); - ispc.gen_context = finalize_result.ftp.gen_context; - //ispc.one_time_skey = finalize_result.one_time_key; - std::string proposal_context_blob = t_serializable_object_to_blob(ispc); - proposal.encrypted_context = crypto::chacha_crypt(static_cast(proposal_context_blob), finalize_result.derivation); - return true; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::get_ionic_swap_proposal_info(const std::string&raw_proposal, wallet_public::ionic_swap_proposal_info& proposal_info) const -{ - wallet_public::ionic_swap_proposal proposal = AUTO_VAL_INIT(proposal); - bool r = t_unserializable_object_from_blob(proposal, raw_proposal); - THROW_IF_TRUE_WALLET_EX(!r, error::wallet_internal_error, "Failed to parse proposal"); - return get_ionic_swap_proposal_info(proposal, proposal_info); -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::get_ionic_swap_proposal_info(const wallet_public::ionic_swap_proposal& proposal, wallet_public::ionic_swap_proposal_info& proposal_info) const -{ - wallet_public::ionic_swap_proposal_context ionic_context = AUTO_VAL_INIT(ionic_context); - return get_ionic_swap_proposal_info(proposal, proposal_info, ionic_context); -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::get_ionic_swap_proposal_info(const wallet_public::ionic_swap_proposal& proposal, wallet_public::ionic_swap_proposal_info& proposal_info, wallet_public::ionic_swap_proposal_context& ionic_context) const -{ - const transaction& tx = proposal.tx_template; - crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); - std::vector outs; - bool r = lookup_acc_outs(m_account.get_keys(), tx, outs, derivation); - THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "Failed to lookup_acc_outs for tx: " << get_transaction_hash(tx)); - - if (!outs.size()) - { - return false; - } - - //decrypt context - std::string decrypted_raw_context = crypto::chacha_crypt(proposal.encrypted_context, derivation); - r = t_unserializable_object_from_blob(ionic_context, decrypted_raw_context); - THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "Failed to unserialize decrypted ionic_context"); - - r = validate_tx_details_against_tx_generation_context(tx, ionic_context.gen_context); - THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "validate_tx_details_against_tx_generation_context failed"); - - std::unordered_map amounts_provided_by_a; - - std::unordered_map ammounts_to_a; //amounts to Alice (the one who created proposal), should be NOT funded - std::unordered_map ammounts_to_b; //amounts to Bob (the one who received proposal), should BE funded - std::vector bob_outs; - bob_outs.resize(proposal.tx_template.vout.size()); - - for (const auto& o : outs) - { - THROW_IF_FALSE_WALLET_INT_ERR_EX(ionic_context.gen_context.asset_ids.size() > o.index, "Tx gen context has mismatch with tx(asset_ids) "); - THROW_IF_FALSE_WALLET_INT_ERR_EX(ionic_context.gen_context.asset_ids[o.index].to_public_key() == o.asset_id, "Tx gen context has mismatch with tx(asset_id != asset_id) "); - THROW_IF_FALSE_WALLET_INT_ERR_EX(ionic_context.gen_context.amounts[o.index].m_u64[0] == o.amount, "Tx gen context has mismatch with tx(amount != amount)"); - - ammounts_to_b[o.asset_id] += o.amount; - bob_outs[o.index] = true; - } - size_t i = 0; - //validate outputs against decrypted tx generation context - for (i = 0; i != tx.vout.size(); i++) - { - - if (bob_outs[i]) + if (verbose) { - continue; - } - - crypto::public_key asset_id = ionic_context.gen_context.asset_ids[i].to_public_key(); - uint64_t amount = ionic_context.gen_context.amounts[i].m_u64[0]; - ammounts_to_a[asset_id] += amount; - } - - //read amounts already provided by third party - size_t zc_current_index = 0; //some inputs might be old ones, so it's asset id assumed as native and there is no entry for it in real_zc_ins_asset_ids - //THROW_IF_FALSE_WALLET_INT_ERR_EX(ionic_context.gen_context.input_amounts.size() == tx.vin.size(), "Tx gen context has mismatch with tx(amount != amount)"); - for (i = 0; i != tx.vin.size(); i++) - { - //size_t mx = 0; - uint64_t amount = 0; - crypto::public_key in_asset_id = currency::native_coin_asset_id; - if (tx.vin[i].type() == typeid(txin_zc_input)) - { - in_asset_id = ionic_context.gen_context.real_zc_ins_asset_ids[zc_current_index].to_public_key(); - amount = ionic_context.gen_context.zc_input_amounts[zc_current_index]; - zc_current_index++; - //mx = boost::get(tx.vin[i]).key_offsets.size() - 1; - } - else if (tx.vin[i].type() == typeid(txin_to_key)) - { - amount = boost::get(tx.vin[i]).amount; - //mx = boost::get(tx.vin[i]).key_offsets.size() - 1; + std::string res_buff; + local_transfers_struct lt(const_cast(m_transfers)); + epee::serialization::store_t_to_json(lt, res_buff); + ss << res_buff; } else { - WLT_LOG_RED("Unexpected type of input in ionic_swap tx: " << tx.vin[i].type().name(), LOG_LEVEL_0); - return false; - } - amounts_provided_by_a[in_asset_id] += amount; - - //if (proposal_info.mixins == 0 || proposal_info.mixins > mx) - //{ - // proposal_info.mixins = mx; - //} - - } - - //this might be 0, if Alice don't want to pay fee herself - proposal_info.fee_paid_by_a = currency::get_tx_fee(tx); - if (proposal_info.fee_paid_by_a) - { - THROW_IF_FALSE_WALLET_INT_ERR_EX(amounts_provided_by_a[currency::native_coin_asset_id] >= proposal_info.fee_paid_by_a, "Fee mentioned as specified but not provided by A"); - amounts_provided_by_a[currency::native_coin_asset_id] -= proposal_info.fee_paid_by_a; - } - - //proposal_info.fee = currency::get_tx_fee(tx); - //need to make sure that funds for Bob properly funded - for (const auto& a : ammounts_to_b) - { - uint64_t amount_sent_back_to_initiator = ammounts_to_a[a.first]; - - if (amounts_provided_by_a[a.first] < (a.second + amount_sent_back_to_initiator) ) - { - WLT_LOG_RED("Amount[" << a.first << "] provided by Alice(" << amounts_provided_by_a[a.first] << ") is less then transfered to Bob(" << a.second <<")", LOG_LEVEL_0); - return false; - } - amounts_provided_by_a[a.first] -= (amount_sent_back_to_initiator + a.second); - proposal_info.to_finalizer.push_back(view::asset_funds{ a.first, a.second }); - //clean accounted assets - ammounts_to_a.erase(ammounts_to_a.find(a.first)); - if (amounts_provided_by_a[a.first] > 0) - { - WLT_LOG_RED("Amount[" << a.first << "] provided by Alice has unused leftovers: " << amounts_provided_by_a[a.first], LOG_LEVEL_0); - return false; - } - } - - //need to see what Alice actually expect in return - for (const auto& a : ammounts_to_a) - { - //now amount provided by A should be less or equal to what we have in a.second - if (amounts_provided_by_a[a.first] > a.second) - { - //could be fee - WLT_LOG_RED("Amount[" << a.first << "] provided by Alice has unused leftovers: " << amounts_provided_by_a[a.first], LOG_LEVEL_0); - return false; - } - - proposal_info.to_initiator.push_back(view::asset_funds{ a.first, a.second - amounts_provided_by_a[a.first] }); - } - - return true; -} - -//---------------------------------------------------------------------------------------------------- -bool wallet2::accept_ionic_swap_proposal(const std::string& raw_proposal, currency::transaction& result_tx) -{ - wallet_public::ionic_swap_proposal proposal = AUTO_VAL_INIT(proposal); - bool r = t_unserializable_object_from_blob(proposal, raw_proposal); - THROW_IF_TRUE_WALLET_EX(!r, error::wallet_internal_error, "Failed to parse proposal info"); - - return accept_ionic_swap_proposal(proposal, result_tx); -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::accept_ionic_swap_proposal(const wallet_public::ionic_swap_proposal& proposal, currency::transaction& result_tx) -{ - mode_separate_context msc = AUTO_VAL_INIT(msc); - msc.tx_for_mode_separate = proposal.tx_template; - result_tx = msc.tx_for_mode_separate; - - wallet_public::ionic_swap_proposal_context ionic_context = AUTO_VAL_INIT(ionic_context); - bool r = get_ionic_swap_proposal_info(proposal, msc.proposal_info, ionic_context); - THROW_IF_TRUE_WALLET_EX(!r, error::wallet_internal_error, "Failed to get info from proposal"); - - std::unordered_map balances; - uint64_t mined = 0; - this->balance(balances, mined); - //validate balances needed - uint64_t native_amount_required = 0; - for (const auto& item : msc.proposal_info.to_initiator) - { - if (balances[item.asset_id].unlocked < item.amount) - { - THROW_IF_FALSE_WALLET_EX(false, error::not_enough_money, balances[item.asset_id].unlocked, item.amount, 0 /*fee*/, item.asset_id, get_asset_decimal_point(item.asset_id)); - } - if (item.asset_id == currency::native_coin_asset_id) - { - native_amount_required = item.amount; - } - } - - // balances is ok, check if fee is added to tx - uint64_t additional_fee = 0; - if (msc.proposal_info.fee_paid_by_a < m_core_runtime_config.tx_default_fee) - { - additional_fee = m_core_runtime_config.tx_default_fee - msc.proposal_info.fee_paid_by_a; - if (balances[currency::native_coin_asset_id].unlocked < additional_fee + native_amount_required) - { - THROW_IF_FALSE_WALLET_EX(false, error::not_enough_money, balances[currency::native_coin_asset_id].unlocked, native_amount_required, additional_fee, currency::native_coin_asset_id); - } - } - - //everything is seemed to be ok - - construct_tx_param construct_param = get_default_construct_tx_param(); - construct_param.fee = additional_fee; - - crypto::secret_key one_time_key = ionic_context.gen_context.tx_key.sec; // TODO: figure out this mess with tx sec key -- sowle - 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.need_at_least_1_zc = true; - - //build transaction - currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); - ftp.tx_version = this->get_current_tx_version(); - ftp.gen_context = ionic_context.gen_context; - prepare_transaction(construct_param, ftp, msc); - - - - try - { - finalize_transaction(ftp, result_tx, one_time_key, true); - } - catch (...) - { - clear_transfers_from_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, std::string("exception in finalize_transaction, tx: ") + epee::string_tools::pod_to_hex(get_transaction_hash(result_tx))); - throw; - } - mark_transfers_as_spent(ftp.selected_transfers, std::string("Proposal has been accepted with tx <" + epee::string_tools::pod_to_hex(get_transaction_hash(result_tx))) + ">"); - return true; -} -//---------------------------------------------------------------------------------------------------- - -// Signing and auth -bool wallet2::sign_buffer(const std::string& buff, crypto::signature& sig) -{ - crypto::hash h = crypto::cn_fast_hash(buff.data(), buff.size()); - crypto::generate_signature(h, m_account.get_public_address().spend_public_key, m_account.get_keys().spend_secret_key, sig); - return true; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::validate_sign(const std::string& buff, const crypto::signature& sig, const crypto::public_key& pkey) -{ - crypto::hash h = crypto::cn_fast_hash(buff.data(), buff.size()); - return crypto::check_signature(h, pkey, sig); -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::encrypt_buffer(const std::string& buff, std::string& res_buff) -{ - res_buff = buff; - crypto::chacha_crypt(res_buff, m_account.get_keys().view_secret_key); - return true; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::decrypt_buffer(const std::string& buff, std::string& res_buff) -{ - res_buff = buff; - crypto::chacha_crypt(res_buff, m_account.get_keys().view_secret_key); - return true; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::prepare_tx_sources_for_defragmentation_tx(std::vector& sources, std::vector& selected_indicies, uint64_t& found_money) -{ - if (!m_defragmentation_tx_enabled) - return false; - - std::stringstream ss; - if (epee::log_space::log_singletone::get_log_detalisation_level() >= LOG_LEVEL_2) - ss << "preparing sources for utxo defragmentation tx:"; - for (size_t i = 0, size = m_transfers.size(); i < size && selected_indicies.size() < m_max_utxo_count_for_defragmentation_tx; ++i) - { - const auto& td = m_transfers[i]; - if (!td.is_native_coin() || td.m_amount > m_max_allowed_output_amount_for_defragmentation_tx) - continue; - - uint64_t fake_outs_count_for_td = m_decoys_count_for_defragmentation_tx == SIZE_MAX ? (td.is_zc() ? m_core_runtime_config.hf4_minimum_mixins : CURRENCY_DEFAULT_DECOY_SET_SIZE) : m_decoys_count_for_defragmentation_tx; - if (is_transfer_ready_to_go(td, fake_outs_count_for_td)) - { - found_money += td.m_amount; - selected_indicies.push_back(i); - if (epee::log_space::log_singletone::get_log_detalisation_level() >= LOG_LEVEL_2) - ss << " selected transfer #" << i << ", amount: " << print_money_brief(td.m_amount) << ", height: " << td.m_ptx_wallet_info->m_block_height << ", " << (td.is_zc() ? "ZC" : " "); - } - } - - if (selected_indicies.size() < m_min_utxo_count_for_defragmentation_tx || found_money <= TX_MINIMUM_FEE) - { - // too few outputs were found, hence don't create a defragmentation tx - selected_indicies.clear(); - found_money = 0; - return false; - } - - WLT_LOG(ss.str(), LOG_LEVEL_2); - - return prepare_tx_sources(m_decoys_count_for_defragmentation_tx == SIZE_MAX ? CURRENCY_DEFAULT_DECOY_SET_SIZE : m_decoys_count_for_defragmentation_tx, sources, selected_indicies); -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::prepare_tx_sources(assets_selection_context& needed_money_map, size_t fake_outputs_count, uint64_t dust_threshold, std::vector& sources, std::vector& selected_indicies) -{ - try - { - select_transfers(needed_money_map, fake_outputs_count, dust_threshold, selected_indicies); // always returns true, TODO consider refactoring -- sowle - return prepare_tx_sources(fake_outputs_count, sources, selected_indicies); - } - catch(...) - { - // if smth went wrong -- invalidate transfers cache to trigger its regeneration on the next use - // it is necessary because it may be in invalid state (some items might be erased within select_indices_for_transfer() or expand_selection_with_zc_input()) - m_found_free_amounts.clear(); - throw; - } -} -//---------------------------------------------------------------------------------------------------- -void wallet2::prefetch_global_indicies_if_needed(const std::vector& selected_indicies) -{ - //std::list> txs; - //std::list indices_that_requested_global_indicies; - for (uint64_t i : selected_indicies) - { - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(m_transfers[i].m_global_output_index != WALLET_GLOBAL_OUTPUT_INDEX_UNDEFINED, - "m_transfers[" << i << "].m_global_output_index is WALLET_GLOBAL_OUTPUT_INDEX_UNDEFINED"); - //indices_that_requested_global_indicies.push_back(i); - //txs.push_back(m_transfers[i].m_ptx_wallet_info->m_tx); - //} - } - - /* - std::vector > outputs_for_all_txs; - fetch_tx_global_indixes(txs, outputs_for_all_txs); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(txs.size() == outputs_for_all_txs.size(), "missmatch sizes txs.size() == outputs_for_all_txs.size()"); - auto it_indices = indices_that_requested_global_indicies.begin(); - auto it_ooutputs = outputs_for_all_txs.begin(); - for (; it_ooutputs != outputs_for_all_txs.end();) - { - transfer_details& td = m_transfers[*it_indices]; - td.m_global_output_index = (*it_ooutputs)[td.m_internal_output_index]; - it_ooutputs++; it_indices++; - }*/ -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::prepare_tx_sources(size_t fake_outputs_count, std::vector& sources, const std::vector& selected_indicies) -{ - return prepare_tx_sources(fake_outputs_count, false, sources, selected_indicies); -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::prepare_tx_sources(size_t fake_outputs_count_, bool use_all_decoys_if_found_less_than_required, std::vector& sources, const std::vector& selected_indicies) -{ - typedef COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry out_entry; - typedef currency::tx_source_entry::output_entry tx_output_entry; - - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response daemon_resp = AUTO_VAL_INIT(daemon_resp); - //we should request even of fake_outputs_count == 0, since for for postzarcanum this era this param is redefined - //todo: remove if(true) block later if this code will be settled - if (true) - { - size_t fake_outputs_count = fake_outputs_count_; - uint64_t zarcanum_start_from = m_core_runtime_config.hard_forks.m_height_the_hardfork_n_active_after[ZANO_HARDFORK_04_ZARCANUM]; - uint64_t current_size = m_chain.get_blockchain_current_size(); - - bool need_to_request = fake_outputs_count != 0; - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS3::request req = AUTO_VAL_INIT(req); - req.height_upper_limit = m_last_pow_block_h; // request decoys to be either older than, or the same age as stake output's height - req.use_forced_mix_outs = false; // TODO: add this feature to UI later - //req.decoys_count = fake_outputs_count + 1; // one more to be able to skip a decoy in case it hits the real output - for (uint64_t i: selected_indicies) - { - req.amounts.push_back(COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS3::offsets_distribution()); - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS3::offsets_distribution& rdisttib = req.amounts.back(); - - auto it = m_transfers.begin() + i; - 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()); - - //rdisttib.own_global_index = it->m_global_output_index; - //check if we have Zarcanum era output of pre-Zarcanum - if (it->is_zc()) + boost::io::ios_flags_saver ifs(ss); + ss << "index amount spent_h g_index block block_ts flg tx out# key image" << ENDL; + for (const auto& tr : m_transfers) { - if(this->is_auditable()) - continue; - //Zarcanum era - rdisttib.amount = 0; - //generate distribution in Zarcanum hardfork - build_distribution_for_input(rdisttib.global_offsets, it->m_global_output_index); - need_to_request = true; - } - else - { - //for prezarcanum era use flat distribution - rdisttib.amount = it->m_amount; - rdisttib.global_offsets.resize(fake_outputs_count + 1, 0); + uint64_t i = tr.first; + const transfer_details& td = tr.second; + ss << std::right << + std::setw(5) << i << " " << + std::setw(21) << print_money(td.amount()) << " " << + std::setw(7) << td.m_spent_height << " " << + std::setw(7) << td.m_global_output_index << " " << + std::setw(6) << td.m_ptx_wallet_info->m_block_height << " " << + std::setw(10) << td.m_ptx_wallet_info->m_block_timestamp << " " << + std::setw(4) << td.m_flags << " " << + get_transaction_hash(td.m_ptx_wallet_info->m_tx) << " " << + std::setw(4) << td.m_internal_output_index << " " << + td.m_key_image << ENDL; } } - if (need_to_request) + } + //---------------------------------------------------------------------------------------------------- + std::string wallet2::dump_trunsfers(bool verbose) const + { + std::stringstream ss; + dump_trunsfers(ss, verbose); + return ss.str(); + } + //---------------------------------------------------------------------------------------------------- + void wallet2::dump_key_images(std::stringstream& ss) + { + for (auto& ki : m_key_images) { - size_t attempt_count = 0; - while (true) + ss << "[" << ki.first << "]: " << ki.second << ENDL; + } + } + void wallet2::get_multisig_transfers(multisig_transfer_container& ms_transfers) + { + ms_transfers = m_multisig_transfers; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::get_contracts(escrow_contracts_container& contracts) + { + contracts = m_contracts; + return true; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::build_escrow_release_templates(crypto::hash multisig_id, + uint64_t fee, + currency::transaction& tx_release_template, + currency::transaction& tx_burn_template, + const bc_services::contract_private_details& ecrow_details) + { + construct_tx_param construct_params = AUTO_VAL_INIT(construct_params); + construct_params.fee = fee; + construct_params.multisig_id = multisig_id; + construct_params.split_strategy_id = get_current_split_strategy(); + construct_params.dsts.resize(2); + //0 - addr_a + //1 - addr_b + construct_params.dsts[0].addr.push_back(ecrow_details.a_addr); + construct_params.dsts[1].addr.push_back(ecrow_details.b_addr); + + + //generate normal escrow release + construct_params.dsts[0].amount = ecrow_details.amount_a_pledge; + construct_params.dsts[1].amount = ecrow_details.amount_b_pledge + ecrow_details.amount_to_pay; + + //in case of ecrow_details.amount_a_pledge == 0 then exclude a + if (construct_params.dsts[0].amount == 0) + construct_params.dsts.erase(construct_params.dsts.begin()); + + + tx_service_attachment tsa = AUTO_VAL_INIT(tsa); + tsa.service_id = BC_ESCROW_SERVICE_ID; + tsa.instruction = BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_NORMAL; + construct_params.extra.push_back(tsa); + { + currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); + ftp.tx_version = this->get_current_tx_version(); + prepare_transaction(construct_params, ftp); + crypto::secret_key sk = AUTO_VAL_INIT(sk); + finalize_transaction(ftp, tx_release_template, sk, false); + } + + //generate burn escrow + construct_params.dsts.resize(1); + construct_params.dsts[0].addr.clear(); + construct_params.dsts[0].addr.push_back(null_pub_addr); + construct_params.dsts[0].amount = ecrow_details.amount_a_pledge + ecrow_details.amount_b_pledge + ecrow_details.amount_to_pay; + + construct_params.extra.clear(); + tsa.instruction = BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_BURN; + construct_params.extra.push_back(tsa); + { + currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); + ftp.tx_version = this->get_current_tx_version(); + prepare_transaction(construct_params, ftp); + crypto::secret_key sk = AUTO_VAL_INIT(sk); + finalize_transaction(ftp, tx_burn_template, sk, false); + } + } + //---------------------------------------------------------------------------------------------------- + void wallet2::build_escrow_cancel_template(crypto::hash multisig_id, + uint64_t expiration_period, + currency::transaction& tx_cancel_template, + const bc_services::contract_private_details& ecrow_details) + { + auto it = m_multisig_transfers.find(multisig_id); + THROW_IF_FALSE_WALLET_EX(it != m_multisig_transfers.end(), error::wallet_internal_error, + "unable to find multisig id"); + + THROW_IF_FALSE_WALLET_EX(it->second.amount() > (ecrow_details.amount_a_pledge + ecrow_details.amount_to_pay + ecrow_details.amount_b_pledge), error::wallet_internal_error, + "multisig id out amount no more than escrow total amount"); + + construct_tx_param construct_params = AUTO_VAL_INIT(construct_params); + currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); + ftp.tx_version = this->get_current_tx_version(); + 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 = get_current_split_strategy(); + construct_params.dsts.resize(2); + //0 - addr_a + //1 - addr_b + construct_params.dsts[0].addr.push_back(ecrow_details.a_addr); + construct_params.dsts[1].addr.push_back(ecrow_details.b_addr); + + + //generate cancel escrow proposal + construct_params.dsts[0].amount = ecrow_details.amount_a_pledge + ecrow_details.amount_to_pay; + construct_params.dsts[1].amount = ecrow_details.amount_b_pledge; + + if (construct_params.dsts[0].amount == 0) + construct_params.dsts.erase(construct_params.dsts.begin()); + else if (construct_params.dsts[1].amount == 0) + construct_params.dsts.erase(construct_params.dsts.begin() + 1); + + tx_service_attachment tsa = AUTO_VAL_INIT(tsa); + tsa.service_id = BC_ESCROW_SERVICE_ID; + tsa.instruction = BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_CANCEL; + construct_params.extra.push_back(tsa); + currency::etc_tx_details_expiration_time expir = AUTO_VAL_INIT(expir); + expir.v = m_core_runtime_config.get_core_time() + expiration_period; + construct_params.extra.push_back(expir); + + prepare_transaction(construct_params, ftp); + crypto::secret_key sk = AUTO_VAL_INIT(sk); + finalize_transaction(ftp, tx_cancel_template, sk, false); + } + + //---------------------------------------------------------------------------------------------------- + void wallet2::build_escrow_template(const bc_services::contract_private_details& ecrow_details, + size_t fake_outputs_count, + uint64_t unlock_time, + uint64_t expiration_time, + uint64_t b_release_fee, + const std::string& payment_id, + currency::transaction& tx, + std::vector& selected_transfers, + 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 = get_current_split_strategy(); + ctp.tx_outs_attr = CURRENCY_TO_KEY_OUT_RELAXED; + ctp.unlock_time = unlock_time; + + etc_tx_details_expiration_time t = AUTO_VAL_INIT(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; + ctp.extra.push_back(att); + + 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; + ctp.attachments.push_back(att); + } + + currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); + ftp.tx_version = this->get_current_tx_version(); + prepare_transaction(ctp, ftp); + + 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, const std::vector& received, uint64_t expiration, const crypto::hash& related_tx_id) + { + // check all elements in selected_transfers for being already mentioned in m_money_expirations + std::vector selected_transfers_local; + for (auto transfer_index : selected_transfers) + { + bool found = false; + for (auto it = m_money_expirations.begin(); !found && it != m_money_expirations.end(); ++it) { - daemon_resp = COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response(); - bool r = m_core_proxy->call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS3(req, daemon_resp); - THROW_IF_FALSE_WALLET_EX(r, error::no_connection_to_daemon, "getrandom_outs3.bin"); - if (daemon_resp.status == API_RETURN_CODE_FAIL) + auto& st = it->selected_transfers; + found = std::find(st.begin(), st.end(), transfer_index) != st.end(); + } + if (!found) + selected_transfers_local.push_back(transfer_index); // populate selected_transfers_local only with indices, not containing in m_money_expirations + } + + if (selected_transfers_local.empty()) + return; + + m_money_expirations.push_back(AUTO_VAL_INIT(expiration_entry_info())); + m_money_expirations.back().expiration_time = expiration; + m_money_expirations.back().selected_transfers = selected_transfers_local; + m_money_expirations.back().related_tx_id = related_tx_id; + m_money_expirations.back().receved = received; + + std::stringstream ss; + for (auto tr_ind : m_money_expirations.back().selected_transfers) + { + //THROW_IF_FALSE_WALLET_INT_ERR_EX(tr_ind < m_transfers.size(), "invalid transfer index"); + uint32_t flags_before = m_transfers.at(tr_ind).m_flags; + m_transfers.at(tr_ind).m_flags |= WALLET_TRANSFER_DETAIL_FLAG_BLOCKED; + m_transfers.at(tr_ind).m_flags |= WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION; + ss << " " << std::right << std::setw(4) << tr_ind << " " << std::setw(21) << print_money(m_transfers.at(tr_ind).amount()) << " " + << std::setw(2) << std::left << flags_before << " -> " << std::setw(2) << std::left << m_transfers.at(tr_ind).m_flags << " " + << get_transaction_hash(m_transfers.at(tr_ind).m_ptx_wallet_info->m_tx) << std::endl; + } + WLT_LOG_GREEN(m_money_expirations.back().selected_transfers.size() << " transfer(s) added to expiration list:" << ENDL << + "index amount flags tx hash" << ENDL << + ss.str() << ", expire(s) at: " << expiration, LOG_LEVEL_0); + } + //---------------------------------------------------------------------------------------------------- + void wallet2::remove_transfer_from_expiration_list(uint64_t transfer_index) + { + //THROW_IF_FALSE_WALLET_INT_ERR_EX(transfer_index < m_transfers.size(), "invalid transfer index"); + auto& tr_entry = m_transfers.at(transfer_index); + for (auto it = m_money_expirations.begin(); it != m_money_expirations.end(); /* nothing */) + { + auto& st = it->selected_transfers; + auto jt = std::find(st.begin(), st.end(), transfer_index); + if (jt != st.end()) + { + WLT_LOG_GREEN("Transfer [" << transfer_index << "], amount: " << print_money(tr_entry.amount()) << ", tx: " << get_transaction_hash(tr_entry.m_ptx_wallet_info->m_tx) << + " was removed from the expiration list", LOG_LEVEL_0); + st.erase(jt); + if (st.empty()) { - if (attempt_count < 10) - { - attempt_count++; - continue; - } - else - { - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(daemon_resp.outs.size() == selected_indicies.size(), - "unable to exacute getrandom_outs2.bin after 10 attempts with code API_RETURN_CODE_FAIL, there must be problems with mixins"); - } + it = m_money_expirations.erase(it); + continue; } - THROW_IF_FALSE_WALLET_EX(daemon_resp.status != API_RETURN_CODE_BUSY, error::daemon_busy, "getrandom_outs.bin"); - THROW_IF_FALSE_WALLET_EX(daemon_resp.status == API_RETURN_CODE_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_outs2.bin, wrong amounts count = " << daemon_resp.outs.size() << ", expected: " << selected_indicies.size()); - break; } + ++it; + } + // clear proposal reservation flag and blocked flag + uint32_t flags_before = tr_entry.m_flags; + tr_entry.m_flags &= ~WALLET_TRANSFER_DETAIL_FLAG_BLOCKED; + tr_entry.m_flags &= ~WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION; + if (flags_before != tr_entry.m_flags) + { + WLT_LOG_BLUE("Transfer [" << transfer_index << "] was cleared from escrow proposal reservation, flags: " << flags_before << " -> " << tr_entry.m_flags << ", reason: intentional removing from expiration list", LOG_LEVEL_0); + } - std::vector scanty_outs; - THROW_IF_FALSE_WALLET_EX(daemon_resp.outs.size() == req.amounts.size(), error::not_enough_outs_to_mix, scanty_outs, fake_outputs_count); + // (don't change m_spent flag, because transfer status is unclear - the caller should take care of it) + } + //---------------------------------------------------------------------------------------------------- + void wallet2::send_escrow_proposal(const wallet_public::create_proposal_param& wp, + currency::transaction& proposal_tx, + currency::transaction& escrow_template_tx) + { + return send_escrow_proposal(wp.details, wp.fake_outputs_count, wp.unlock_time, wp.expiration_period, wp.fee, wp.b_fee, wp.payment_id, proposal_tx, escrow_template_tx); + } + //---------------------------------------------------------------------------------------------------- + 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, + uint64_t fee, + uint64_t b_release_fee, + const std::string& payment_id, + currency::transaction& tx, + currency::transaction& template_tx) + { + if (!is_connected_to_net()) + { + THROW_IF_TRUE_WALLET_EX(true, error::wallet_internal_error, + "Transfer attempt while daemon offline"); + } - if (!use_all_decoys_if_found_less_than_required) + 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; + 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, get_multisig_out_index(template_tx.vout)); + + 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, contract: " + epee::string_tools::pod_to_hex(ms_id)); + + 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; + 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); + + 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 = get_current_split_strategy(); + ctp.tx_outs_attr = CURRENCY_TO_KEY_OUT_RELAXED; + ctp.unlock_time = unlock_time; + + currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); + ftp.tx_version = this->get_current_tx_version(); + try + { + prepare_transaction(ctp, ftp); + crypto::secret_key sk = AUTO_VAL_INIT(sk); + finalize_transaction(ftp, tx, sk, false); + } + catch (...) + { + clear_transfers_from_flag(selected_transfers_for_template, mask_to_mark_escrow_template_locked_transfers, "undo prepared escrow template tx"); // don't forget to unlock template transfers if smth went wrong + add_transfers_to_transfers_cache(selected_transfers_for_template); + throw; + } + + send_transaction_to_network(tx); + + 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.attachments, ftp.prepared_destinations, ftp.selected_transfers); + + print_tx_sent_message(tx, "(from multisig)", fee); + } + //---------------------------------------------------------------------------------------------------- + void wallet2::create_htlc_proposal(uint64_t amount, const currency::account_public_address& addr, uint64_t lock_blocks_count, currency::transaction& tx, const crypto::hash& htlc_hash, std::string& origin) + { + construct_tx_param ctp = get_default_construct_tx_param(); + ctp.fee = TX_DEFAULT_FEE; + ctp.dsts.resize(1); + ctp.dsts.back().addr.push_back(addr); + ctp.dsts.back().amount = amount; + destination_option_htlc_out& htlc_option = ctp.dsts.back().htlc_options; + htlc_option.expiration = lock_blocks_count; //about 12 hours + htlc_option.htlc_hash = htlc_hash; + + currency::create_and_add_tx_payer_to_container_from_address(ctp.extra, + get_account().get_keys().account_address, get_top_block_height(), get_core_runtime_config()); + + finalized_tx ft = AUTO_VAL_INIT(ft); + this->transfer(ctp, ft, true, nullptr); + origin = ft.htlc_origin; + tx = ft.tx; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::get_list_of_active_htlc(std::list& htlcs, bool only_redeem_txs) + { + for (auto htlc_entry : m_active_htlcs_txid) + { + const transfer_details& td = m_transfers.at(htlc_entry.second); + if (only_redeem_txs && !(td.m_flags & WALLET_TRANSFER_DETAIL_FLAG_HTLC_REDEEM)) { - // make sure we have received the requested number of decoys - for(size_t i = 0; i != daemon_resp.outs.size(); i++) - if (req.amounts[i].amount != 0 && daemon_resp.outs[i].outs.size() != req.amounts[i].global_offsets.size()) - scanty_outs.push_back(daemon_resp.outs[i]); - THROW_IF_FALSE_WALLET_EX(scanty_outs.empty(), error::not_enough_outs_to_mix, scanty_outs, fake_outputs_count); + continue; } + wallet_public::htlc_entry_info entry = AUTO_VAL_INIT(entry); + entry.tx_id = htlc_entry.first; + if (td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].type() != typeid(tx_out_bare)) + { + //@#@ + LOG_ERROR("Unexpected output type in get_list_of_active_htlc:" << td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].type().name()); + continue; + } + const tx_out_bare out_b = boost::get(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]); + entry.amount = out_b.amount; + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(out_b.target.type() == typeid(txout_htlc), + "[get_list_of_active_htlc]Internal error: unexpected type of out"); + const txout_htlc& htlc = boost::get(out_b.target); + entry.sha256_hash = htlc.htlc_hash; + + currency::tx_payer payer = AUTO_VAL_INIT(payer); + if (currency::get_type_in_variant_container(td.varian_options, payer)) + entry.counterparty_address = payer.acc_addr; + + entry.is_redeem = td.m_flags & WALLET_TRANSFER_DETAIL_FLAG_HTLC_REDEEM ? true : false; + htlcs.push_back(entry); } } - - //lets prefetch m_global_output_index for selected_indicies - //this days doesn't prefetch, only validated that prefetch is not needed - prefetch_global_indicies_if_needed(selected_indicies); - - //prepare inputs - size_t i = 0; - for (uint64_t J : selected_indicies) + //---------------------------------------------------------------------------------------------------- + void wallet2::redeem_htlc(const crypto::hash& htlc_tx_id, const std::string& origin) + { + currency::transaction result_tx = AUTO_VAL_INIT(result_tx); + return redeem_htlc(htlc_tx_id, origin, result_tx); + } + //---------------------------------------------------------------------------------------------------- + void wallet2::redeem_htlc(const crypto::hash& htlc_tx_id, const std::string& origin, currency::transaction& result_tx) { - auto it = m_transfers.begin() + J; - sources.push_back(AUTO_VAL_INIT(currency::tx_source_entry())); - currency::tx_source_entry& src = sources.back(); - transfer_details& td = *it; - src.transfer_index = it - m_transfers.begin(); - src.amount = td.amount(); - src.asset_id = td.get_asset_id(); - size_t fake_outputs_count = fake_outputs_count_; - //redefine for hardfork - if (td.is_zc() && !this->is_auditable()) - fake_outputs_count = m_core_runtime_config.hf4_minimum_mixins; - + construct_tx_param ctp = get_default_construct_tx_param(); + ctp.fee = TX_DEFAULT_FEE; + ctp.htlc_tx_id = htlc_tx_id; + ctp.htlc_origin = origin; + ctp.dsts.resize(1); + ctp.dsts.back().addr.push_back(m_account.get_keys().account_address); - //paste mixin transaction - if (daemon_resp.outs.size()) + auto it = m_active_htlcs_txid.find(htlc_tx_id); + WLT_THROW_IF_FALSE_WITH_CODE(it != m_active_htlcs_txid.end(), + "htlc not found with tx_id = " << htlc_tx_id, API_RETURN_CODE_NOT_FOUND); + + ctp.dsts.back().amount = m_transfers.at(it->second).amount() - ctp.fee; + this->transfer(ctp, result_tx, true, nullptr); + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::check_htlc_redeemed(const crypto::hash& htlc_tx_id, std::string& origin, crypto::hash& redeem_tx_id) + { + auto it = m_active_htlcs_txid.find(htlc_tx_id); + + WLT_THROW_IF_FALSE_WITH_CODE(it != m_active_htlcs_txid.end(), + "htlc not found with tx_id = " << htlc_tx_id, API_RETURN_CODE_NOT_FOUND); + + transfer_details_extra_option_htlc_info htlc_options = AUTO_VAL_INIT(htlc_options); + if (!currency::get_type_in_variant_container(m_transfers.at(it->second).varian_options, htlc_options)) { - if (td.is_zc()) + return false; + } + if (htlc_options.origin.size()) + { + origin = htlc_options.origin; + redeem_tx_id = htlc_options.redeem_tx_id; + return true; + } + return false; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::create_ionic_swap_proposal(const wallet_public::ionic_swap_proposal_info& proposal_details, const currency::account_public_address& destination_addr, wallet_public::ionic_swap_proposal& proposal) + { + std::vector selected_transfers_for_template; + + return build_ionic_swap_template(proposal_details, destination_addr, proposal, selected_transfers_for_template); + + //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 ionic_swap"); + //return true; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::build_ionic_swap_template(const wallet_public::ionic_swap_proposal_info& proposal_detais, const currency::account_public_address& destination_addr, + wallet_public::ionic_swap_proposal& proposal, + std::vector& selected_transfers) + { + WLT_THROW_IF_FALSE_WITH_CODE(proposal_detais.fee_paid_by_a >= get_current_minimum_network_fee(), "Error at build_ionic_swap_template, ", API_RETURN_CODE_WALLET_FEE_TOO_LOW); + + construct_tx_param ctp = get_default_construct_tx_param(); + + //ctp.fake_outputs_count = proposal_detais.mixins; + ctp.fee = proposal_detais.fee_paid_by_a; + ctp.flags = TX_FLAG_SIGNATURE_MODE_SEPARATE; + ctp.mark_tx_as_complete = false; + ctp.crypt_address = destination_addr; + + ctp.dsts.resize(proposal_detais.to_finalizer.size() + proposal_detais.to_initiator.size()); + size_t i = 0; + // Here is an proposed for exchange funds + for (; i != proposal_detais.to_finalizer.size(); i++) + { + ctp.dsts[i].amount = proposal_detais.to_finalizer[i].amount; + ctp.dsts[i].amount_to_provide = proposal_detais.to_finalizer[i].amount; + ctp.dsts[i].flags |= tx_destination_entry_flags::tdef_explicit_amount_to_provide; + ctp.dsts[i].addr.push_back(destination_addr); + ctp.dsts[i].asset_id = proposal_detais.to_finalizer[i].asset_id; + } + // Here is an expected in return funds + std::vector for_expiration_list; + for (size_t j = 0; j != proposal_detais.to_initiator.size(); j++, i++) + { + ctp.dsts[i].amount = proposal_detais.to_initiator[j].amount; + ctp.dsts[i].amount_to_provide = 0; + ctp.dsts[i].flags |= tx_destination_entry_flags::tdef_explicit_amount_to_provide; + ctp.dsts[i].addr.push_back(m_account.get_public_address()); + ctp.dsts[i].asset_id = proposal_detais.to_initiator[j].asset_id; + for_expiration_list.push_back(payment_details_subtransfer{ ctp.dsts[i].asset_id, ctp.dsts[i].amount }); + } + + currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); + ftp.mode_separate_fee = ctp.fee; + ftp.tx_version = this->get_current_tx_version(); + prepare_transaction(ctp, ftp); + + selected_transfers = ftp.selected_transfers; + currency::finalized_tx finalize_result = AUTO_VAL_INIT(finalize_result); + finalize_transaction(ftp, finalize_result, false); + for (uint64_t i : selected_transfers) + m_transfers.at(i).m_flags &= ~WALLET_TRANSFER_DETAIL_FLAG_BLOCKED; + + //add_transfers_to_expiration_list(selected_transfers, for_expiration_list, this->get_core_runtime_config().get_core_time() + proposal_detais.expiration_time, currency::null_hash); + + //wrap it all + proposal.tx_template = finalize_result.tx; + wallet_public::ionic_swap_proposal_context ispc = AUTO_VAL_INIT(ispc); + ispc.gen_context = finalize_result.ftp.gen_context; + //ispc.one_time_skey = finalize_result.one_time_key; + std::string proposal_context_blob = t_serializable_object_to_blob(ispc); + proposal.encrypted_context = crypto::chacha_crypt(static_cast(proposal_context_blob), finalize_result.derivation); + return true; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::get_ionic_swap_proposal_info(const std::string& raw_proposal, wallet_public::ionic_swap_proposal_info& proposal_info) const + { + wallet_public::ionic_swap_proposal proposal = AUTO_VAL_INIT(proposal); + bool r = t_unserializable_object_from_blob(proposal, raw_proposal); + THROW_IF_TRUE_WALLET_EX(!r, error::wallet_internal_error, "Failed to parse proposal"); + return get_ionic_swap_proposal_info(proposal, proposal_info); + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::get_ionic_swap_proposal_info(const wallet_public::ionic_swap_proposal& proposal, wallet_public::ionic_swap_proposal_info& proposal_info) const + { + wallet_public::ionic_swap_proposal_context ionic_context = AUTO_VAL_INIT(ionic_context); + return get_ionic_swap_proposal_info(proposal, proposal_info, ionic_context); + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::get_ionic_swap_proposal_info(const wallet_public::ionic_swap_proposal& proposal, wallet_public::ionic_swap_proposal_info& proposal_info, wallet_public::ionic_swap_proposal_context& ionic_context) const + { + const transaction& tx = proposal.tx_template; + crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); + std::vector outs; + bool r = lookup_acc_outs(m_account.get_keys(), tx, outs, derivation); + THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "Failed to lookup_acc_outs for tx: " << get_transaction_hash(tx)); + + if (!outs.size()) + { + return false; + } + + //decrypt context + std::string decrypted_raw_context = crypto::chacha_crypt(proposal.encrypted_context, derivation); + r = t_unserializable_object_from_blob(ionic_context, decrypted_raw_context); + THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "Failed to unserialize decrypted ionic_context"); + + r = validate_tx_details_against_tx_generation_context(tx, ionic_context.gen_context); + THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "validate_tx_details_against_tx_generation_context failed"); + + std::unordered_map amounts_provided_by_a; + + std::unordered_map ammounts_to_a; //amounts to Alice (the one who created proposal), should be NOT funded + std::unordered_map ammounts_to_b; //amounts to Bob (the one who received proposal), should BE funded + std::vector bob_outs; + bob_outs.resize(proposal.tx_template.vout.size()); + + for (const auto& o : outs) + { + THROW_IF_FALSE_WALLET_INT_ERR_EX(ionic_context.gen_context.asset_ids.size() > o.index, "Tx gen context has mismatch with tx(asset_ids) "); + THROW_IF_FALSE_WALLET_INT_ERR_EX(ionic_context.gen_context.asset_ids[o.index].to_public_key() == o.asset_id, "Tx gen context has mismatch with tx(asset_id != asset_id) "); + THROW_IF_FALSE_WALLET_INT_ERR_EX(ionic_context.gen_context.amounts[o.index].m_u64[0] == o.amount, "Tx gen context has mismatch with tx(amount != amount)"); + + ammounts_to_b[o.asset_id] += o.amount; + bob_outs[o.index] = true; + } + size_t i = 0; + //validate outputs against decrypted tx generation context + for (i = 0; i != tx.vout.size(); i++) + { + + if (bob_outs[i]) { - //get rid of unneeded - select_decoys(daemon_resp.outs[i], td.m_global_output_index); + continue; + } + + crypto::public_key asset_id = ionic_context.gen_context.asset_ids[i].to_public_key(); + uint64_t amount = ionic_context.gen_context.amounts[i].m_u64[0]; + ammounts_to_a[asset_id] += amount; + } + + //read amounts already provided by third party + size_t zc_current_index = 0; //some inputs might be old ones, so it's asset id assumed as native and there is no entry for it in real_zc_ins_asset_ids + //THROW_IF_FALSE_WALLET_INT_ERR_EX(ionic_context.gen_context.input_amounts.size() == tx.vin.size(), "Tx gen context has mismatch with tx(amount != amount)"); + for (i = 0; i != tx.vin.size(); i++) + { + //size_t mx = 0; + uint64_t amount = 0; + crypto::public_key in_asset_id = currency::native_coin_asset_id; + if (tx.vin[i].type() == typeid(txin_zc_input)) + { + in_asset_id = ionic_context.gen_context.real_zc_ins_asset_ids[zc_current_index].to_public_key(); + amount = ionic_context.gen_context.zc_input_amounts[zc_current_index]; + zc_current_index++; + //mx = boost::get(tx.vin[i]).key_offsets.size() - 1; + } + else if (tx.vin[i].type() == typeid(txin_to_key)) + { + amount = boost::get(tx.vin[i]).amount; + //mx = boost::get(tx.vin[i]).key_offsets.size() - 1; } else { - //TODO: make sure we have exact count needed + WLT_LOG_RED("Unexpected type of input in ionic_swap tx: " << tx.vin[i].type().name(), LOG_LEVEL_0); + return false; } - - daemon_resp.outs[i].outs.sort([](const out_entry& a, const out_entry& b){return a.global_amount_index < b.global_amount_index; }); - for(out_entry& daemon_oe : daemon_resp.outs[i].outs) + amounts_provided_by_a[in_asset_id] += amount; + + //if (proposal_info.mixins == 0 || proposal_info.mixins > mx) + //{ + // proposal_info.mixins = mx; + //} + + } + + //this might be 0, if Alice don't want to pay fee herself + proposal_info.fee_paid_by_a = currency::get_tx_fee(tx); + if (proposal_info.fee_paid_by_a) + { + THROW_IF_FALSE_WALLET_INT_ERR_EX(amounts_provided_by_a[currency::native_coin_asset_id] >= proposal_info.fee_paid_by_a, "Fee mentioned as specified but not provided by A"); + amounts_provided_by_a[currency::native_coin_asset_id] -= proposal_info.fee_paid_by_a; + } + + //proposal_info.fee = currency::get_tx_fee(tx); + //need to make sure that funds for Bob properly funded + for (const auto& a : ammounts_to_b) + { + uint64_t amount_sent_back_to_initiator = ammounts_to_a[a.first]; + + if (amounts_provided_by_a[a.first] < (a.second + amount_sent_back_to_initiator)) { - if (td.m_global_output_index == daemon_oe.global_amount_index) - continue; - tx_output_entry oe = AUTO_VAL_INIT(oe); - oe.amount_commitment = daemon_oe.amount_commitment; - oe.concealing_point = daemon_oe.concealing_point; - oe.out_reference = daemon_oe.global_amount_index; - oe.stealth_address = daemon_oe.stealth_address; - oe.blinded_asset_id = daemon_oe.blinded_asset_id; // TODO @#@# BAD DESIGN, consider refactoring -- sowle - src.outputs.push_back(oe); - if (src.outputs.size() >= fake_outputs_count) - break; + WLT_LOG_RED("Amount[" << a.first << "] provided by Alice(" << amounts_provided_by_a[a.first] << ") is less then transfered to Bob(" << a.second << ")", LOG_LEVEL_0); + return false; + } + amounts_provided_by_a[a.first] -= (amount_sent_back_to_initiator + a.second); + proposal_info.to_finalizer.push_back(view::asset_funds{ a.first, a.second }); + //clean accounted assets + ammounts_to_a.erase(ammounts_to_a.find(a.first)); + if (amounts_provided_by_a[a.first] > 0) + { + WLT_LOG_RED("Amount[" << a.first << "] provided by Alice has unused leftovers: " << amounts_provided_by_a[a.first], LOG_LEVEL_0); + return false; } } - //paste real transaction to the random index - auto it_to_insert = std::find_if(src.outputs.begin(), src.outputs.end(), [&](const tx_output_entry& a) + //need to see what Alice actually expect in return + for (const auto& a : ammounts_to_a) { - if (a.out_reference.type().hash_code() == typeid(uint64_t).hash_code()) - return static_cast(boost::get(a.out_reference) >= td.m_global_output_index); - return false; // TODO: implement deterministics real output placement in case there're ref_by_id outs - }); - //size_t real_index = src.outputs.size() ? (rand() % src.outputs.size() ):0; - tx_output_entry real_oe = AUTO_VAL_INIT(real_oe); - real_oe.out_reference = td.m_global_output_index; // TODO: use ref_by_id when neccessary - VARIANT_SWITCH_BEGIN(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]); - VARIANT_CASE_CONST(tx_out_bare, o) - { - VARIANT_SWITCH_BEGIN(o.target); - VARIANT_CASE_CONST(txout_to_key, o) - real_oe.stealth_address = o.key; - VARIANT_CASE_CONST(txout_htlc, htlc) - real_oe.stealth_address = htlc.pkey_refund; - VARIANT_CASE_OTHER() + //now amount provided by A should be less or equal to what we have in a.second + if (amounts_provided_by_a[a.first] > a.second) { - WLT_THROW_IF_FALSE_WITH_CODE(false, - "Internal error: unexpected type of target: " << o.target.type().name(), - API_RETURN_CODE_INTERNAL_ERROR); + //could be fee + WLT_LOG_RED("Amount[" << a.first << "] provided by Alice has unused leftovers: " << amounts_provided_by_a[a.first], LOG_LEVEL_0); + return false; } - VARIANT_SWITCH_END(); + + proposal_info.to_initiator.push_back(view::asset_funds{ a.first, a.second - amounts_provided_by_a[a.first] }); } - VARIANT_CASE_CONST(tx_out_zarcanum, o); - real_oe.amount_commitment = o.amount_commitment; // TODO @#@# consider using shorter code like in sweep_below() (or better reuse it) - real_oe.concealing_point = o.concealing_point; - real_oe.stealth_address = o.stealth_address; - real_oe.blinded_asset_id = o.blinded_asset_id; - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.is_zc(), "transfer #" << J << ", amount: " << print_money_brief(td.amount()) << " is not a ZC"); - src.real_out_amount_blinding_mask = td.m_zc_info_ptr->amount_blinding_mask; + + return true; + } + + //---------------------------------------------------------------------------------------------------- + bool wallet2::accept_ionic_swap_proposal(const std::string& raw_proposal, currency::transaction& result_tx) + { + wallet_public::ionic_swap_proposal proposal = AUTO_VAL_INIT(proposal); + bool r = t_unserializable_object_from_blob(proposal, raw_proposal); + THROW_IF_TRUE_WALLET_EX(!r, error::wallet_internal_error, "Failed to parse proposal info"); + + return accept_ionic_swap_proposal(proposal, result_tx); + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::accept_ionic_swap_proposal(const wallet_public::ionic_swap_proposal& proposal, currency::transaction& result_tx) + { + mode_separate_context msc = AUTO_VAL_INIT(msc); + msc.tx_for_mode_separate = proposal.tx_template; + result_tx = msc.tx_for_mode_separate; + + wallet_public::ionic_swap_proposal_context ionic_context = AUTO_VAL_INIT(ionic_context); + bool r = get_ionic_swap_proposal_info(proposal, msc.proposal_info, ionic_context); + THROW_IF_TRUE_WALLET_EX(!r, error::wallet_internal_error, "Failed to get info from proposal"); + + std::unordered_map balances; + uint64_t mined = 0; + this->balance(balances, mined); + //validate balances needed + uint64_t native_amount_required = 0; + for (const auto& item : msc.proposal_info.to_initiator) + { + if (balances[item.asset_id].unlocked < item.amount) + { + THROW_IF_FALSE_WALLET_EX(false, error::not_enough_money, balances[item.asset_id].unlocked, item.amount, 0 /*fee*/, item.asset_id, get_asset_decimal_point(item.asset_id)); + } + if (item.asset_id == currency::native_coin_asset_id) + { + native_amount_required = item.amount; + } + } + + // balances is ok, check if fee is added to tx + uint64_t additional_fee = 0; + if (msc.proposal_info.fee_paid_by_a < m_core_runtime_config.tx_default_fee) + { + additional_fee = m_core_runtime_config.tx_default_fee - msc.proposal_info.fee_paid_by_a; + if (balances[currency::native_coin_asset_id].unlocked < additional_fee + native_amount_required) + { + THROW_IF_FALSE_WALLET_EX(false, error::not_enough_money, balances[currency::native_coin_asset_id].unlocked, native_amount_required, additional_fee, currency::native_coin_asset_id); + } + } + + //everything is seemed to be ok + + construct_tx_param construct_param = get_default_construct_tx_param(); + construct_param.fee = additional_fee; + + crypto::secret_key one_time_key = ionic_context.gen_context.tx_key.sec; // TODO: figure out this mess with tx sec key -- sowle + 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.need_at_least_1_zc = true; + + //build transaction + currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); + ftp.tx_version = this->get_current_tx_version(); + ftp.gen_context = ionic_context.gen_context; + prepare_transaction(construct_param, ftp, msc); + + + + try + { + finalize_transaction(ftp, result_tx, one_time_key, true); + } + catch (...) + { + clear_transfers_from_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, std::string("exception in finalize_transaction, tx: ") + epee::string_tools::pod_to_hex(get_transaction_hash(result_tx))); + throw; + } + mark_transfers_as_spent(ftp.selected_transfers, std::string("Proposal has been accepted with tx <" + epee::string_tools::pod_to_hex(get_transaction_hash(result_tx))) + ">"); + return true; + } + //---------------------------------------------------------------------------------------------------- + + // Signing and auth + bool wallet2::sign_buffer(const std::string& buff, crypto::signature& sig) + { + crypto::hash h = crypto::cn_fast_hash(buff.data(), buff.size()); + crypto::generate_signature(h, m_account.get_public_address().spend_public_key, m_account.get_keys().spend_secret_key, sig); + return true; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::validate_sign(const std::string& buff, const crypto::signature& sig, const crypto::public_key& pkey) + { + crypto::hash h = crypto::cn_fast_hash(buff.data(), buff.size()); + return crypto::check_signature(h, pkey, sig); + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::encrypt_buffer(const std::string& buff, std::string& res_buff) + { + res_buff = buff; + crypto::chacha_crypt(res_buff, m_account.get_keys().view_secret_key); + return true; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::decrypt_buffer(const std::string& buff, std::string& res_buff) + { + res_buff = buff; + crypto::chacha_crypt(res_buff, m_account.get_keys().view_secret_key); + return true; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::prepare_tx_sources_for_defragmentation_tx(std::vector& sources, std::vector& selected_indicies, uint64_t& found_money) + { + if (!m_defragmentation_tx_enabled) + return false; + + std::stringstream ss; + if (epee::log_space::log_singletone::get_log_detalisation_level() >= LOG_LEVEL_2) + ss << "preparing sources for utxo defragmentation tx:"; + for (const auto& tr : m_transfers)//size_t i = 0, size = m_transfers .size(); i < size && selected_indicies.size() < m_max_utxo_count_for_defragmentation_tx; ++i) + { + if (selected_indicies.size() >= m_max_utxo_count_for_defragmentation_tx) + break; + uint64_t i = tr.first; + const auto& td = tr.second; + if (!td.is_native_coin() || td.m_amount > m_max_allowed_output_amount_for_defragmentation_tx) + continue; + + uint64_t fake_outs_count_for_td = m_decoys_count_for_defragmentation_tx == SIZE_MAX ? (td.is_zc() ? m_core_runtime_config.hf4_minimum_mixins : CURRENCY_DEFAULT_DECOY_SET_SIZE) : m_decoys_count_for_defragmentation_tx; + if (is_transfer_ready_to_go(td, fake_outs_count_for_td)) + { + found_money += td.m_amount; + selected_indicies.push_back(i); + if (epee::log_space::log_singletone::get_log_detalisation_level() >= LOG_LEVEL_2) + ss << " selected transfer #" << i << ", amount: " << print_money_brief(td.m_amount) << ", height: " << td.m_ptx_wallet_info->m_block_height << ", " << (td.is_zc() ? "ZC" : " "); + } + } + + if (selected_indicies.size() < m_min_utxo_count_for_defragmentation_tx || found_money <= TX_MINIMUM_FEE) + { + // too few outputs were found, hence don't create a defragmentation tx + selected_indicies.clear(); + found_money = 0; + return false; + } + + WLT_LOG(ss.str(), LOG_LEVEL_2); + + return prepare_tx_sources(m_decoys_count_for_defragmentation_tx == SIZE_MAX ? CURRENCY_DEFAULT_DECOY_SET_SIZE : m_decoys_count_for_defragmentation_tx, sources, selected_indicies); + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::prepare_tx_sources(assets_selection_context& needed_money_map, size_t fake_outputs_count, uint64_t dust_threshold, std::vector& sources, std::vector& selected_indicies) + { + try + { + select_transfers(needed_money_map, fake_outputs_count, dust_threshold, selected_indicies); // always returns true, TODO consider refactoring -- sowle + return prepare_tx_sources(fake_outputs_count, sources, selected_indicies); + } + catch (...) + { + // if smth went wrong -- invalidate transfers cache to trigger its regeneration on the next use + // it is necessary because it may be in invalid state (some items might be erased within select_indices_for_transfer() or expand_selection_with_zc_input()) + m_found_free_amounts.clear(); + throw; + } + } + //---------------------------------------------------------------------------------------------------- + void wallet2::prefetch_global_indicies_if_needed(const std::vector& selected_indicies) + { + //std::list> txs; + //std::list indices_that_requested_global_indicies; + for (uint64_t i : selected_indicies) + { + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(m_transfers.at(i).m_global_output_index != WALLET_GLOBAL_OUTPUT_INDEX_UNDEFINED, + "m_transfers.at(" << i << ").m_global_output_index is WALLET_GLOBAL_OUTPUT_INDEX_UNDEFINED"); + //indices_that_requested_global_indicies.push_back(i); + //txs.push_back(m_transfers.at(i).m_ptx_wallet_info->m_tx); + //} + } + + /* + std::vector > outputs_for_all_txs; + fetch_tx_global_indixes(txs, outputs_for_all_txs); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(txs.size() == outputs_for_all_txs.size(), "missmatch sizes txs.size() == outputs_for_all_txs.size()"); + auto it_indices = indices_that_requested_global_indicies.begin(); + auto it_ooutputs = outputs_for_all_txs.begin(); + for (; it_ooutputs != outputs_for_all_txs.end();) + { + transfer_details& td = m_transfers.at(*it_indices); + td.m_global_output_index = (*it_ooutputs)[td.m_internal_output_index]; + it_ooutputs++; it_indices++; + }*/ + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::prepare_tx_sources(size_t fake_outputs_count, std::vector& sources, const std::vector& selected_indicies) + { + return prepare_tx_sources(fake_outputs_count, false, sources, selected_indicies); + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::prepare_tx_sources(size_t fake_outputs_count_, bool use_all_decoys_if_found_less_than_required, std::vector& sources, const std::vector& selected_indicies) + { + typedef COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry out_entry; + typedef currency::tx_source_entry::output_entry tx_output_entry; + + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response daemon_resp = AUTO_VAL_INIT(daemon_resp); + //we should request even of fake_outputs_count == 0, since for for postzarcanum this era this param is redefined + //todo: remove if(true) block later if this code will be settled + if (true) + { + size_t fake_outputs_count = fake_outputs_count_; + uint64_t zarcanum_start_from = m_core_runtime_config.hard_forks.m_height_the_hardfork_n_active_after[ZANO_HARDFORK_04_ZARCANUM]; + uint64_t current_size = m_chain.get_blockchain_current_size(); + + bool need_to_request = fake_outputs_count != 0; + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS3::request req = AUTO_VAL_INIT(req); + req.height_upper_limit = m_last_pow_block_h; // request decoys to be either older than, or the same age as stake output's height + req.use_forced_mix_outs = false; // TODO: add this feature to UI later + //req.decoys_count = fake_outputs_count + 1; // one more to be able to skip a decoy in case it hits the real output + for (uint64_t i : selected_indicies) + { + req.amounts.push_back(COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS3::offsets_distribution()); + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS3::offsets_distribution& rdisttib = req.amounts.back(); + + auto it = m_transfers.begin() + i; + 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()); + + //rdisttib.own_global_index = it->m_global_output_index; + //check if we have Zarcanum era output of pre-Zarcanum + if (it->is_zc()) + { + if (this->is_auditable()) + continue; + //Zarcanum era + rdisttib.amount = 0; + //generate distribution in Zarcanum hardfork + build_distribution_for_input(rdisttib.global_offsets, it->m_global_output_index); + need_to_request = true; + } + else + { + //for prezarcanum era use flat distribution + rdisttib.amount = it->m_amount; + rdisttib.global_offsets.resize(fake_outputs_count + 1, 0); + } + } + if (need_to_request) + { + size_t attempt_count = 0; + while (true) + { + daemon_resp = COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response(); + bool r = m_core_proxy->call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS3(req, daemon_resp); + THROW_IF_FALSE_WALLET_EX(r, error::no_connection_to_daemon, "getrandom_outs3.bin"); + if (daemon_resp.status == API_RETURN_CODE_FAIL) + { + if (attempt_count < 10) + { + attempt_count++; + continue; + } + else + { + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(daemon_resp.outs.size() == selected_indicies.size(), + "unable to exacute getrandom_outs2.bin after 10 attempts with code API_RETURN_CODE_FAIL, there must be problems with mixins"); + } + } + THROW_IF_FALSE_WALLET_EX(daemon_resp.status != API_RETURN_CODE_BUSY, error::daemon_busy, "getrandom_outs.bin"); + THROW_IF_FALSE_WALLET_EX(daemon_resp.status == API_RETURN_CODE_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_outs2.bin, wrong amounts count = " << daemon_resp.outs.size() << ", expected: " << selected_indicies.size()); + break; + } + + std::vector scanty_outs; + THROW_IF_FALSE_WALLET_EX(daemon_resp.outs.size() == req.amounts.size(), error::not_enough_outs_to_mix, scanty_outs, fake_outputs_count); + + if (!use_all_decoys_if_found_less_than_required) + { + // make sure we have received the requested number of decoys + for (size_t i = 0; i != daemon_resp.outs.size(); i++) + if (req.amounts[i].amount != 0 && daemon_resp.outs[i].outs.size() != req.amounts[i].global_offsets.size()) + scanty_outs.push_back(daemon_resp.outs[i]); + THROW_IF_FALSE_WALLET_EX(scanty_outs.empty(), error::not_enough_outs_to_mix, scanty_outs, fake_outputs_count); + } + } + } + + //lets prefetch m_global_output_index for selected_indicies + //this days doesn't prefetch, only validated that prefetch is not needed + prefetch_global_indicies_if_needed(selected_indicies); + + //prepare inputs + size_t i = 0; + for (uint64_t J : selected_indicies) + { + auto it = m_transfers.begin() + J; + + sources.push_back(AUTO_VAL_INIT(currency::tx_source_entry())); + currency::tx_source_entry& src = sources.back(); + transfer_details& td = *it; + src.transfer_index = it - m_transfers.begin(); + src.amount = td.amount(); + src.asset_id = td.get_asset_id(); + size_t fake_outputs_count = fake_outputs_count_; + //redefine for hardfork + if (td.is_zc() && !this->is_auditable()) + fake_outputs_count = m_core_runtime_config.hf4_minimum_mixins; + + + //paste mixin transaction + if (daemon_resp.outs.size()) + { + if (td.is_zc()) + { + //get rid of unneeded + select_decoys(daemon_resp.outs[i], td.m_global_output_index); + } + else + { + //TODO: make sure we have exact count needed + } + + daemon_resp.outs[i].outs.sort([](const out_entry& a, const out_entry& b) {return a.global_amount_index < b.global_amount_index; }); + for (out_entry& daemon_oe : daemon_resp.outs[i].outs) + { + if (td.m_global_output_index == daemon_oe.global_amount_index) + continue; + tx_output_entry oe = AUTO_VAL_INIT(oe); + oe.amount_commitment = daemon_oe.amount_commitment; + oe.concealing_point = daemon_oe.concealing_point; + oe.out_reference = daemon_oe.global_amount_index; + oe.stealth_address = daemon_oe.stealth_address; + oe.blinded_asset_id = daemon_oe.blinded_asset_id; // TODO @#@# BAD DESIGN, consider refactoring -- sowle + src.outputs.push_back(oe); + if (src.outputs.size() >= fake_outputs_count) + break; + } + } + + //paste real transaction to the random index + auto it_to_insert = std::find_if(src.outputs.begin(), src.outputs.end(), [&](const tx_output_entry& a) + { + if (a.out_reference.type().hash_code() == typeid(uint64_t).hash_code()) + return static_cast(boost::get(a.out_reference) >= td.m_global_output_index); + return false; // TODO: implement deterministics real output placement in case there're ref_by_id outs + }); + //size_t real_index = src.outputs.size() ? (rand() % src.outputs.size() ):0; + tx_output_entry real_oe = AUTO_VAL_INIT(real_oe); + real_oe.out_reference = td.m_global_output_index; // TODO: use ref_by_id when neccessary + VARIANT_SWITCH_BEGIN(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]); + VARIANT_CASE_CONST(tx_out_bare, o) + { + VARIANT_SWITCH_BEGIN(o.target); + VARIANT_CASE_CONST(txout_to_key, o) + real_oe.stealth_address = o.key; + VARIANT_CASE_CONST(txout_htlc, htlc) + real_oe.stealth_address = htlc.pkey_refund; + VARIANT_CASE_OTHER() + { + WLT_THROW_IF_FALSE_WITH_CODE(false, + "Internal error: unexpected type of target: " << o.target.type().name(), + API_RETURN_CODE_INTERNAL_ERROR); + } + VARIANT_SWITCH_END(); + } + VARIANT_CASE_CONST(tx_out_zarcanum, o); + real_oe.amount_commitment = o.amount_commitment; // TODO @#@# consider using shorter code like in sweep_below() (or better reuse it) + real_oe.concealing_point = o.concealing_point; + real_oe.stealth_address = o.stealth_address; + real_oe.blinded_asset_id = o.blinded_asset_id; + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.is_zc(), "transfer #" << J << ", amount: " << print_money_brief(td.amount()) << " is not a ZC"); + src.real_out_amount_blinding_mask = td.m_zc_info_ptr->amount_blinding_mask; src.real_out_asset_id_blinding_mask = td.m_zc_info_ptr->asset_id_blinding_mask; - src.asset_id = td.m_zc_info_ptr->asset_id; + src.asset_id = td.m_zc_info_ptr->asset_id; #ifndef NDEBUG WLT_CHECK_AND_ASSERT_MES(crypto::point_t(src.asset_id) + src.real_out_asset_id_blinding_mask * crypto::c_point_X == crypto::point_t(real_oe.blinded_asset_id).modify_mul8(), false, "real_out_asset_id_blinding_mask doesn't match real_oe.blinded_asset_id"); WLT_CHECK_AND_ASSERT_MES(td.m_amount * crypto::point_t(real_oe.blinded_asset_id).modify_mul8() + src.real_out_amount_blinding_mask * crypto::c_point_G == crypto::point_t(real_oe.amount_commitment).modify_mul8(), false, "real_out_amount_blinding_mask doesn't match real_oe.amount_commitment"); #endif - VARIANT_SWITCH_END(); + VARIANT_SWITCH_END(); - auto interted_it = src.outputs.insert(it_to_insert, real_oe); - src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_ptx_wallet_info->m_tx); - src.real_output = interted_it - src.outputs.begin(); + auto interted_it = src.outputs.insert(it_to_insert, real_oe); + src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_ptx_wallet_info->m_tx); + src.real_output = interted_it - src.outputs.begin(); + src.real_output_in_tx_index = td.m_internal_output_index; + + if (epee::log_space::get_set_log_detalisation_level() >= LOG_LEVEL_1) + { + std::stringstream ss; + ss << "source entry [" << i << "], td_idx: " << J << ", "; + print_source_entry(ss, src); + WLT_LOG_L1(ss.str()); + } + + ++i; + } + return true; + } + + + //---------------------------------------------------------------------------------------------------------------- + template + typename t_obj_container::value_type extract_random_from_container(t_obj_container& container) + { + auto it = container.begin(); + std::advance(it, (crypto::rand() % container.size())); + typename t_obj_container::value_type obj = *it; + container.erase(it); + return obj; + } + //---------------------------------------------------------------------------------------------------------------- + void wallet2::select_decoys(currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& amount_entry, uint64_t own_g_index) + { + THROW_IF_FALSE_WALLET_INT_ERR_EX(amount_entry.amount == 0, "Amount is not 0 in zc decoys entry"); + typedef currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry out_entry; + + //TODO: This strategy would be a subject for continuous refactoring + + //first take all real transactions if ther are some + std::list local_outs; + std::list coinbases; + + while (amount_entry.outs.size() && local_outs.size() != m_core_runtime_config.hf4_minimum_mixins) + { + out_entry entry = extract_random_from_container(amount_entry.outs); + + // + if (entry.global_amount_index == own_g_index) + { + continue; + } + + //skip auditable + if ((entry.flags & (RANDOM_OUTPUTS_FOR_AMOUNTS_FLAGS_NOT_ALLOWED))) + { + continue; + } + if (entry.flags & (RANDOM_OUTPUTS_FOR_AMOUNTS_FLAGS_COINBASE)) + { + coinbases.push_back(entry); + continue; + } + + + local_outs.push_back(entry); + } + + //extend with coin base outs if needed + while (coinbases.size() && local_outs.size() != m_core_runtime_config.hf4_minimum_mixins) + { + out_entry entry = extract_random_from_container(coinbases); + local_outs.push_back(entry); + } + + THROW_IF_FALSE_WALLET_INT_ERR_EX(local_outs.size() == m_core_runtime_config.hf4_minimum_mixins, "Amount is not 0 in zc decoys entry"); + amount_entry.outs = local_outs; + } + //---------------------------------------------------------------------------------------------------------------- + void wallet2::build_distribution_for_input(std::vector& offsets, uint64_t own_index) + { + decoy_selection_generator zarcanum_decoy_set_generator; + zarcanum_decoy_set_generator.init(get_actual_zc_global_index()); + + THROW_IF_FALSE_WALLET_INT_ERR_EX(zarcanum_decoy_set_generator.is_initialized(), "zarcanum_decoy_set_generator are not initialized"); + if (m_core_runtime_config.hf4_minimum_mixins) + { + uint64_t actual_zc_index = get_actual_zc_global_index(); + offsets = zarcanum_decoy_set_generator.generate_unique_reversed_distribution(actual_zc_index - 1 > WALLET_FETCH_RANDOM_OUTS_SIZE ? WALLET_FETCH_RANDOM_OUTS_SIZE : actual_zc_index - 1, own_index); + } + } + //---------------------------------------------------------------------------------------------------------------- + bool wallet2::prepare_tx_sources(crypto::hash multisig_id, std::vector& sources, uint64_t& found_money) + { + 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: " + epee::string_tools::pod_to_hex(multisig_id)); + THROW_IF_FALSE_WALLET_INT_ERR_EX(!it->second.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)); + + 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()"); + //@#@ + THROW_IF_FALSE_WALLET_INT_ERR_EX(it->second.m_ptx_wallet_info->m_tx.vout[it->second.m_internal_output_index].type() == typeid(tx_out_bare), "Unknown type id in prepare_tx_sources: " << it->second.m_ptx_wallet_info->m_tx.vout[it->second.m_internal_output_index].type().name()); + const tx_out_bare& out = boost::get(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(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); + src.multisig_id = multisig_id; + src.ms_sigs_count = ms_out.minimum_sigs; + src.ms_keys_count = ms_out.keys.size(); + return true; + } + //---------------------------------------------------------------------------------------------------------------- + bool wallet2::prepare_tx_sources_htlc(crypto::hash htlc_tx_id, const std::string& origin, std::vector& sources, uint64_t& found_money) + { + typedef currency::tx_source_entry::output_entry tx_output_entry; + //lets figure out, if we have active htlc for this htlc + auto it = m_active_htlcs_txid.find(htlc_tx_id); + if (it == m_active_htlcs_txid.end()) + { + WLT_THROW_IF_FALSE_WITH_CODE(false, + "htlc not found with tx_id = " << htlc_tx_id, API_RETURN_CODE_NOT_FOUND); + } + + //WLT_THROW_IF_FALSE_WITH_CODE(m_transfers.size() > it->second, + // "Internal error: index in m_active_htlcs_txid <" << it->second << "> is bigger then size of m_transfers <" << m_transfers.size() << ">", API_RETURN_CODE_INTERNAL_ERROR); + + const transfer_details& td = m_transfers.at(it->second); + //@#@ + WLT_THROW_IF_FALSE_WITH_CODE(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].type() == typeid(tx_out_bare), + "Unexpected out type in prepare_tx_sources_htlc:" << td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].type().name(), API_RETURN_CODE_INTERNAL_ERROR); + + const tx_out_bare& out_bare = boost::get(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]); + WLT_THROW_IF_FALSE_WITH_CODE(out_bare.target.type() == typeid(txout_htlc), + "Unexpected type in active htlc", API_RETURN_CODE_INTERNAL_ERROR); + + const txout_htlc& htlc_out = boost::get(out_bare.target); + bool use_sha256 = !(htlc_out.flags & CURRENCY_TXOUT_HTLC_FLAGS_HASH_TYPE_MASK); + + //check origin + WLT_THROW_IF_FALSE_WITH_CODE(origin.size() != 0, + "Origin for htlc is empty", API_RETURN_CODE_BAD_ARG); + + crypto::hash htlc_calculated_hash = currency::null_hash; + if (use_sha256) + { + htlc_calculated_hash = crypto::sha256_hash(origin.data(), origin.size()); + } + else + { + htlc_calculated_hash = crypto::RIPEMD160_hash_256(origin.data(), origin.size()); + } + WLT_THROW_IF_FALSE_WITH_CODE(htlc_calculated_hash == htlc_out.htlc_hash, + "Origin hash is missmatched with txout_htlc", API_RETURN_CODE_HTLC_ORIGIN_HASH_MISSMATCHED); + + sources.push_back(AUTO_VAL_INIT(currency::tx_source_entry())); + currency::tx_source_entry& src = sources.back(); + tx_output_entry real_oe = AUTO_VAL_INIT(real_oe); + real_oe.out_reference = td.m_global_output_index; // TODO: use ref_by_id when necessary + real_oe.stealth_address = htlc_out.pkey_redeem; + src.outputs.push_back(real_oe); //m_global_output_index should be prefetched + src.amount = found_money = td.amount(); src.real_output_in_tx_index = td.m_internal_output_index; - - if (epee::log_space::get_set_log_detalisation_level() >= LOG_LEVEL_1) + src.real_output = 0;//no mixins supposed to be in htlc + src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_ptx_wallet_info->m_tx); + src.htlc_origin = origin; + return true; + + } + //---------------------------------------------------------------------------------------------------------------- + assets_selection_context wallet2::get_needed_money(uint64_t fee, const std::vector& dsts) + { + assets_selection_context amounts_map; + amounts_map[currency::native_coin_asset_id].needed_amount = fee; + for (auto& dt : dsts) { - std::stringstream ss; - ss << "source entry [" << i << "], td_idx: " << J << ", "; - print_source_entry(ss, src); - WLT_LOG_L1(ss.str()); + if (dt.asset_id == currency::null_pkey) + continue; //this destination for emmition only + + THROW_IF_TRUE_WALLET_EX(0 == dt.amount, error::zero_destination); + uint64_t money_to_add = dt.amount; + if (dt.amount_to_provide || dt.flags & tx_destination_entry_flags::tdef_explicit_amount_to_provide) + money_to_add = dt.amount_to_provide; + + amounts_map[dt.asset_id].needed_amount += money_to_add; + THROW_IF_TRUE_WALLET_EX(amounts_map[dt.asset_id].needed_amount < money_to_add, error::tx_sum_overflow, dsts, fee); + //clean up empty entries + if (amounts_map[dt.asset_id].needed_amount == 0) + { + amounts_map.erase(amounts_map.find(dt.asset_id)); + } } - - ++i; + return amounts_map; } - return true; -} - - -//---------------------------------------------------------------------------------------------------------------- -template -typename t_obj_container::value_type extract_random_from_container(t_obj_container& container) -{ - auto it = container.begin(); - std::advance(it, (crypto::rand() % container.size())); - typename t_obj_container::value_type obj = *it; - container.erase(it); - return obj; -} -//---------------------------------------------------------------------------------------------------------------- -void wallet2::select_decoys(currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& amount_entry, uint64_t own_g_index) -{ - THROW_IF_FALSE_WALLET_INT_ERR_EX(amount_entry.amount == 0, "Amount is not 0 in zc decoys entry"); - typedef currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry out_entry; - - //TODO: This strategy would be a subject for continuous refactoring - - //first take all real transactions if ther are some - std::list local_outs; - std::list coinbases; - - while (amount_entry.outs.size() && local_outs.size() != m_core_runtime_config.hf4_minimum_mixins) + //---------------------------------------------------------------------------------------------------------------- + void wallet2::set_disable_tor_relay(bool disable) { - out_entry entry = extract_random_from_container(amount_entry.outs); - - // - if (entry.global_amount_index == own_g_index) - { - continue; - } - - //skip auditable - if ((entry.flags & (RANDOM_OUTPUTS_FOR_AMOUNTS_FLAGS_NOT_ALLOWED))) - { - continue; - } - if (entry.flags & (RANDOM_OUTPUTS_FOR_AMOUNTS_FLAGS_COINBASE)) - { - coinbases.push_back(entry); - continue; - } - - - local_outs.push_back(entry); + m_disable_tor_relay = disable; } - - //extend with coin base outs if needed - while (coinbases.size() && local_outs.size() != m_core_runtime_config.hf4_minimum_mixins) + //---------------------------------------------------------------------------------------------------------------- + void wallet2::notify_state_change(const std::string& state_code, const std::string& details) { - out_entry entry = extract_random_from_container(coinbases); - local_outs.push_back(entry); + m_wcallback->on_tor_status_change(state_code); } - - THROW_IF_FALSE_WALLET_INT_ERR_EX(local_outs.size() == m_core_runtime_config.hf4_minimum_mixins, "Amount is not 0 in zc decoys entry"); - amount_entry.outs = local_outs; -} -//---------------------------------------------------------------------------------------------------------------- -void wallet2::build_distribution_for_input(std::vector& offsets, uint64_t own_index) -{ - decoy_selection_generator zarcanum_decoy_set_generator; - zarcanum_decoy_set_generator.init(get_actual_zc_global_index()); - - THROW_IF_FALSE_WALLET_INT_ERR_EX(zarcanum_decoy_set_generator.is_initialized(), "zarcanum_decoy_set_generator are not initialized"); - if (m_core_runtime_config.hf4_minimum_mixins) + //---------------------------------------------------------------------------------------------------------------- + void wallet2::send_transaction_to_network(const transaction& tx) { - uint64_t actual_zc_index = get_actual_zc_global_index(); - offsets = zarcanum_decoy_set_generator.generate_unique_reversed_distribution(actual_zc_index - 1 > WALLET_FETCH_RANDOM_OUTS_SIZE ? WALLET_FETCH_RANDOM_OUTS_SIZE : actual_zc_index - 1, own_index); - } -} -//---------------------------------------------------------------------------------------------------------------- -bool wallet2::prepare_tx_sources(crypto::hash multisig_id, std::vector& sources, uint64_t& found_money) -{ - 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: " + epee::string_tools::pod_to_hex(multisig_id)); - THROW_IF_FALSE_WALLET_INT_ERR_EX(!it->second.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)); - - 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()"); - //@#@ - THROW_IF_FALSE_WALLET_INT_ERR_EX(it->second.m_ptx_wallet_info->m_tx.vout[it->second.m_internal_output_index].type() == typeid(tx_out_bare), "Unknown type id in prepare_tx_sources: " << it->second.m_ptx_wallet_info->m_tx.vout[it->second.m_internal_output_index].type().name()); - const tx_out_bare& out = boost::get(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(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); - src.multisig_id = multisig_id; - src.ms_sigs_count = ms_out.minimum_sigs; - src.ms_keys_count = ms_out.keys.size(); - return true; -} -//---------------------------------------------------------------------------------------------------------------- -bool wallet2::prepare_tx_sources_htlc(crypto::hash htlc_tx_id, const std::string& origin, std::vector& sources, uint64_t& found_money) -{ - typedef currency::tx_source_entry::output_entry tx_output_entry; - //lets figure out, if we have active htlc for this htlc - auto it = m_active_htlcs_txid.find(htlc_tx_id); - if (it == m_active_htlcs_txid.end()) - { - WLT_THROW_IF_FALSE_WITH_CODE(false, - "htlc not found with tx_id = " << htlc_tx_id, API_RETURN_CODE_NOT_FOUND); - } - - WLT_THROW_IF_FALSE_WITH_CODE(m_transfers.size() > it->second, - "Internal error: index in m_active_htlcs_txid <" << it->second << "> is bigger then size of m_transfers <" << m_transfers.size() << ">", API_RETURN_CODE_INTERNAL_ERROR); - - const transfer_details& td = m_transfers[it->second]; - //@#@ - WLT_THROW_IF_FALSE_WITH_CODE(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].type() == typeid(tx_out_bare), - "Unexpected out type in prepare_tx_sources_htlc:" << td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].type().name(), API_RETURN_CODE_INTERNAL_ERROR); - - const tx_out_bare& out_bare = boost::get(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]); - WLT_THROW_IF_FALSE_WITH_CODE(out_bare.target.type() == typeid(txout_htlc), - "Unexpected type in active htlc", API_RETURN_CODE_INTERNAL_ERROR); - - const txout_htlc& htlc_out = boost::get(out_bare.target); - bool use_sha256 = !(htlc_out.flags&CURRENCY_TXOUT_HTLC_FLAGS_HASH_TYPE_MASK); - - //check origin - WLT_THROW_IF_FALSE_WITH_CODE(origin.size() != 0, - "Origin for htlc is empty", API_RETURN_CODE_BAD_ARG); - - crypto::hash htlc_calculated_hash = currency::null_hash; - if (use_sha256) - { - htlc_calculated_hash = crypto::sha256_hash(origin.data(), origin.size()); - } - else - { - htlc_calculated_hash = crypto::RIPEMD160_hash_256(origin.data(), origin.size()); - } - WLT_THROW_IF_FALSE_WITH_CODE(htlc_calculated_hash == htlc_out.htlc_hash, - "Origin hash is missmatched with txout_htlc", API_RETURN_CODE_HTLC_ORIGIN_HASH_MISSMATCHED); - - sources.push_back(AUTO_VAL_INIT(currency::tx_source_entry())); - currency::tx_source_entry& src = sources.back(); - tx_output_entry real_oe = AUTO_VAL_INIT(real_oe); - real_oe.out_reference = td.m_global_output_index; // TODO: use ref_by_id when necessary - real_oe.stealth_address = htlc_out.pkey_redeem; - src.outputs.push_back(real_oe); //m_global_output_index should be prefetched - src.amount = found_money = td.amount(); - src.real_output_in_tx_index = td.m_internal_output_index; - src.real_output = 0;//no mixins supposed to be in htlc - src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_ptx_wallet_info->m_tx); - src.htlc_origin = origin; - return true; - -} -//---------------------------------------------------------------------------------------------------------------- -assets_selection_context wallet2::get_needed_money(uint64_t fee, const std::vector& dsts) -{ - assets_selection_context amounts_map; - amounts_map[currency::native_coin_asset_id].needed_amount = fee; - for(auto& dt : dsts) - { - if(dt.asset_id == currency::null_pkey) - continue; //this destination for emmition only - - THROW_IF_TRUE_WALLET_EX(0 == dt.amount, error::zero_destination); - uint64_t money_to_add = dt.amount; - if (dt.amount_to_provide || dt.flags & tx_destination_entry_flags::tdef_explicit_amount_to_provide) - money_to_add = dt.amount_to_provide; - - amounts_map[dt.asset_id].needed_amount += money_to_add; - THROW_IF_TRUE_WALLET_EX(amounts_map[dt.asset_id].needed_amount < money_to_add, error::tx_sum_overflow, dsts, fee); - //clean up empty entries - if (amounts_map[dt.asset_id].needed_amount == 0) - { - amounts_map.erase(amounts_map.find(dt.asset_id)); - } - } - return amounts_map; -} -//---------------------------------------------------------------------------------------------------------------- -void wallet2::set_disable_tor_relay(bool disable) -{ - m_disable_tor_relay = disable; -} -//---------------------------------------------------------------------------------------------------------------- -void wallet2::notify_state_change(const std::string& state_code, const std::string& details) -{ - m_wcallback->on_tor_status_change(state_code); -} -//---------------------------------------------------------------------------------------------------------------- -void wallet2::send_transaction_to_network(const transaction& tx) -{ #ifndef DISABLE_TOR - if (!m_disable_tor_relay) - { - //TODO check that core synchronized - //epee::net_utils::levin_client2 p2p_client; - - //make few attempts - tools::levin_over_tor_client p2p_client; - p2p_client.get_transport().set_notifier(this); - bool succeseful_sent = false; - for (size_t i = 0; i != 3; i++) + if (!m_disable_tor_relay) { - if (!p2p_client.connect("144.76.183.143", 2121, 10000)) + //TODO check that core synchronized + //epee::net_utils::levin_client2 p2p_client; + + //make few attempts + tools::levin_over_tor_client p2p_client; + p2p_client.get_transport().set_notifier(this); + bool succeseful_sent = false; + for (size_t i = 0; i != 3; i++) { - continue;//THROW_IF_FALSE_WALLET_EX(false, error::no_connection_to_daemon, "Failed to connect to TOR node"); - } + if (!p2p_client.connect("144.76.183.143", 2121, 10000)) + { + continue;//THROW_IF_FALSE_WALLET_EX(false, error::no_connection_to_daemon, "Failed to connect to TOR node"); + } - currency::NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request p2p_req = AUTO_VAL_INIT(p2p_req); - currency::NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::response p2p_rsp = AUTO_VAL_INIT(p2p_rsp); - p2p_req.txs.push_back(t_serializable_object_to_blob(tx)); - this->notify_state_change(WALLET_LIB_STATE_SENDING); - epee::net_utils::invoke_remote_command2(NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::ID, p2p_req, p2p_rsp, p2p_client); - p2p_client.disconnect(); - if (p2p_rsp.code == API_RETURN_CODE_OK) - { - this->notify_state_change(WALLET_LIB_SENT_SUCCESS); - succeseful_sent = true; - break; + currency::NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request p2p_req = AUTO_VAL_INIT(p2p_req); + currency::NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::response p2p_rsp = AUTO_VAL_INIT(p2p_rsp); + p2p_req.txs.push_back(t_serializable_object_to_blob(tx)); + this->notify_state_change(WALLET_LIB_STATE_SENDING); + epee::net_utils::invoke_remote_command2(NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::ID, p2p_req, p2p_rsp, p2p_client); + p2p_client.disconnect(); + if (p2p_rsp.code == API_RETURN_CODE_OK) + { + this->notify_state_change(WALLET_LIB_SENT_SUCCESS); + succeseful_sent = true; + break; + } + this->notify_state_change(WALLET_LIB_SEND_FAILED); + //checking if transaction got relayed to other nodes and + //return; + } + if (!succeseful_sent) + { + this->notify_state_change(WALLET_LIB_SEND_FAILED); + THROW_IF_FALSE_WALLET_EX(succeseful_sent, error::no_connection_to_daemon, "Faile to build TOR stream"); } - this->notify_state_change(WALLET_LIB_SEND_FAILED); - //checking if transaction got relayed to other nodes and - //return; } - if (!succeseful_sent) - { - this->notify_state_change(WALLET_LIB_SEND_FAILED); - THROW_IF_FALSE_WALLET_EX(succeseful_sent, error::no_connection_to_daemon, "Faile to build TOR stream"); - } - } - else + else #endif // - { - COMMAND_RPC_SEND_RAW_TX::request req; - req.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(tx)); - COMMAND_RPC_SEND_RAW_TX::response daemon_send_resp; - bool r = m_core_proxy->call_COMMAND_RPC_SEND_RAW_TX(req, daemon_send_resp); - THROW_IF_TRUE_WALLET_EX(!r, error::no_connection_to_daemon, "sendrawtransaction"); - THROW_IF_TRUE_WALLET_EX(daemon_send_resp.status == API_RETURN_CODE_BUSY, error::daemon_busy, "sendrawtransaction"); - THROW_IF_TRUE_WALLET_EX(daemon_send_resp.status == API_RETURN_CODE_DISCONNECTED, error::no_connection_to_daemon, "Transfer attempt while daemon offline"); - THROW_IF_TRUE_WALLET_EX(daemon_send_resp.status != API_RETURN_CODE_OK, error::tx_rejected, tx, daemon_send_resp.status); - - WLT_LOG_L2("transaction " << get_transaction_hash(tx) << " generated ok and sent to daemon:" << ENDL << currency::obj_to_json_str(tx)); - } - -} -//---------------------------------------------------------------------------------------------------------------- -void wallet2::add_sent_tx_detailed_info(const transaction& tx, const std::vector& decrypted_att, - const std::vector& destinations, - const std::vector& selected_transfers) -{ - payment_id_t payment_id; - get_payment_id_from_decrypted_container(decrypted_att, payment_id); - - std::vector recipients; - std::unordered_set used_addresses; - for (const auto& d : destinations) - { - for (const auto& addr : d.addr) { - if (used_addresses.insert(addr).second && addr != m_account.get_public_address()) - recipients.push_back(payment_id.empty() ? get_account_address_as_str(addr) : get_account_address_and_payment_id_as_str(addr, payment_id)); + COMMAND_RPC_SEND_RAW_TX::request req; + req.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(tx)); + COMMAND_RPC_SEND_RAW_TX::response daemon_send_resp; + bool r = m_core_proxy->call_COMMAND_RPC_SEND_RAW_TX(req, daemon_send_resp); + THROW_IF_TRUE_WALLET_EX(!r, error::no_connection_to_daemon, "sendrawtransaction"); + THROW_IF_TRUE_WALLET_EX(daemon_send_resp.status == API_RETURN_CODE_BUSY, error::daemon_busy, "sendrawtransaction"); + THROW_IF_TRUE_WALLET_EX(daemon_send_resp.status == API_RETURN_CODE_DISCONNECTED, error::no_connection_to_daemon, "Transfer attempt while daemon offline"); + THROW_IF_TRUE_WALLET_EX(daemon_send_resp.status != API_RETURN_CODE_OK, error::tx_rejected, tx, daemon_send_resp.status); + + WLT_LOG_L2("transaction " << get_transaction_hash(tx) << " generated ok and sent to daemon:" << ENDL << currency::obj_to_json_str(tx)); + } + + } + //---------------------------------------------------------------------------------------------------------------- + void wallet2::add_sent_tx_detailed_info(const transaction& tx, const std::vector& decrypted_att, + const std::vector& destinations, + const std::vector& selected_transfers) + { + payment_id_t payment_id; + get_payment_id_from_decrypted_container(decrypted_att, payment_id); + + std::vector recipients; + std::unordered_set used_addresses; + for (const auto& d : destinations) + { + for (const auto& addr : d.addr) + { + if (used_addresses.insert(addr).second && addr != m_account.get_public_address()) + recipients.push_back(payment_id.empty() ? get_account_address_as_str(addr) : get_account_address_and_payment_id_as_str(addr, payment_id)); + } + } + if (!recipients.size()) + { + //transaction send to ourself + recipients.push_back(payment_id.empty() ? get_account_address_as_str(m_account.get_public_address()) : get_account_address_and_payment_id_as_str(m_account.get_public_address(), payment_id)); + } + + add_sent_unconfirmed_tx(tx, recipients, selected_transfers, destinations); + } + //---------------------------------------------------------------------------------------------------- + void wallet2::mark_transfers_with_flag(const std::vector& selected_transfers, uint32_t flag, const std::string& reason /* = empty_string */, bool throw_if_flag_already_set /* = false */) + { + // check all selected transfers prior to flag change + for (uint64_t i : selected_transfers) + { + //WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(i < m_transfers.size(), "invalid transfer index given: " << i << ", m_transfers.size() == " << m_transfers.size()); + if (throw_if_flag_already_set) + { + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX((m_transfers.at(i).m_flags & flag) == 0, "transfer #" << i << " already has flag " << flag << ": " << m_transfers.at(i).m_flags << ", transfer info:" << ENDL << epee::serialization::store_t_to_json(m_transfers.at(i))); + } + } + + for (uint64_t i : selected_transfers) + { + uint32_t flags_before = m_transfers.at(i).m_flags; + m_transfers.at(i).m_flags |= flag; + WLT_LOG_L1("marking transfer #" << std::setfill('0') << std::right << std::setw(3) << i << " with flag " << flag << " : " << flags_before << " -> " << m_transfers.at(i).m_flags << + (reason.empty() ? "" : ", reason: ") << reason); } } - if (!recipients.size()) + //---------------------------------------------------------------------------------------------------- + void wallet2::clear_transfers_from_flag(const std::vector& selected_transfers, uint32_t flag, const std::string& reason /* = empty_string */) noexcept { - //transaction send to ourself - recipients.push_back(payment_id.empty() ? get_account_address_as_str(m_account.get_public_address()) : get_account_address_and_payment_id_as_str(m_account.get_public_address(), payment_id)); - } - - add_sent_unconfirmed_tx(tx, recipients, selected_transfers, destinations); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::mark_transfers_with_flag(const std::vector& selected_transfers, uint32_t flag, const std::string& reason /* = empty_string */, bool throw_if_flag_already_set /* = false */) -{ - // check all selected transfers prior to flag change - for (uint64_t i : selected_transfers) - { - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(i < m_transfers.size(), "invalid transfer index given: " << i << ", m_transfers.size() == " << m_transfers.size()); - if (throw_if_flag_already_set) + TRY_ENTRY(); + for (uint64_t i : selected_transfers) { - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX((m_transfers[i].m_flags & flag) == 0, "transfer #" << i << " already has flag " << flag << ": " << m_transfers[i].m_flags << ", transfer info:" << ENDL << epee::serialization::store_t_to_json(m_transfers[i])); + //if (i >= m_transfers.size()) + //{ + // WLT_LOG_ERROR("INTERNAL ERROR: i: " << i << " >= m_transfers.size() : " << m_transfers.size()); + // continue; + //} + auto& tr_entry = m_transfers.at(i); + uint32_t flags_before = tr_entry.m_flags; + tr_entry.m_flags &= ~flag; + WLT_LOG_L1("clearing transfer #" << std::setfill('0') << std::right << std::setw(3) << i << " from flag " << flag << " : " << flags_before << " -> " << tr_entry.m_flags << + (reason.empty() ? "" : ", reason: ") << reason); } + CATCH_ENTRY_NO_RETURN(); } - - for (uint64_t i : selected_transfers) + //---------------------------------------------------------------------------------------------------- + void wallet2::exception_handler() { - uint32_t flags_before = m_transfers[i].m_flags; - m_transfers[i].m_flags |= flag; - WLT_LOG_L1("marking transfer #" << std::setfill('0') << std::right << std::setw(3) << i << " with flag " << flag << " : " << flags_before << " -> " << m_transfers[i].m_flags << - (reason.empty() ? "" : ", reason: ") << reason); + m_found_free_amounts.clear(); } -} -//---------------------------------------------------------------------------------------------------- -void wallet2::clear_transfers_from_flag(const std::vector& selected_transfers, uint32_t flag, const std::string& reason /* = empty_string */) noexcept -{ - TRY_ENTRY(); - for (uint64_t i : selected_transfers) + //---------------------------------------------------------------------------------------------------- + void wallet2::exception_handler() const { - if (i >= m_transfers.size()) + // do nothing + // TODO: is it correct? + } + //---------------------------------------------------------------------------------------------------- + void wallet2::mark_transfers_as_spent(const std::vector& selected_transfers, const std::string& reason /* = empty_string */) + { + // TODO: design a safe undo for this operation + mark_transfers_with_flag(selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, reason); + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::extract_offers_from_transfer_entry(size_t i, std::unordered_map& offers_local) + { + //TODO: this code supports only one market(offer) instruction per transaction + load_wallet_transfer_info_flags(m_transfer_history[i]); + switch (m_transfer_history[i].tx_type) { - WLT_LOG_ERROR("INTERNAL ERROR: i: " << i << " >= m_transfers.size() : " << m_transfers.size()); - continue; - } - uint32_t flags_before = m_transfers[i].m_flags; - m_transfers[i].m_flags &= ~flag; - WLT_LOG_L1("clearing transfer #" << std::setfill('0') << std::right << std::setw(3) << i << " from flag " << flag << " : " << flags_before << " -> " << m_transfers[i].m_flags << - (reason.empty() ? "" : ", reason: ") << reason); - } - CATCH_ENTRY_NO_RETURN(); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::exception_handler() -{ - m_found_free_amounts.clear(); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::exception_handler() const -{ - // do nothing - // TODO: is it correct? -} -//---------------------------------------------------------------------------------------------------- -void wallet2::mark_transfers_as_spent(const std::vector& selected_transfers, const std::string& reason /* = empty_string */) -{ - // TODO: design a safe undo for this operation - mark_transfers_with_flag(selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, reason); -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::extract_offers_from_transfer_entry(size_t i, std::unordered_map& offers_local) -{ - //TODO: this code supports only one market(offer) instruction per transaction - load_wallet_transfer_info_flags(m_transfer_history[i]); - switch (m_transfer_history[i].tx_type) - { case GUI_TX_TYPE_PUSH_OFFER: { bc_services::offer_details od; @@ -7026,7 +7078,7 @@ bool wallet2::extract_offers_from_transfer_entry(size_t i, std::unordered_map& offers) -{ - std::unordered_map offers_local; - - if (!m_transfer_history.size()) - return true; - - uint64_t stop_timestamp = m_core_runtime_config.get_core_time() - OFFER_MAXIMUM_LIFE_TIME; - - size_t i = m_transfer_history.size() - 1; - for (; i != 0; i--) - { - if (m_transfer_history[i].timestamp < stop_timestamp) - { - i++; - break; } - } - if (i == 0 && m_transfer_history[0].timestamp < stop_timestamp) - i++; - if (i >= m_transfer_history.size()) + return true; - - for (; i != m_transfer_history.size(); i++) - { - extract_offers_from_transfer_entry(i, offers_local); } - for (const auto& o : offers_local) - offers.push_back(o.second); - - return true; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::get_actual_offers(std::list& offers) -{ - select_my_offers(offers); - return true; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::expand_selection_with_zc_input(assets_selection_context& needed_money_map, uint64_t fake_outputs_count, std::vector& selected_indexes) -{ - free_amounts_cache_type& found_free_amounts = m_found_free_amounts[currency::native_coin_asset_id]; - auto& asset_needed_money_item = needed_money_map[currency::native_coin_asset_id]; - //need to add ZC input - for (auto it = found_free_amounts.begin(); it != found_free_amounts.end(); it++) + //---------------------------------------------------------------------------------------------------- + bool wallet2::select_my_offers(std::list& offers) { - for (auto it_in_amount = it->second.begin(); it_in_amount != it->second.end(); it_in_amount++) + std::unordered_map offers_local; + + if (!m_transfer_history.size()) + return true; + + uint64_t stop_timestamp = m_core_runtime_config.get_core_time() - OFFER_MAXIMUM_LIFE_TIME; + + size_t i = m_transfer_history.size() - 1; + for (; i != 0; i--) { - if (!m_transfers[*it_in_amount].is_zc()) + if (m_transfer_history[i].timestamp < stop_timestamp) { - continue; - } - - if (is_transfer_ready_to_go(m_transfers[*it->second.begin()], fake_outputs_count)) - { - asset_needed_money_item.found_amount += it->first; - selected_indexes.push_back(*it_in_amount); - it->second.erase(it_in_amount); - if (!it->second.size()) - { - found_free_amounts.erase(it); - } - return true; - } - } - } - WLT_THROW_IF_FALSE_WALLET_EX_MES(false, error::no_zc_inputs, "At least one ZC is required for the operation, but none were found"); - return true; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::select_indices_for_transfer(assets_selection_context& needed_money_map, uint64_t fake_outputs_count, std::vector& selected_indexes) -{ - for (auto& item : needed_money_map) - { - if(item.second.needed_amount == 0) - continue; - - const crypto::public_key asset_id = item.first; - asset_descriptor_base asset_info{}; - uint32_t asset_flags = 0; - if (!get_asset_info(asset_id, asset_info, asset_flags)) - WLT_LOG_L1("select_indices_for_transfer: unknown asset id: " << asset_id); - - auto asset_cache_it = m_found_free_amounts.find(asset_id); - THROW_IF_FALSE_WALLET_EX(asset_cache_it != m_found_free_amounts.end(), error::not_enough_money, item.second.found_amount, item.second.needed_amount, (uint64_t)0, asset_id, asset_info.decimal_point); - item.second.found_amount = select_indices_for_transfer(selected_indexes, asset_cache_it->second, item.second.needed_amount, fake_outputs_count, asset_id, asset_info.decimal_point); - THROW_IF_FALSE_WALLET_EX(item.second.found_amount >= item.second.needed_amount, error::not_enough_money, item.second.found_amount, item.second.needed_amount, (uint64_t)0, asset_id, asset_info.decimal_point); - } - if (m_current_context.pconstruct_tx_param && m_current_context.pconstruct_tx_param->need_at_least_1_zc) - { - bool found_zc_input = false; - for (auto i : selected_indexes) - { - if (m_transfers[i].is_zc()) - { - found_zc_input = true; + i++; break; } } - if (!found_zc_input) - { - expand_selection_with_zc_input(needed_money_map, fake_outputs_count, selected_indexes); - } - } + if (i == 0 && m_transfer_history[0].timestamp < stop_timestamp) + i++; + if (i >= m_transfer_history.size()) + return true; - return true; -} -//---------------------------------------------------------------------------------------------------- -uint64_t wallet2::select_indices_for_transfer(std::vector& selected_indexes, free_amounts_cache_type& found_free_amounts, uint64_t needed_money, uint64_t fake_outputs_count_, - const crypto::public_key& asset_id, size_t decimal_point) -{ - WLT_LOG_GREEN("Selecting indices for transfer of " << print_money_brief(needed_money, decimal_point) << " with " << fake_outputs_count_ << " fake outs, found_free_amounts.size()=" << found_free_amounts.size() << - (asset_id == native_coin_asset_id ? std::string() : std::string(", asset_id: ") + crypto::pod_to_hex(asset_id)) << "...", LOG_LEVEL_0); - uint64_t found_money = 0; - size_t outputs_found = 0; - std::string selected_amounts_str; - while (found_money < needed_money && found_free_amounts.size()) - { - auto it = found_free_amounts.lower_bound(needed_money - found_money); - if (!(it != found_free_amounts.end() && it->second.size())) + for (; i != m_transfer_history.size(); i++) { - it = --found_free_amounts.end(); - WLT_CHECK_AND_ASSERT_MES(it->second.size(), 0, "internal error: empty found_free_amounts map"); + extract_offers_from_transfer_entry(i, offers_local); } - uint64_t fake_outputs_count = fake_outputs_count_; - if (!this->is_auditable() && m_transfers[*it->second.begin()].is_zc()) - { - fake_outputs_count = m_core_runtime_config.hf4_minimum_mixins; - } - if (is_transfer_ready_to_go(m_transfers[*it->second.begin()], fake_outputs_count)) - { - found_money += it->first; - selected_indexes.push_back(*it->second.begin()); - WLT_LOG_L2("Selected index: " << *it->second.begin() << ", transfer_details: " << ENDL << epee::serialization::store_t_to_json(m_transfers[*it->second.begin()])); - selected_amounts_str += (selected_amounts_str.empty() ? "" : "+") + print_money_brief(it->first, decimal_point); - ++outputs_found; - } - it->second.erase(it->second.begin()); - if (!it->second.size()) - found_free_amounts.erase(it); + for (const auto& o : offers_local) + offers.push_back(o.second); - } - - WLT_LOG_GREEN("Found " << print_money_brief(found_money, decimal_point) << " as " << outputs_found << " out(s): " << selected_amounts_str << ", found_free_amounts.size()=" << found_free_amounts.size() << - (asset_id == native_coin_asset_id ? std::string() : std::string(", asset_id: ") + crypto::pod_to_hex(asset_id)), LOG_LEVEL_0); - return found_money; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::is_transfer_ready_to_go(const transfer_details& td, uint64_t fake_outputs_count) const -{ - if (is_transfer_able_to_go(td, fake_outputs_count) && is_transfer_unlocked(td)) - { return true; } - return false; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::is_transfer_able_to_go(const transfer_details& td, uint64_t fake_outputs_count) const -{ - if (!td.is_spendable()) - return false; - - const tx_out_v &out_v = td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]; - - uint8_t mix_attr = CURRENCY_TO_KEY_OUT_RELAXED; - if (get_mix_attr_from_tx_out_v(out_v, mix_attr)) + //---------------------------------------------------------------------------------------------------- + bool wallet2::get_actual_offers(std::list& offers) { - if (!currency::is_mixattr_applicable_for_fake_outs_counter(td.m_ptx_wallet_info->m_tx.version, mix_attr, fake_outputs_count, m_core_runtime_config)) - return false; + select_my_offers(offers); + return true; } + //---------------------------------------------------------------------------------------------------- + bool wallet2::expand_selection_with_zc_input(assets_selection_context& needed_money_map, uint64_t fake_outputs_count, std::vector& selected_indexes) + { + free_amounts_cache_type& found_free_amounts = m_found_free_amounts[currency::native_coin_asset_id]; + auto& asset_needed_money_item = needed_money_map[currency::native_coin_asset_id]; + //need to add ZC input + for (auto it = found_free_amounts.begin(); it != found_free_amounts.end(); it++) + { + for (auto it_in_amount = it->second.begin(); it_in_amount != it->second.end(); it_in_amount++) + { + if (!m_transfers.at(*it_in_amount).is_zc()) + { + continue; + } - VARIANT_SWITCH_BEGIN(out_v); - VARIANT_CASE_CONST(tx_out_bare, o); + if (is_transfer_ready_to_go(m_transfers.at(*it->second.begin()), fake_outputs_count)) + { + asset_needed_money_item.found_amount += it->first; + selected_indexes.push_back(*it_in_amount); + it->second.erase(it_in_amount); + if (!it->second.size()) + { + found_free_amounts.erase(it); + } + return true; + } + } + } + WLT_THROW_IF_FALSE_WALLET_EX_MES(false, error::no_zc_inputs, "At least one ZC is required for the operation, but none were found"); + return true; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::select_indices_for_transfer(assets_selection_context& needed_money_map, uint64_t fake_outputs_count, std::vector& selected_indexes) + { + for (auto& item : needed_money_map) + { + if (item.second.needed_amount == 0) + continue; + + const crypto::public_key asset_id = item.first; + asset_descriptor_base asset_info{}; + uint32_t asset_flags = 0; + if (!get_asset_info(asset_id, asset_info, asset_flags)) + WLT_LOG_L1("select_indices_for_transfer: unknown asset id: " << asset_id); + + auto asset_cache_it = m_found_free_amounts.find(asset_id); + THROW_IF_FALSE_WALLET_EX(asset_cache_it != m_found_free_amounts.end(), error::not_enough_money, item.second.found_amount, item.second.needed_amount, (uint64_t)0, asset_id, asset_info.decimal_point); + item.second.found_amount = select_indices_for_transfer(selected_indexes, asset_cache_it->second, item.second.needed_amount, fake_outputs_count, asset_id, asset_info.decimal_point); + THROW_IF_FALSE_WALLET_EX(item.second.found_amount >= item.second.needed_amount, error::not_enough_money, item.second.found_amount, item.second.needed_amount, (uint64_t)0, asset_id, asset_info.decimal_point); + } + if (m_current_context.pconstruct_tx_param && m_current_context.pconstruct_tx_param->need_at_least_1_zc) + { + bool found_zc_input = false; + for (auto i : selected_indexes) + { + if (m_transfers.at(i).is_zc()) + { + found_zc_input = true; + break; + } + } + if (!found_zc_input) + { + expand_selection_with_zc_input(needed_money_map, fake_outputs_count, selected_indexes); + } + } + + return true; + } + //---------------------------------------------------------------------------------------------------- + uint64_t wallet2::select_indices_for_transfer(std::vector& selected_indexes, free_amounts_cache_type& found_free_amounts, uint64_t needed_money, uint64_t fake_outputs_count_, + const crypto::public_key& asset_id, size_t decimal_point) + { + WLT_LOG_GREEN("Selecting indices for transfer of " << print_money_brief(needed_money, decimal_point) << " with " << fake_outputs_count_ << " fake outs, found_free_amounts.size()=" << found_free_amounts.size() << + (asset_id == native_coin_asset_id ? std::string() : std::string(", asset_id: ") + crypto::pod_to_hex(asset_id)) << "...", LOG_LEVEL_0); + uint64_t found_money = 0; + size_t outputs_found = 0; + std::string selected_amounts_str; + while (found_money < needed_money && found_free_amounts.size()) + { + auto it = found_free_amounts.lower_bound(needed_money - found_money); + if (!(it != found_free_amounts.end() && it->second.size())) + { + it = --found_free_amounts.end(); + WLT_CHECK_AND_ASSERT_MES(it->second.size(), 0, "internal error: empty found_free_amounts map"); + } + uint64_t fake_outputs_count = fake_outputs_count_; + if (!this->is_auditable() && m_transfers.at(*it->second.begin()).is_zc()) + { + fake_outputs_count = m_core_runtime_config.hf4_minimum_mixins; + } + if (is_transfer_ready_to_go(m_transfers.at(*it->second.begin()), fake_outputs_count)) + { + found_money += it->first; + selected_indexes.push_back(*it->second.begin()); + WLT_LOG_L2("Selected index: " << *it->second.begin() << ", transfer_details: " << ENDL << epee::serialization::store_t_to_json(m_transfers.at(*it->second.begin()))); + selected_amounts_str += (selected_amounts_str.empty() ? "" : "+") + print_money_brief(it->first, decimal_point); + ++outputs_found; + } + it->second.erase(it->second.begin()); + if (!it->second.size()) + found_free_amounts.erase(it); + + } + + WLT_LOG_GREEN("Found " << print_money_brief(found_money, decimal_point) << " as " << outputs_found << " out(s): " << selected_amounts_str << ", found_free_amounts.size()=" << found_free_amounts.size() << + (asset_id == native_coin_asset_id ? std::string() : std::string(", asset_id: ") + crypto::pod_to_hex(asset_id)), LOG_LEVEL_0); + return found_money; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::is_transfer_ready_to_go(const transfer_details& td, uint64_t fake_outputs_count) const + { + if (is_transfer_able_to_go(td, fake_outputs_count) && is_transfer_unlocked(td)) + { + return true; + } + return false; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::is_transfer_able_to_go(const transfer_details& td, uint64_t fake_outputs_count) const + { + if (!td.is_spendable()) + return false; + + const tx_out_v& out_v = td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]; + + uint8_t mix_attr = CURRENCY_TO_KEY_OUT_RELAXED; + if (get_mix_attr_from_tx_out_v(out_v, mix_attr)) + { + if (!currency::is_mixattr_applicable_for_fake_outs_counter(td.m_ptx_wallet_info->m_tx.version, mix_attr, fake_outputs_count, m_core_runtime_config)) + return false; + } + + VARIANT_SWITCH_BEGIN(out_v); + VARIANT_CASE_CONST(tx_out_bare, o); if (o.target.type() == typeid(txout_htlc)) { if (fake_outputs_count != 0) return false; } - VARIANT_SWITCH_END(); + VARIANT_SWITCH_END(); - return true; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::prepare_free_transfers_cache(uint64_t fake_outputs_count) -{ - WLT_LOG_L2("Preparing transfers_cache..."); - uint64_t count = 0; - if (!m_found_free_amounts.size() || fake_outputs_count != m_fake_outputs_count) - { - m_found_free_amounts.clear(); - for (size_t i = 0; i < m_transfers.size(); ++i) - { - const transfer_details& td = m_transfers[i]; - uint64_t fake_outputs_count_local = fake_outputs_count; - if (td.m_zc_info_ptr) - { - //zarcanum out, redefine fake_outputs_count - fake_outputs_count_local = this->is_auditable() ? 0 : m_core_runtime_config.hf4_minimum_mixins; - } - if (is_transfer_able_to_go(td, fake_outputs_count_local)) - { - //@#@ - m_found_free_amounts[td.get_asset_id()][td.amount()].insert(i); - count++; - } - } - m_fake_outputs_count = fake_outputs_count; + return true; } - - WLT_LOG_L2("Transfers_cache prepared. " << count << " items cached for " << m_found_free_amounts.size() << " amounts"); - return true; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::add_transfers_to_transfers_cache(const std::vector& indexs) -{ - //@#@ - for (auto i : indexs) - add_transfer_to_transfers_cache(m_transfers[i].amount(), i, m_transfers[i].get_asset_id()); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::add_transfer_to_transfers_cache(uint64_t amount, uint64_t index, const crypto::public_key& asset_id /* = currency::native_coin_asset_id */) -{ - m_found_free_amounts[asset_id][amount].insert(index); -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::select_transfers(assets_selection_context& needed_money_map, size_t fake_outputs_count, uint64_t /*dust_threshold*/, std::vector& selected_indicies) -{ - prepare_free_transfers_cache(fake_outputs_count); - return select_indices_for_transfer(needed_money_map, fake_outputs_count, selected_indicies); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::add_sent_unconfirmed_tx(const currency::transaction& tx, - const std::vector& recipients, - const std::vector& selected_indicies, - const std::vector& splitted_dsts) -{ - PROFILE_FUNC("wallet2::add_sent_unconfirmed_tx"); - process_transaction_context ptc(tx); - ptc.recipients = recipients; - for (auto addr : recipients) - ptc.remote_aliases.push_back(get_alias_for_address(addr)); - - handle_unconfirmed_tx(ptc); - wallet_public::wallet_transfer_info& unconfirmed_wti = misc_utils::get_or_insert_value_initialized(m_unconfirmed_txs, currency::get_transaction_hash(tx)); - //override some info that might be missing - unconfirmed_wti.selected_indicies = selected_indicies; -} -//---------------------------------------------------------------------------------------------------- -std::string wallet2::get_alias_for_address(const std::string& addr) -{ - std::vector aliases = get_aliases_for_address(addr); - if (aliases.size()) - return aliases.front(); - return ""; -} -//---------------------------------------------------------------------------------------------------- -std::vector wallet2::get_aliases_for_address(const std::string& addr) -{ - PROFILE_FUNC("wallet2::get_alias_for_address"); - currency::COMMAND_RPC_GET_ALIASES_BY_ADDRESS::request req = addr; - currency::COMMAND_RPC_GET_ALIASES_BY_ADDRESS::response res = AUTO_VAL_INIT(res); - std::vector aliases; - if (!m_core_proxy->call_COMMAND_RPC_GET_ALIASES_BY_ADDRESS(req, res)) + //---------------------------------------------------------------------------------------------------- + bool wallet2::prepare_free_transfers_cache(uint64_t fake_outputs_count) { - WLT_LOG_L0("Failed to COMMAND_RPC_GET_ALIASES_BY_ADDRESS"); + WLT_LOG_L2("Preparing transfers_cache..."); + uint64_t count = 0; + if (!m_found_free_amounts.size() || fake_outputs_count != m_fake_outputs_count) + { + m_found_free_amounts.clear(); + for (const auto& tr : m_transfers) + { + uint64_t i = tr.first; + const transfer_details& td = tr.second; + uint64_t fake_outputs_count_local = fake_outputs_count; + if (td.m_zc_info_ptr) + { + //zarcanum out, redefine fake_outputs_count + fake_outputs_count_local = this->is_auditable() ? 0 : m_core_runtime_config.hf4_minimum_mixins; + } + if (is_transfer_able_to_go(td, fake_outputs_count_local)) + { + //@#@ + m_found_free_amounts[td.get_asset_id()][td.amount()].insert(i); + count++; + } + } + m_fake_outputs_count = fake_outputs_count; + } + + WLT_LOG_L2("Transfers_cache prepared. " << count << " items cached for " << m_found_free_amounts.size() << " amounts"); + return true; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::add_transfers_to_transfers_cache(const std::vector& indexs) + { + //@#@ + for (auto i : indexs) + add_transfer_to_transfers_cache(m_transfers.at(i).amount(), i, m_transfers.at(i).get_asset_id()); + } + //---------------------------------------------------------------------------------------------------- + void wallet2::add_transfer_to_transfers_cache(uint64_t amount, uint64_t index, const crypto::public_key& asset_id /* = currency::native_coin_asset_id */) + { + m_found_free_amounts[asset_id][amount].insert(index); + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::select_transfers(assets_selection_context& needed_money_map, size_t fake_outputs_count, uint64_t /*dust_threshold*/, std::vector& selected_indicies) + { + prepare_free_transfers_cache(fake_outputs_count); + return select_indices_for_transfer(needed_money_map, fake_outputs_count, selected_indicies); + } + //---------------------------------------------------------------------------------------------------- + void wallet2::add_sent_unconfirmed_tx(const currency::transaction& tx, + const std::vector& recipients, + const std::vector& selected_indicies, + const std::vector& splitted_dsts) + { + PROFILE_FUNC("wallet2::add_sent_unconfirmed_tx"); + process_transaction_context ptc(tx); + ptc.recipients = recipients; + for (auto addr : recipients) + ptc.remote_aliases.push_back(get_alias_for_address(addr)); + + handle_unconfirmed_tx(ptc); + wallet_public::wallet_transfer_info& unconfirmed_wti = misc_utils::get_or_insert_value_initialized(m_unconfirmed_txs, currency::get_transaction_hash(tx)); + //override some info that might be missing + unconfirmed_wti.selected_indicies = selected_indicies; + } + //---------------------------------------------------------------------------------------------------- + std::string wallet2::get_alias_for_address(const std::string& addr) + { + std::vector aliases = get_aliases_for_address(addr); + if (aliases.size()) + return aliases.front(); + return ""; + } + //---------------------------------------------------------------------------------------------------- + std::vector wallet2::get_aliases_for_address(const std::string& addr) + { + PROFILE_FUNC("wallet2::get_alias_for_address"); + currency::COMMAND_RPC_GET_ALIASES_BY_ADDRESS::request req = addr; + currency::COMMAND_RPC_GET_ALIASES_BY_ADDRESS::response res = AUTO_VAL_INIT(res); + std::vector aliases; + if (!m_core_proxy->call_COMMAND_RPC_GET_ALIASES_BY_ADDRESS(req, res)) + { + WLT_LOG_L0("Failed to COMMAND_RPC_GET_ALIASES_BY_ADDRESS"); + return aliases; + } + for (auto& e : res.alias_info_list) + { + aliases.push_back(e.alias); + } return aliases; } - for (auto& e : res.alias_info_list) + //---------------------------------------------------------------------------------------------------- + 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, + currency::transaction& tx) { - aliases.push_back(e.alias); + transfer(dsts, fake_outputs_count, unlock_time, fee, extra, attachments, get_current_split_strategy(), tx_dust_policy(DEFAULT_DUST_THRESHOLD), tx); } - return aliases; -} -//---------------------------------------------------------------------------------------------------- -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, - currency::transaction& tx) -{ - transfer(dsts, fake_outputs_count, unlock_time, fee, extra, attachments, get_current_split_strategy(), 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) -{ - currency::transaction tx; - transfer(dsts, fake_outputs_count, unlock_time, fee, extra, attachments, tx); -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::is_connected_to_net() -{ - currency::COMMAND_RPC_GET_INFO::request req = AUTO_VAL_INIT(req); - currency::COMMAND_RPC_GET_INFO::response res = AUTO_VAL_INIT(res); - if (!m_core_proxy->call_COMMAND_RPC_GET_INFO(req, res)) + //---------------------------------------------------------------------------------------------------- + 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) { - WLT_LOG_L0("Failed to COMMAND_RPC_GET_INFO"); - return false; + currency::transaction tx; + transfer(dsts, fake_outputs_count, unlock_time, fee, extra, attachments, tx); } - return (res.synchronized_connections_count) ? true : false; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::process_genesis_if_needed(const currency::block& genesis, const std::vector* pglobal_indexes) -{ - if (!m_transfers.empty() || !m_key_images.empty()) - return; + //---------------------------------------------------------------------------------------------------- + bool wallet2::is_connected_to_net() + { + currency::COMMAND_RPC_GET_INFO::request req = AUTO_VAL_INIT(req); + currency::COMMAND_RPC_GET_INFO::response res = AUTO_VAL_INIT(res); + if (!m_core_proxy->call_COMMAND_RPC_GET_INFO(req, res)) + { + WLT_LOG_L0("Failed to COMMAND_RPC_GET_INFO"); + return false; + } + return (res.synchronized_connections_count) ? true : false; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::process_genesis_if_needed(const currency::block& genesis, const std::vector* pglobal_indexes) + { + if (!m_transfers.empty() || !m_key_images.empty()) + return; - THROW_IF_TRUE_WALLET_EX(get_blockchain_current_size() > 1, error::wallet_internal_error, "Can't change wallet genesis block once the blockchain has been populated"); + THROW_IF_TRUE_WALLET_EX(get_blockchain_current_size() > 1, error::wallet_internal_error, "Can't change wallet genesis block once the blockchain has been populated"); - crypto::hash genesis_hash = get_block_hash(genesis); - if (get_blockchain_current_size() == 1 && m_chain.get_genesis() != genesis_hash) + crypto::hash genesis_hash = get_block_hash(genesis); + if (get_blockchain_current_size() == 1 && m_chain.get_genesis() != genesis_hash) WLT_LOG_L0("Changing genesis block for wallet " << m_account.get_public_address_str() << ":" << ENDL << " " << m_chain.get_genesis() << " -> " << genesis_hash); - //m_blockchain.clear(); + //m_blockchain.clear(); - //m_blockchain.push_back(genesis_hash); - m_chain.set_genesis(genesis_hash); - m_last_bc_timestamp = genesis.timestamp; + //m_blockchain.push_back(genesis_hash); + m_chain.set_genesis(genesis_hash); + m_last_bc_timestamp = genesis.timestamp; - WLT_LOG_L2("Processing genesis block: " << genesis_hash); - process_new_transaction(genesis.miner_tx, 0, genesis, pglobal_indexes); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::set_genesis(const crypto::hash& genesis_hash) -{ - THROW_IF_TRUE_WALLET_EX(get_blockchain_current_size() != 1, error::wallet_internal_error, "Can't change wallet genesis hash once the blockchain has been populated"); - WLT_LOG_L0("Changing genesis hash for wallet " << m_account.get_public_address_str() << ":" << ENDL << " " << m_chain.get_genesis() << " -> " << genesis_hash); - m_chain.set_genesis(genesis_hash); -} -//---------------------------------------------------------------------------------------------------- -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 - << 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.", - LOG_LEVEL_0); -} -//---------------------------------------------------------------------------------------------------- -uint64_t wallet2::get_tx_expiration_median() const -{ - currency::COMMAND_RPC_GET_CURRENT_CORE_TX_EXPIRATION_MEDIAN::request req = AUTO_VAL_INIT(req); - currency::COMMAND_RPC_GET_CURRENT_CORE_TX_EXPIRATION_MEDIAN::response res = AUTO_VAL_INIT(res); - m_core_proxy->call_COMMAND_RPC_GET_CURRENT_CORE_TX_EXPIRATION_MEDIAN(req, res); - - if (res.status != API_RETURN_CODE_OK) - { - WLT_LOG_ERROR("COMMAND_RPC_GET_CURRENT_CORE_TX_EXPIRATION_MEDIAN failed, status: " << res.status); - return 0; + WLT_LOG_L2("Processing genesis block: " << genesis_hash); + process_new_transaction(genesis.miner_tx, 0, genesis, pglobal_indexes); } - - return res.expiration_median; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::print_source_entry(std::stringstream& output, const currency::tx_source_entry& src) const -{ - std::stringstream ss; - for(auto& el : src.outputs) - ss << el.out_reference << " "; - - output << "amount: " << print_money_brief(src.amount, get_asset_decimal_point(src.asset_id)) << (src.is_zc() ? "" : " (bare)"); - - if (src.asset_id != currency::native_coin_asset_id) - output << " (" << print16(src.asset_id) << ")"; - - output << ", real_output: " << src.real_output - << ", real_output_in_tx_index: " << src.real_output_in_tx_index - << ", indexes: " << ss.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; -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::is_need_to_split_outputs() -{ - return !is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::prepare_tx_destinations(const assets_selection_context& needed_money_map, - detail::split_strategy_id_t destination_split_strategy_id, - const tx_dust_policy& dust_policy, - const std::vector& dsts, - uint8_t tx_flags, - std::vector& final_destinations) -{ - - /* - let's account all processes assets, so if there are some destinations - that haven't been present in needed_money_map we can add it to final destinations - (could be in ionic swaps for example) - */ - std::unordered_set processed_assets; - for (auto& el: needed_money_map) + //---------------------------------------------------------------------------------------------------- + void wallet2::set_genesis(const crypto::hash& genesis_hash) { - prepare_tx_destinations(el.second.needed_amount, el.second.found_amount, destination_split_strategy_id, dust_policy, dsts, el.first, final_destinations); - processed_assets.insert(el.first); + THROW_IF_TRUE_WALLET_EX(get_blockchain_current_size() != 1, error::wallet_internal_error, "Can't change wallet genesis hash once the blockchain has been populated"); + WLT_LOG_L0("Changing genesis hash for wallet " << m_account.get_public_address_str() << ":" << ENDL << " " << m_chain.get_genesis() << " -> " << genesis_hash); + m_chain.set_genesis(genesis_hash); } - - if (is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM)) + //---------------------------------------------------------------------------------------------------- + void wallet2::print_tx_sent_message(const currency::transaction& tx, const std::string& description, uint64_t fee /* = UINT64_MAX */) { - // special case for asset minting destinations - for (auto& dst : dsts) - if (dst.asset_id == currency::null_pkey || processed_assets.count(dst.asset_id) == 0) - final_destinations.emplace_back(dst.amount, dst.addr, dst.asset_id); + //uint64_t balance_unlocked = 0; + //uint64_t balance_total = balance(balance_unlocked); - //exclude destinations that supposed to be burned (for ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN) - for (size_t i = 0; i < final_destinations.size(); ) + 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 + << 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.", + LOG_LEVEL_0); + } + //---------------------------------------------------------------------------------------------------- + uint64_t wallet2::get_tx_expiration_median() const + { + currency::COMMAND_RPC_GET_CURRENT_CORE_TX_EXPIRATION_MEDIAN::request req = AUTO_VAL_INIT(req); + currency::COMMAND_RPC_GET_CURRENT_CORE_TX_EXPIRATION_MEDIAN::response res = AUTO_VAL_INIT(res); + m_core_proxy->call_COMMAND_RPC_GET_CURRENT_CORE_TX_EXPIRATION_MEDIAN(req, res); + + if (res.status != API_RETURN_CODE_OK) { - if (final_destinations[i].addr.size() == 0) + WLT_LOG_ERROR("COMMAND_RPC_GET_CURRENT_CORE_TX_EXPIRATION_MEDIAN failed, status: " << res.status); + return 0; + } + + return res.expiration_median; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::print_source_entry(std::stringstream& output, const currency::tx_source_entry& src) const + { + std::stringstream ss; + for (auto& el : src.outputs) + ss << el.out_reference << " "; + + output << "amount: " << print_money_brief(src.amount, get_asset_decimal_point(src.asset_id)) << (src.is_zc() ? "" : " (bare)"); + + if (src.asset_id != currency::native_coin_asset_id) + output << " (" << print16(src.asset_id) << ")"; + + output << ", real_output: " << src.real_output + << ", real_output_in_tx_index: " << src.real_output_in_tx_index + << ", indexes: " << ss.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; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::is_need_to_split_outputs() + { + return !is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM); + } + //---------------------------------------------------------------------------------------------------- + void wallet2::prepare_tx_destinations(const assets_selection_context& needed_money_map, + detail::split_strategy_id_t destination_split_strategy_id, + const tx_dust_policy& dust_policy, + const std::vector& dsts, + uint8_t tx_flags, + std::vector& final_destinations) + { + + /* + let's account all processes assets, so if there are some destinations + that haven't been present in needed_money_map we can add it to final destinations + (could be in ionic swaps for example) + */ + std::unordered_set processed_assets; + for (auto& el : needed_money_map) + { + prepare_tx_destinations(el.second.needed_amount, el.second.found_amount, destination_split_strategy_id, dust_policy, dsts, el.first, final_destinations); + processed_assets.insert(el.first); + } + + if (is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM)) + { + // special case for asset minting destinations + for (auto& dst : dsts) + if (dst.asset_id == currency::null_pkey || processed_assets.count(dst.asset_id) == 0) + final_destinations.emplace_back(dst.amount, dst.addr, dst.asset_id); + + //exclude destinations that supposed to be burned (for ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN) + for (size_t i = 0; i < final_destinations.size(); ) { - final_destinations.erase(final_destinations.begin() + i); - } - else - { - i++; - } - } - - if (!(tx_flags & TX_FLAG_SIGNATURE_MODE_SEPARATE)) - { - if (final_destinations.empty()) - { - // if there's no destinations -- make CURRENCY_TX_MIN_ALLOWED_OUTS empty destinations - for(size_t i = 0; i < CURRENCY_TX_MIN_ALLOWED_OUTS; ++i) - final_destinations.emplace_back(0, m_account.get_public_address()); - } - else if (final_destinations.size() < CURRENCY_TX_MIN_ALLOWED_OUTS) - { - // if there's not ehough destinations items (i.e. outputs), split the last one - tx_destination_entry de = final_destinations.back(); - final_destinations.pop_back(); - size_t items_to_be_added = CURRENCY_TX_MIN_ALLOWED_OUTS - final_destinations.size(); - // TODO: consider allowing to set them somewhere - size_t num_digits_to_keep = CURRENCY_TX_OUTS_RND_SPLIT_DIGITS_TO_KEEP; - decompose_amount_randomly(de.amount, [&](uint64_t amount){ de.amount = amount; final_destinations.push_back(de); }, items_to_be_added, num_digits_to_keep); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(final_destinations.size() == CURRENCY_TX_MIN_ALLOWED_OUTS, - "can't get necessary number of outputs using decompose_amount_randomly(), got " << final_destinations.size() << " while mininum is " << CURRENCY_TX_MIN_ALLOWED_OUTS); - } - } - } -} -//---------------------------------------------------------------------------------------------------- -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, - const crypto::public_key& asset_id, - std::vector& final_destinations) -{ - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(found_money >= needed_money, "found_money = " << print_money_brief(found_money) << " is less than needed_money = " << print_money_brief(needed_money) << ", assed_id: " << asset_id); - - if (is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM)) - { - for(auto& dst : dsts) - { - if (dst.asset_id == asset_id) - final_destinations.emplace_back(dst); - } - if (found_money > needed_money) - final_destinations.emplace_back(found_money - needed_money, m_account.get_public_address(), asset_id); // returning back the change - } - else - { - // pre-HF4 - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(asset_id == currency::native_coin_asset_id, "assets are not allowed prior to HF4"); - 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().account_address); - change_dts.amount = found_money - needed_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_destinations, 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); - - if (0 != dust && !dust_policy.add_to_fee) - { - final_destinations.emplace_back(dust, dust_policy.addr_for_dust); - } - } -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::prepare_transaction(construct_tx_param& ctp, currency::finalize_tx_param& ftp, const mode_separate_context& msc) -{ - - SET_CONTEXT_OBJ_FOR_SCOPE(pconstruct_tx_param, ctp); - SET_CONTEXT_OBJ_FOR_SCOPE(pfinalize_tx_param, ftp); - SET_CONTEXT_OBJ_FOR_SCOPE(pmode_separate_context, msc); - - TIME_MEASURE_START_MS(get_needed_money_time); - - const currency::transaction& tx_for_mode_separate = msc.tx_for_mode_separate; - assets_selection_context needed_money_map = get_needed_money(ctp.fee, ctp.dsts); - if (this->is_auditable() && ctp.fake_outputs_count > 0) - { - WLT_THROW_IF_FALSE_WITH_CODE(false, "WALLET_RPC_ERROR_CODE_WRONG_MIXINS_FOR_AUDITABLE_WALLET", "WALLET_RPC_ERROR_CODE_WRONG_MIXINS_FOR_AUDITABLE_WALLET"); - } - ftp.ado_current_asset_owner = ctp.ado_current_asset_owner; - ftp.pthirdparty_sign_handler = ctp.pthirdparty_sign_handler; - // - // TODO @#@# need to do refactoring over this part to support hidden amounts and asset_id - // - 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"); - if (ftp.tx_version > TRANSACTION_VERSION_PRE_HF4) - { - for (const auto& el : msc.proposal_info.to_initiator) - needed_money_map[el.asset_id].needed_amount += el.amount; - } - - if (msc.escrow) - needed_money_map[currency::native_coin_asset_id].needed_amount += (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.create_utxo_defragmentation_tx) - { - try - { - if (!prepare_tx_sources_for_defragmentation_tx(ftp.sources, ftp.selected_transfers, needed_money_map[currency::native_coin_asset_id].found_amount)) - return false; - } - catch(const error::not_enough_outs_to_mix&) { return false; } // if there's not enough decoys, return false to indicate minor non-fatal error - } - else if (ctp.htlc_tx_id != currency::null_hash) - { - //htlc - //@#@ need to do refactoring over this part to support hidden amounts and asset_id - prepare_tx_sources_htlc(ctp.htlc_tx_id, ctp.htlc_origin, ftp.sources, needed_money_map[currency::native_coin_asset_id].found_amount); - WLT_THROW_IF_FALSE_WITH_CODE(ctp.dsts.size() == 1, - "htlc: unexpected ctp.dsts.size() =" << ctp.dsts.size(), API_RETURN_CODE_INTERNAL_ERROR); - - WLT_THROW_IF_FALSE_WITH_CODE(needed_money_map[currency::native_coin_asset_id].found_amount > ctp.fee, - "htlc: found money less then fee", API_RETURN_CODE_INTERNAL_ERROR); - - //fill amount - ctp.dsts.begin()->amount = needed_money_map[currency::native_coin_asset_id].found_amount - ctp.fee; - - } - else if (ctp.multisig_id != currency::null_hash) - { - //multisig - //@#@ need to do refactoring over this part to support hidden amounts and asset_id - prepare_tx_sources(ctp.multisig_id, ftp.sources, needed_money_map[currency::native_coin_asset_id].found_amount); - } - else - { - //regular tx - prepare_tx_sources(needed_money_map, ctp.fake_outputs_count, ctp.dust_policy.dust_threshold, ftp.sources, ftp.selected_transfers); - } - TIME_MEASURE_FINISH_MS(prepare_tx_sources_time); - - TIME_MEASURE_START_MS(prepare_tx_destinations_time); - prepare_tx_destinations(needed_money_map, static_cast(ctp.split_strategy_id), ctp.dust_policy, ctp.dsts, ctp.flags, 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; - ftp.spend_pub_key = m_account.get_public_address().spend_public_key; - - /* 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);*/ - return true; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::finalize_transaction(currency::finalize_tx_param& ftp, currency::transaction& tx, crypto::secret_key& tx_key, bool broadcast_tx, bool store_tx_secret_key /* = true */) -{ - currency::finalized_tx result = AUTO_VAL_INIT(result); - result.tx = tx; - result.one_time_key = tx_key; - finalize_transaction(ftp, result, broadcast_tx, store_tx_secret_key); - tx = result.tx; - tx_key = result.one_time_key; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::finalize_transaction(currency::finalize_tx_param& ftp, currency::finalized_tx& result, bool broadcast_tx, bool store_tx_secret_key /* = true */) -{ - // NOTE: if broadcast_tx == true callback rise_on_transfer2() may be called at the end of this function. - // That callback may call balance(), so it's important to have all used/spending transfers - // to be correctly marked with corresponding flags PRIOR to calling finalize_transaction() - - // broadcasting tx without secret key storing is forbidden to avoid lost key issues - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(!broadcast_tx || store_tx_secret_key, "finalize_tx is requested to broadcast a tx without storing the key"); - - THROW_IF_FALSE_WALLET_EX_MES(ftp.sources.size() <= CURRENCY_TX_MAX_ALLOWED_INPUTS, error::tx_too_big, "Too many inputs: " << ftp.sources.size() << ", maximum allowed is " << CURRENCY_TX_MAX_ALLOWED_INPUTS << "."); - - bool r = currency::construct_tx(m_account.get_keys(), - ftp, result); - //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); - uint64_t effective_fee = 0; - THROW_IF_FALSE_WALLET_CMN_ERR_EX(!get_tx_fee(result.tx, effective_fee) || effective_fee <= WALLET_TX_MAX_ALLOWED_FEE, "tx fee is WAY too big: " << print_money_brief(effective_fee) << ", maximum allowed is " << print_money_brief(WALLET_TX_MAX_ALLOWED_FEE) << "."); - - //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(result.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); - - size_t tx_blob_size = tx_to_blob(result.tx).size(); - THROW_IF_FALSE_WALLET_EX_MES(tx_blob_size < CURRENCY_MAX_TRANSACTION_BLOB_SIZE, error::tx_too_big, "Transaction size: " << tx_blob_size << " bytes, transaction size limit: " << CURRENCY_MAX_TRANSACTION_BLOB_SIZE << " bytes."); - - if (store_tx_secret_key) - m_tx_keys.insert(std::make_pair(get_transaction_hash(result.tx), result.one_time_key)); - - //TIME_MEASURE_START(send_transaction_to_network_time); - if (broadcast_tx) - send_transaction_to_network(result.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(result.tx, ftp.attachments, ftp.prepared_destinations, ftp.selected_transfers); - //TIME_MEASURE_FINISH(add_sent_tx_detailed_info_time); - - // not logging success here because it's the caller's responsibility -} -//---------------------------------------------------------------------------------------------------- -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); -} -//---------------------------------------------------------------------------------------------------- -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, - std::string* p_unsigned_filename_or_tx_blob_str) -{ - //TIME_MEASURE_START(precalculation_time); - construct_tx_param ctp = AUTO_VAL_INIT(ctp); - ctp.attachments = attachments; - ctp.crypt_address = currency::get_crypt_address_from_destinations(m_account.get_keys(), dsts); - ctp.dsts = dsts; - ctp.dust_policy = dust_policy; - ctp.extra = extra; - ctp.fake_outputs_count = fake_outputs_count; - ctp.fee = fee; - ctp.flags = flags; - // ctp.mark_tx_as_complete - // ctp.multisig_id - ctp.shuffle = shuffle; - ctp.split_strategy_id = destination_split_strategy_id; - ctp.tx_outs_attr = tx_outs_attr; - ctp.unlock_time = unlock_time; - //TIME_MEASURE_FINISH(precalculation_time); - transfer(ctp, tx, send_to_network, p_unsigned_filename_or_tx_blob_str); -} -//---------------------------------------------------------------------------------------------------- -//---------------------------------------------------------------------------------------------------- -construct_tx_param wallet2::get_default_construct_tx_param_inital() -{ - construct_tx_param ctp = AUTO_VAL_INIT(ctp); - - ctp.fee = m_core_runtime_config.tx_default_fee; - ctp.dust_policy = tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD); - ctp.split_strategy_id = get_current_split_strategy(); - ctp.tx_outs_attr = CURRENCY_TO_KEY_OUT_RELAXED; - ctp.shuffle = 0; - return ctp; -} -construct_tx_param wallet2::get_default_construct_tx_param() -{ - return get_default_construct_tx_param_inital(); -} -//---------------------------------------------------------------------------------------------------- -bool wallet2::store_unsigned_tx_to_file_and_reserve_transfers(const currency::finalize_tx_param& ftp, const std::string& filename, std::string* p_unsigned_tx_blob_str /* = nullptr */) -{ - TIME_MEASURE_START(store_unsigned_tx_time); - blobdata bl = t_serializable_object_to_blob(ftp); - crypto::chacha_crypt(bl, m_account.get_keys().view_secret_key); - - if (!filename.empty()) - { - bool r = epee::file_io_utils::save_string_to_file(filename, bl); - CHECK_AND_ASSERT_MES(r, false, "failed to store unsigned tx to " << filename); - LOG_PRINT_L0("Transaction stored to " << filename << ". You need to sign this tx using a full-access wallet."); - } - else - { - CHECK_AND_ASSERT_MES(p_unsigned_tx_blob_str != nullptr, false, "empty filename and p_unsigned_tx_blob_str == null"); - *p_unsigned_tx_blob_str = bl; - } - - TIME_MEASURE_FINISH(store_unsigned_tx_time); - - // reserve transfers at the very end - TIME_MEASURE_START(mark_transfers_as_spent_time); - mark_transfers_with_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_COLD_SIG_RESERVATION, std::string("cold sig reservation for money transfer"), true); - TIME_MEASURE_FINISH(mark_transfers_as_spent_time); - - WLT_LOG_GREEN("[wallet::store_unsigned_tx_to_file_and_reserve_transfers]" - << " store_unsigned_tx_time: " << print_fixed_decimal_point(store_unsigned_tx_time, 3) - << ", mark_transfers_as_spent_time: " << print_fixed_decimal_point(mark_transfers_as_spent_time, 3) - , LOG_LEVEL_1); - return true; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::check_and_throw_if_self_directed_tx_with_payment_id_requested(const construct_tx_param& ctp) -{ - // If someone sends coins to his own address, all tx outputs will be detected as own outputs. - // It's totally okay unless payment id is used, because it would be impossible to distinguish - // between change outs and transfer outs. Thus, such tx with a payment id can't be correctly - // obtained via RPC by the given payment id. It could be a problem for an exchange or other - // service when a user, identifyied by payment id sends coins to another user on the same - // exchange/service. Coins will be received but RPCs like get_payments won't give the transfer. - // To avoid such issues we prohibit such txs with a soft rule on sender side. - - for (auto& d : ctp.dsts) - { - for (auto& addr : d.addr) - { - if (addr != m_account.get_public_address()) - return; // at least one destination address is not our address -- it's not self-directed tx - } - } - - // it's self-directed tx - payment_id_t pid; - bool has_payment_id = get_payment_id_from_decrypted_container(ctp.attachments, pid) && !pid.empty(); - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(!has_payment_id, "sending funds to yourself with payment id is not allowed"); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::transfer(construct_tx_param& ctp, - currency::transaction &tx, - bool send_to_network, - std::string* p_unsigned_filename_or_tx_blob_str) -{ - currency::finalized_tx result = AUTO_VAL_INIT(result); - transfer(ctp, result, send_to_network, p_unsigned_filename_or_tx_blob_str); - tx = result.tx; -} -//---------------------------------------------------------------------------------------------------- -void wallet2::transfer(construct_tx_param& ctp, - currency::finalized_tx& result, - bool send_to_network, - std::string* p_unsigned_filename_or_tx_blob_str) -{ - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(!is_auditable() || !is_watch_only(), "You can't initiate coins transfer using an auditable watch-only wallet."); // btw, watch-only wallets can call transfer() within cold-signing process - - check_and_throw_if_self_directed_tx_with_payment_id_requested(ctp); - - bool asset_operation_requested = count_type_in_variant_container(ctp.extra) != 0; - bool dont_have_zero_asset_ids_in_destinations = std::count_if(ctp.dsts.begin(), ctp.dsts.end(), [](const tx_destination_entry& de) { return de.asset_id == null_pkey; }) == 0; - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(asset_operation_requested || dont_have_zero_asset_ids_in_destinations, "zero asset id is used errounesly (no asset operation was requested)"); - - if (ctp.crypt_address.spend_public_key == currency::null_pkey) - { - ctp.crypt_address = currency::get_crypt_address_from_destinations(m_account.get_keys(), ctp.dsts); - } - - TIME_MEASURE_START(prepare_transaction_time); - currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); - ftp.pevents_dispatcher = &m_debug_events_dispatcher; - ftp.tx_version = this->get_current_tx_version(); - if (!prepare_transaction(ctp, ftp)) - { - result.was_not_prepared = true; - return; - } - TIME_MEASURE_FINISH(prepare_transaction_time); - - if (m_watch_only) - { - bool r = store_unsigned_tx_to_file_and_reserve_transfers(ftp, (p_unsigned_filename_or_tx_blob_str != nullptr ? *p_unsigned_filename_or_tx_blob_str : "zano_tx_unsigned"), p_unsigned_filename_or_tx_blob_str); - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "failed to store unsigned tx"); - WLT_LOG_GREEN("[wallet::transfer]" << " prepare_transaction_time: " << print_fixed_decimal_point(prepare_transaction_time, 3), LOG_LEVEL_0); - return; - } - - 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(result.tx))); - TIME_MEASURE_FINISH(mark_transfers_as_spent_time); - - TIME_MEASURE_START(finalize_transaction_time); - try - { - finalize_transaction(ftp, result, send_to_network); - } - catch (...) - { - clear_transfers_from_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, std::string("exception on money transfer, tx: ") + epee::string_tools::pod_to_hex(get_transaction_hash(result.tx))); - throw; - } - TIME_MEASURE_FINISH(finalize_transaction_time); - - - WLT_LOG_GREEN("[wallet::transfer]" - //<< " precalculation_time: " << print_fixed_decimal_point(precalculation_time, 3) - << ", prepare_transaction_time: " << print_fixed_decimal_point(prepare_transaction_time, 3) - << ", finalize_transaction_time: " << print_fixed_decimal_point(finalize_transaction_time, 3) - << ", mark_transfers_as_spent_time: " << print_fixed_decimal_point(mark_transfers_as_spent_time, 3) - , LOG_LEVEL_0); - - print_tx_sent_message(result.tx, std::string() + "(transfer)", ctp.fee); -} - -//---------------------------------------------------------------------------------------------------- -void wallet2::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 */) -{ - bool r = false; - outs_total = 0; - amount_total = 0; - outs_swept = 0; - amount_swept = 0; - - std::vector selected_transfers; - std::unordered_map fake_outs_for_selected_transfers; // tr index -> fake outs count - selected_transfers.reserve(m_transfers.size()); - fake_outs_for_selected_transfers.reserve(m_transfers.size()); - for (size_t i = 0; i < m_transfers.size(); ++i) - { - const transfer_details& td = m_transfers[i]; - size_t fake_outs_count_for_td = is_auditable() ? 0 : (td.is_zc() ? m_core_runtime_config.hf4_minimum_mixins : fake_outs_count); - uint64_t amount = td.amount(); - if (amount < threshold_amount && td.is_native_coin() && - is_transfer_ready_to_go(td, fake_outs_count_for_td)) - { - selected_transfers.push_back(i); - r = fake_outs_for_selected_transfers.insert(std::make_pair(i, fake_outs_count_for_td)).second; - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "unable to insert: " << i << ", " << fake_outs_count_for_td); - outs_total += 1; - amount_total += amount; - } - } - - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(!selected_transfers.empty(), "No spendable outputs meet the criterion"); - - // sort by amount descending in order to spend bigger outputs first - std::sort(selected_transfers.begin(), selected_transfers.end(), [this](uint64_t a, uint64_t b) { return m_transfers[b].amount() < m_transfers[a].amount(); }); - - // limit RPC request with reasonable number of sources - if (selected_transfers.size() > CURRENCY_TX_MAX_ALLOWED_INPUTS) - selected_transfers.erase(selected_transfers.begin() + CURRENCY_TX_MAX_ALLOWED_INPUTS, selected_transfers.end()); - - prefetch_global_indicies_if_needed(selected_transfers); - - size_t max_fake_outs_count = 0; - for(auto tr_idx : selected_transfers) - if (max_fake_outs_count < fake_outs_for_selected_transfers[tr_idx]) - max_fake_outs_count = fake_outs_for_selected_transfers[tr_idx]; - - static const size_t estimated_bytes_per_input = 85; - const size_t estimated_max_inputs = static_cast(CURRENCY_MAX_TRANSACTION_BLOB_SIZE / (estimated_bytes_per_input * (max_fake_outs_count + 1.5))); // estimated number of maximum tx inputs under the tx size limit - - typedef COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry out_entry; - typedef currency::tx_source_entry::output_entry tx_output_entry; - - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response rpc_get_random_outs_resp = AUTO_VAL_INIT(rpc_get_random_outs_resp); - if (max_fake_outs_count > 0) - { - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request req = AUTO_VAL_INIT(req); - req.height_upper_limit = m_last_pow_block_h; - req.use_forced_mix_outs = false; - req.decoys_count = max_fake_outs_count + 1; - for (uint64_t i : selected_transfers) - req.amounts.push_back(m_transfers[i].is_zc() ? 0 : m_transfers[i].m_amount); - - r = m_core_proxy->call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS(req, rpc_get_random_outs_resp); - - THROW_IF_FALSE_WALLET_EX(r, error::no_connection_to_daemon, "getrandom_outs1.bin"); - THROW_IF_FALSE_WALLET_EX(rpc_get_random_outs_resp.status != API_RETURN_CODE_BUSY, error::daemon_busy, "getrandom_outs1.bin"); - THROW_IF_FALSE_WALLET_EX(rpc_get_random_outs_resp.status == API_RETURN_CODE_OK, error::get_random_outs_error, rpc_get_random_outs_resp.status); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(rpc_get_random_outs_resp.outs.size() == selected_transfers.size(), - "daemon returned wrong number of amounts for getrandom_outs1.bin: " << rpc_get_random_outs_resp.outs.size() << ", requested: " << selected_transfers.size()); - - std::vector scanty_outs; - for (COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& amount_outs : rpc_get_random_outs_resp.outs) - { - if (amount_outs.outs.size() < max_fake_outs_count) - scanty_outs.push_back(amount_outs); - } - THROW_IF_FALSE_WALLET_EX(scanty_outs.empty(), error::not_enough_outs_to_mix, scanty_outs, max_fake_outs_count); - } - - currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); - ftp.tx_version = this->get_current_tx_version(); - bool is_hf4 = this->is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM); - if (!payment_id.empty()) - set_payment_id_to_tx(ftp.attachments, payment_id, is_hf4); - // put encrypted payer info into the extra - ftp.crypt_address = destination_addr; - - currency::create_and_add_tx_payer_to_container_from_address(ftp.extra, m_account.get_public_address(), get_top_block_height(), m_core_runtime_config); - - ftp.flags = 0; - // ftp.multisig_id -- not required - // ftp.prepared_destinations -- will be filled by prepare_tx_destinations - // ftp.selected_transfers -- needed only at stage of broadcasting or storing unsigned tx - ftp.shuffle = false; - // ftp.sources -- will be filled in try_construct_tx - ftp.spend_pub_key = m_account.get_public_address().spend_public_key; // needed for offline signing - ftp.tx_outs_attr = CURRENCY_TO_KEY_OUT_RELAXED; - ftp.unlock_time = 0; - - enum try_construct_result_t {rc_ok = 0, rc_too_few_outputs = 1, rc_too_many_outputs = 2, rc_create_tx_failed = 3 }; - auto get_result_t_str = [](try_construct_result_t t) -> const char* - { return t == rc_ok ? "rc_ok" : t == rc_too_few_outputs ? "rc_too_few_outputs" : t == rc_too_many_outputs ? "rc_too_many_outputs" : t == rc_create_tx_failed ? "rc_create_tx_failed" : "unknown"; }; - - auto try_construct_tx = [this, &selected_transfers, &rpc_get_random_outs_resp, &fake_outs_for_selected_transfers, &fee, &destination_addr] - (size_t st_index_upper_boundary, currency::finalize_tx_param& ftp, uint64_t& amount_swept) -> try_construct_result_t - { - amount_swept = 0; - ftp.gen_context = tx_generation_context{}; - ftp.sources.clear(); - ftp.prepared_destinations.clear(); - - // prepare inputs - ftp.sources.resize(st_index_upper_boundary); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(st_index_upper_boundary <= selected_transfers.size(), "index_upper_boundary = " << st_index_upper_boundary << ", selected_transfers.size() = " << selected_transfers.size()); - for (size_t st_index = 0; st_index < st_index_upper_boundary; ++st_index) - { - currency::tx_source_entry& src = ftp.sources[st_index]; - uint64_t tr_index = selected_transfers[st_index]; - transfer_details& td = m_transfers[tr_index]; - src.transfer_index = tr_index; - src.amount = td.amount(); - amount_swept += src.amount; - - // populate src.outputs with mix-ins - if (rpc_get_random_outs_resp.outs.size()) - { - rpc_get_random_outs_resp.outs[st_index].outs.sort([](const out_entry& a, const out_entry& b) { return a.global_amount_index < b.global_amount_index; }); - for (out_entry& daemon_oe : rpc_get_random_outs_resp.outs[st_index].outs) + if (final_destinations[i].addr.size() == 0) { - if (td.m_global_output_index == daemon_oe.global_amount_index) - continue; - src.outputs.emplace_back(daemon_oe.global_amount_index, daemon_oe.stealth_address, daemon_oe.concealing_point, daemon_oe.amount_commitment, daemon_oe.blinded_asset_id); - if (src.outputs.size() >= fake_outs_for_selected_transfers[tr_index]) - break; + final_destinations.erase(final_destinations.begin() + i); + } + else + { + i++; } } - // insert real output into src.outputs - // TODO: bad design, we need to get rid of code duplicates below -- sowle - auto it_to_insert = std::find_if(src.outputs.begin(), src.outputs.end(), [&](const tx_output_entry& a) + if (!(tx_flags & TX_FLAG_SIGNATURE_MODE_SEPARATE)) { - if (a.out_reference.type().hash_code() == typeid(uint64_t).hash_code()) - return static_cast(boost::get(a.out_reference) >= td.m_global_output_index); - return false; // TODO: implement deterministics real output placement in case there're ref_by_id outs - }); - tx_output_entry real_oe = AUTO_VAL_INIT(real_oe); - txout_ref_v out_reference = td.m_global_output_index; // TODO: use ref_by_id when neccessary - std::vector::iterator interted_it = src.outputs.end(); - VARIANT_SWITCH_BEGIN(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]); - VARIANT_CASE_CONST(tx_out_bare, o) - { - VARIANT_SWITCH_BEGIN(o.target); - VARIANT_CASE_CONST(txout_to_key, o) - interted_it = src.outputs.emplace(it_to_insert, out_reference, o.key); - VARIANT_CASE_CONST(txout_htlc, htlc) - interted_it = src.outputs.emplace(it_to_insert, out_reference, htlc.pkey_refund); - VARIANT_CASE_OTHER() + if (final_destinations.empty()) { - WLT_THROW_IF_FALSE_WITH_CODE(false, - "Internal error: unexpected type of target: " << o.target.type().name(), - API_RETURN_CODE_INTERNAL_ERROR); + // if there's no destinations -- make CURRENCY_TX_MIN_ALLOWED_OUTS empty destinations + for (size_t i = 0; i < CURRENCY_TX_MIN_ALLOWED_OUTS; ++i) + final_destinations.emplace_back(0, m_account.get_public_address()); + } + else if (final_destinations.size() < CURRENCY_TX_MIN_ALLOWED_OUTS) + { + // if there's not ehough destinations items (i.e. outputs), split the last one + tx_destination_entry de = final_destinations.back(); + final_destinations.pop_back(); + size_t items_to_be_added = CURRENCY_TX_MIN_ALLOWED_OUTS - final_destinations.size(); + // TODO: consider allowing to set them somewhere + size_t num_digits_to_keep = CURRENCY_TX_OUTS_RND_SPLIT_DIGITS_TO_KEEP; + decompose_amount_randomly(de.amount, [&](uint64_t amount) { de.amount = amount; final_destinations.push_back(de); }, items_to_be_added, num_digits_to_keep); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(final_destinations.size() == CURRENCY_TX_MIN_ALLOWED_OUTS, + "can't get necessary number of outputs using decompose_amount_randomly(), got " << final_destinations.size() << " while mininum is " << CURRENCY_TX_MIN_ALLOWED_OUTS); } - VARIANT_SWITCH_END(); } - VARIANT_CASE_CONST(tx_out_zarcanum, o); - interted_it = src.outputs.emplace(it_to_insert, out_reference, o.stealth_address, o.concealing_point, o.amount_commitment, o.blinded_asset_id); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.is_zc(), "transfer #" << tr_index << ", amount: " << print_money_brief(td.amount()) << " is not a ZC"); - src.real_out_amount_blinding_mask = td.m_zc_info_ptr->amount_blinding_mask; - src.real_out_asset_id_blinding_mask = td.m_zc_info_ptr->asset_id_blinding_mask; - src.asset_id = td.m_zc_info_ptr->asset_id; - VARIANT_SWITCH_END(); - src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_ptx_wallet_info->m_tx); - src.real_output = interted_it - src.outputs.begin(); - src.real_output_in_tx_index = td.m_internal_output_index; + } + } + //---------------------------------------------------------------------------------------------------- + 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, + const crypto::public_key& asset_id, + std::vector& final_destinations) + { + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(found_money >= needed_money, "found_money = " << print_money_brief(found_money) << " is less than needed_money = " << print_money_brief(needed_money) << ", assed_id: " << asset_id); + + if (is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM)) + { + for (auto& dst : dsts) + { + if (dst.asset_id == asset_id) + final_destinations.emplace_back(dst); + } + if (found_money > needed_money) + final_destinations.emplace_back(found_money - needed_money, m_account.get_public_address(), asset_id); // returning back the change + } + else + { + // pre-HF4 + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(asset_id == currency::native_coin_asset_id, "assets are not allowed prior to HF4"); + 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().account_address); + change_dts.amount = found_money - needed_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_destinations, 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); + + if (0 != dust && !dust_policy.add_to_fee) + { + final_destinations.emplace_back(dust, dust_policy.addr_for_dust); + } + } + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::prepare_transaction(construct_tx_param& ctp, currency::finalize_tx_param& ftp, const mode_separate_context& msc) + { + + SET_CONTEXT_OBJ_FOR_SCOPE(pconstruct_tx_param, ctp); + SET_CONTEXT_OBJ_FOR_SCOPE(pfinalize_tx_param, ftp); + SET_CONTEXT_OBJ_FOR_SCOPE(pmode_separate_context, msc); + + TIME_MEASURE_START_MS(get_needed_money_time); + + const currency::transaction& tx_for_mode_separate = msc.tx_for_mode_separate; + assets_selection_context needed_money_map = get_needed_money(ctp.fee, ctp.dsts); + if (this->is_auditable() && ctp.fake_outputs_count > 0) + { + WLT_THROW_IF_FALSE_WITH_CODE(false, "WALLET_RPC_ERROR_CODE_WRONG_MIXINS_FOR_AUDITABLE_WALLET", "WALLET_RPC_ERROR_CODE_WRONG_MIXINS_FOR_AUDITABLE_WALLET"); + } + ftp.ado_current_asset_owner = ctp.ado_current_asset_owner; + ftp.pthirdparty_sign_handler = ctp.pthirdparty_sign_handler; + // + // TODO @#@# need to do refactoring over this part to support hidden amounts and asset_id + // + 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"); + if (ftp.tx_version > TRANSACTION_VERSION_PRE_HF4) + { + for (const auto& el : msc.proposal_info.to_initiator) + needed_money_map[el.asset_id].needed_amount += el.amount; + } + + if (msc.escrow) + needed_money_map[currency::native_coin_asset_id].needed_amount += (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.create_utxo_defragmentation_tx) + { + try + { + if (!prepare_tx_sources_for_defragmentation_tx(ftp.sources, ftp.selected_transfers, needed_money_map[currency::native_coin_asset_id].found_amount)) + return false; + } + catch (const error::not_enough_outs_to_mix&) { return false; } // if there's not enough decoys, return false to indicate minor non-fatal error + } + else if (ctp.htlc_tx_id != currency::null_hash) + { + //htlc + //@#@ need to do refactoring over this part to support hidden amounts and asset_id + prepare_tx_sources_htlc(ctp.htlc_tx_id, ctp.htlc_origin, ftp.sources, needed_money_map[currency::native_coin_asset_id].found_amount); + WLT_THROW_IF_FALSE_WITH_CODE(ctp.dsts.size() == 1, + "htlc: unexpected ctp.dsts.size() =" << ctp.dsts.size(), API_RETURN_CODE_INTERNAL_ERROR); + + WLT_THROW_IF_FALSE_WITH_CODE(needed_money_map[currency::native_coin_asset_id].found_amount > ctp.fee, + "htlc: found money less then fee", API_RETURN_CODE_INTERNAL_ERROR); + + //fill amount + ctp.dsts.begin()->amount = needed_money_map[currency::native_coin_asset_id].found_amount - ctp.fee; + + } + else if (ctp.multisig_id != currency::null_hash) + { + //multisig + //@#@ need to do refactoring over this part to support hidden amounts and asset_id + prepare_tx_sources(ctp.multisig_id, ftp.sources, needed_money_map[currency::native_coin_asset_id].found_amount); + } + else + { + //regular tx + prepare_tx_sources(needed_money_map, ctp.fake_outputs_count, ctp.dust_policy.dust_threshold, ftp.sources, ftp.selected_transfers); + } + TIME_MEASURE_FINISH_MS(prepare_tx_sources_time); + + TIME_MEASURE_START_MS(prepare_tx_destinations_time); + prepare_tx_destinations(needed_money_map, static_cast(ctp.split_strategy_id), ctp.dust_policy, ctp.dsts, ctp.flags, 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; + ftp.spend_pub_key = m_account.get_public_address().spend_public_key; + + /* 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);*/ + return true; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::finalize_transaction(currency::finalize_tx_param& ftp, currency::transaction& tx, crypto::secret_key& tx_key, bool broadcast_tx, bool store_tx_secret_key /* = true */) + { + currency::finalized_tx result = AUTO_VAL_INIT(result); + result.tx = tx; + result.one_time_key = tx_key; + finalize_transaction(ftp, result, broadcast_tx, store_tx_secret_key); + tx = result.tx; + tx_key = result.one_time_key; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::finalize_transaction(currency::finalize_tx_param& ftp, currency::finalized_tx& result, bool broadcast_tx, bool store_tx_secret_key /* = true */) + { + // NOTE: if broadcast_tx == true callback rise_on_transfer2() may be called at the end of this function. + // That callback may call balance(), so it's important to have all used/spending transfers + // to be correctly marked with corresponding flags PRIOR to calling finalize_transaction() + + // broadcasting tx without secret key storing is forbidden to avoid lost key issues + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(!broadcast_tx || store_tx_secret_key, "finalize_tx is requested to broadcast a tx without storing the key"); + + THROW_IF_FALSE_WALLET_EX_MES(ftp.sources.size() <= CURRENCY_TX_MAX_ALLOWED_INPUTS, error::tx_too_big, "Too many inputs: " << ftp.sources.size() << ", maximum allowed is " << CURRENCY_TX_MAX_ALLOWED_INPUTS << "."); + + bool r = currency::construct_tx(m_account.get_keys(), + ftp, result); + //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); + uint64_t effective_fee = 0; + THROW_IF_FALSE_WALLET_CMN_ERR_EX(!get_tx_fee(result.tx, effective_fee) || effective_fee <= WALLET_TX_MAX_ALLOWED_FEE, "tx fee is WAY too big: " << print_money_brief(effective_fee) << ", maximum allowed is " << print_money_brief(WALLET_TX_MAX_ALLOWED_FEE) << "."); + + //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(result.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); + + size_t tx_blob_size = tx_to_blob(result.tx).size(); + THROW_IF_FALSE_WALLET_EX_MES(tx_blob_size < CURRENCY_MAX_TRANSACTION_BLOB_SIZE, error::tx_too_big, "Transaction size: " << tx_blob_size << " bytes, transaction size limit: " << CURRENCY_MAX_TRANSACTION_BLOB_SIZE << " bytes."); + + if (store_tx_secret_key) + m_tx_keys.insert(std::make_pair(get_transaction_hash(result.tx), result.one_time_key)); + + //TIME_MEASURE_START(send_transaction_to_network_time); + if (broadcast_tx) + send_transaction_to_network(result.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(result.tx, ftp.attachments, ftp.prepared_destinations, ftp.selected_transfers); + //TIME_MEASURE_FINISH(add_sent_tx_detailed_info_time); + + // not logging success here because it's the caller's responsibility + } + //---------------------------------------------------------------------------------------------------- + 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); + } + //---------------------------------------------------------------------------------------------------- + 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, + std::string* p_unsigned_filename_or_tx_blob_str) + { + //TIME_MEASURE_START(precalculation_time); + construct_tx_param ctp = AUTO_VAL_INIT(ctp); + ctp.attachments = attachments; + ctp.crypt_address = currency::get_crypt_address_from_destinations(m_account.get_keys(), dsts); + ctp.dsts = dsts; + ctp.dust_policy = dust_policy; + ctp.extra = extra; + ctp.fake_outputs_count = fake_outputs_count; + ctp.fee = fee; + ctp.flags = flags; + // ctp.mark_tx_as_complete + // ctp.multisig_id + ctp.shuffle = shuffle; + ctp.split_strategy_id = destination_split_strategy_id; + ctp.tx_outs_attr = tx_outs_attr; + ctp.unlock_time = unlock_time; + //TIME_MEASURE_FINISH(precalculation_time); + transfer(ctp, tx, send_to_network, p_unsigned_filename_or_tx_blob_str); + } + //---------------------------------------------------------------------------------------------------- + //---------------------------------------------------------------------------------------------------- + construct_tx_param wallet2::get_default_construct_tx_param_inital() + { + construct_tx_param ctp = AUTO_VAL_INIT(ctp); + + ctp.fee = m_core_runtime_config.tx_default_fee; + ctp.dust_policy = tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD); + ctp.split_strategy_id = get_current_split_strategy(); + ctp.tx_outs_attr = CURRENCY_TO_KEY_OUT_RELAXED; + ctp.shuffle = 0; + return ctp; + } + construct_tx_param wallet2::get_default_construct_tx_param() + { + return get_default_construct_tx_param_inital(); + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::store_unsigned_tx_to_file_and_reserve_transfers(const currency::finalize_tx_param& ftp, const std::string& filename, std::string* p_unsigned_tx_blob_str /* = nullptr */) + { + TIME_MEASURE_START(store_unsigned_tx_time); + blobdata bl = t_serializable_object_to_blob(ftp); + crypto::chacha_crypt(bl, m_account.get_keys().view_secret_key); + + if (!filename.empty()) + { + bool r = epee::file_io_utils::save_string_to_file(filename, bl); + CHECK_AND_ASSERT_MES(r, false, "failed to store unsigned tx to " << filename); + LOG_PRINT_L0("Transaction stored to " << filename << ". You need to sign this tx using a full-access wallet."); + } + else + { + CHECK_AND_ASSERT_MES(p_unsigned_tx_blob_str != nullptr, false, "empty filename and p_unsigned_tx_blob_str == null"); + *p_unsigned_tx_blob_str = bl; } - if (amount_swept <= fee) - return rc_too_few_outputs; + TIME_MEASURE_FINISH(store_unsigned_tx_time); - // try to construct a transaction + // reserve transfers at the very end + TIME_MEASURE_START(mark_transfers_as_spent_time); + mark_transfers_with_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_COLD_SIG_RESERVATION, std::string("cold sig reservation for money transfer"), true); + TIME_MEASURE_FINISH(mark_transfers_as_spent_time); - assets_selection_context needed_money_map; - needed_money_map[currency::native_coin_asset_id] = {}; - const std::vector dsts({ tx_destination_entry(amount_swept - fee, destination_addr) }); - prepare_tx_destinations(needed_money_map, get_current_split_strategy(), tools::tx_dust_policy(), dsts, ftp.flags, ftp.prepared_destinations); + WLT_LOG_GREEN("[wallet::store_unsigned_tx_to_file_and_reserve_transfers]" + << " store_unsigned_tx_time: " << print_fixed_decimal_point(store_unsigned_tx_time, 3) + << ", mark_transfers_as_spent_time: " << print_fixed_decimal_point(mark_transfers_as_spent_time, 3) + , LOG_LEVEL_1); + return true; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::check_and_throw_if_self_directed_tx_with_payment_id_requested(const construct_tx_param& ctp) + { + // If someone sends coins to his own address, all tx outputs will be detected as own outputs. + // It's totally okay unless payment id is used, because it would be impossible to distinguish + // between change outs and transfer outs. Thus, such tx with a payment id can't be correctly + // obtained via RPC by the given payment id. It could be a problem for an exchange or other + // service when a user, identifyied by payment id sends coins to another user on the same + // exchange/service. Coins will be received but RPCs like get_payments won't give the transfer. + // To avoid such issues we prohibit such txs with a soft rule on sender side. - currency::transaction tx = AUTO_VAL_INIT(tx); - crypto::secret_key tx_key = AUTO_VAL_INIT(tx_key); + for (auto& d : ctp.dsts) + { + for (auto& addr : d.addr) + { + if (addr != m_account.get_public_address()) + return; // at least one destination address is not our address -- it's not self-directed tx + } + } + + // it's self-directed tx + payment_id_t pid; + bool has_payment_id = get_payment_id_from_decrypted_container(ctp.attachments, pid) && !pid.empty(); + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(!has_payment_id, "sending funds to yourself with payment id is not allowed"); + } + //---------------------------------------------------------------------------------------------------- + void wallet2::transfer(construct_tx_param& ctp, + currency::transaction& tx, + bool send_to_network, + std::string* p_unsigned_filename_or_tx_blob_str) + { + currency::finalized_tx result = AUTO_VAL_INIT(result); + transfer(ctp, result, send_to_network, p_unsigned_filename_or_tx_blob_str); + tx = result.tx; + } + //---------------------------------------------------------------------------------------------------- + void wallet2::transfer(construct_tx_param& ctp, + currency::finalized_tx& result, + bool send_to_network, + std::string* p_unsigned_filename_or_tx_blob_str) + { + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(!is_auditable() || !is_watch_only(), "You can't initiate coins transfer using an auditable watch-only wallet."); // btw, watch-only wallets can call transfer() within cold-signing process + + check_and_throw_if_self_directed_tx_with_payment_id_requested(ctp); + + bool asset_operation_requested = count_type_in_variant_container(ctp.extra) != 0; + bool dont_have_zero_asset_ids_in_destinations = std::count_if(ctp.dsts.begin(), ctp.dsts.end(), [](const tx_destination_entry& de) { return de.asset_id == null_pkey; }) == 0; + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(asset_operation_requested || dont_have_zero_asset_ids_in_destinations, "zero asset id is used errounesly (no asset operation was requested)"); + + if (ctp.crypt_address.spend_public_key == currency::null_pkey) + { + ctp.crypt_address = currency::get_crypt_address_from_destinations(m_account.get_keys(), ctp.dsts); + } + + TIME_MEASURE_START(prepare_transaction_time); + currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); + ftp.pevents_dispatcher = &m_debug_events_dispatcher; + ftp.tx_version = this->get_current_tx_version(); + if (!prepare_transaction(ctp, ftp)) + { + result.was_not_prepared = true; + return; + } + TIME_MEASURE_FINISH(prepare_transaction_time); + + if (m_watch_only) + { + bool r = store_unsigned_tx_to_file_and_reserve_transfers(ftp, (p_unsigned_filename_or_tx_blob_str != nullptr ? *p_unsigned_filename_or_tx_blob_str : "zano_tx_unsigned"), p_unsigned_filename_or_tx_blob_str); + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "failed to store unsigned tx"); + WLT_LOG_GREEN("[wallet::transfer]" << " prepare_transaction_time: " << print_fixed_decimal_point(prepare_transaction_time, 3), LOG_LEVEL_0); + return; + } + + 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(result.tx))); + TIME_MEASURE_FINISH(mark_transfers_as_spent_time); + + TIME_MEASURE_START(finalize_transaction_time); try { - finalize_transaction(ftp, tx, tx_key, false, false); - } - catch (error::tx_too_big&) - { - return rc_too_many_outputs; + finalize_transaction(ftp, result, send_to_network); } catch (...) { - return rc_create_tx_failed; + clear_transfers_from_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, std::string("exception on money transfer, tx: ") + epee::string_tools::pod_to_hex(get_transaction_hash(result.tx))); + throw; } + TIME_MEASURE_FINISH(finalize_transaction_time); - return rc_ok; - }; - size_t st_index_upper_boundary = std::min(selected_transfers.size(), estimated_max_inputs); - try_construct_result_t res = try_construct_tx(st_index_upper_boundary, ftp, amount_swept); + WLT_LOG_GREEN("[wallet::transfer]" + //<< " precalculation_time: " << print_fixed_decimal_point(precalculation_time, 3) + << ", prepare_transaction_time: " << print_fixed_decimal_point(prepare_transaction_time, 3) + << ", finalize_transaction_time: " << print_fixed_decimal_point(finalize_transaction_time, 3) + << ", mark_transfers_as_spent_time: " << print_fixed_decimal_point(mark_transfers_as_spent_time, 3) + , LOG_LEVEL_0); - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(res != rc_too_few_outputs, st_index_upper_boundary << " biggest unspent outputs have total amount of " << print_money_brief(amount_swept) - << " which is less than required fee: " << print_money_brief(fee) << ", transaction cannot be constructed"); - - if (res == rc_too_many_outputs) + print_tx_sent_message(result.tx, std::string() + "(transfer)", ctp.fee); + } + + //---------------------------------------------------------------------------------------------------- + void wallet2::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 */) { - WLT_LOG_L1("sweep_below: first try of try_construct_tx(" << st_index_upper_boundary << ") returned " << get_result_t_str(res)); - size_t low_bound = 0; - size_t high_bound = st_index_upper_boundary; - currency::finalize_tx_param ftp_ok = ftp; - for (;;) + bool r = false; + outs_total = 0; + amount_total = 0; + outs_swept = 0; + amount_swept = 0; + + std::vector selected_transfers; + std::unordered_map fake_outs_for_selected_transfers; // tr index -> fake outs count + selected_transfers.reserve(m_transfers.size()); + fake_outs_for_selected_transfers.reserve(m_transfers.size()); + for (const auto& tr : m_transfers) { - if (low_bound + 1 >= high_bound) + uint64_t i = tr.first; + const transfer_details& td = tr.second; + size_t fake_outs_count_for_td = is_auditable() ? 0 : (td.is_zc() ? m_core_runtime_config.hf4_minimum_mixins : fake_outs_count); + uint64_t amount = td.amount(); + if (amount < threshold_amount && td.is_native_coin() && + is_transfer_ready_to_go(td, fake_outs_count_for_td)) { - st_index_upper_boundary = low_bound; - res = rc_ok; - ftp = ftp_ok; - break; + selected_transfers.push_back(i); + r = fake_outs_for_selected_transfers.insert(std::make_pair(i, fake_outs_count_for_td)).second; + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "unable to insert: " << i << ", " << fake_outs_count_for_td); + outs_total += 1; + amount_total += amount; } - st_index_upper_boundary = (low_bound + high_bound) / 2; - try_construct_result_t res = try_construct_tx(st_index_upper_boundary, ftp, amount_swept); - WLT_LOG_L1("sweep_below: try_construct_tx(" << st_index_upper_boundary << ") returned " << get_result_t_str(res)); - if (res == rc_ok) - { - low_bound = st_index_upper_boundary; - ftp_ok = ftp; - } - else if (res == rc_too_many_outputs) - { - high_bound = st_index_upper_boundary; - } - else - break; } - } - if (res != rc_ok) - { - uint64_t amount_min = UINT64_MAX, amount_max = 0, amount_sum = 0; - for (auto& i : selected_transfers) + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(!selected_transfers.empty(), "No spendable outputs meet the criterion"); + + // sort by amount descending in order to spend bigger outputs first + std::sort(selected_transfers.begin(), selected_transfers.end(), [this](uint64_t a, uint64_t b) { return m_transfers.at(b).amount() < m_transfers.at(a).amount(); }); + + // limit RPC request with reasonable number of sources + if (selected_transfers.size() > CURRENCY_TX_MAX_ALLOWED_INPUTS) + selected_transfers.erase(selected_transfers.begin() + CURRENCY_TX_MAX_ALLOWED_INPUTS, selected_transfers.end()); + + prefetch_global_indicies_if_needed(selected_transfers); + + size_t max_fake_outs_count = 0; + for (auto tr_idx : selected_transfers) + if (max_fake_outs_count < fake_outs_for_selected_transfers[tr_idx]) + max_fake_outs_count = fake_outs_for_selected_transfers[tr_idx]; + + static const size_t estimated_bytes_per_input = 85; + const size_t estimated_max_inputs = static_cast(CURRENCY_MAX_TRANSACTION_BLOB_SIZE / (estimated_bytes_per_input * (max_fake_outs_count + 1.5))); // estimated number of maximum tx inputs under the tx size limit + + typedef COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry out_entry; + typedef currency::tx_source_entry::output_entry tx_output_entry; + + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response rpc_get_random_outs_resp = AUTO_VAL_INIT(rpc_get_random_outs_resp); + if (max_fake_outs_count > 0) { - uint64_t amount = m_transfers[i].amount(); - amount_min = std::min(amount_min, amount); - amount_max = std::max(amount_max, amount); - amount_sum += amount; + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request req = AUTO_VAL_INIT(req); + req.height_upper_limit = m_last_pow_block_h; + req.use_forced_mix_outs = false; + req.decoys_count = max_fake_outs_count + 1; + for (uint64_t i : selected_transfers) + req.amounts.push_back(m_transfers.at(i).is_zc() ? 0 : m_transfers.at(i).m_amount); + + r = m_core_proxy->call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS(req, rpc_get_random_outs_resp); + + THROW_IF_FALSE_WALLET_EX(r, error::no_connection_to_daemon, "getrandom_outs1.bin"); + THROW_IF_FALSE_WALLET_EX(rpc_get_random_outs_resp.status != API_RETURN_CODE_BUSY, error::daemon_busy, "getrandom_outs1.bin"); + THROW_IF_FALSE_WALLET_EX(rpc_get_random_outs_resp.status == API_RETURN_CODE_OK, error::get_random_outs_error, rpc_get_random_outs_resp.status); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(rpc_get_random_outs_resp.outs.size() == selected_transfers.size(), + "daemon returned wrong number of amounts for getrandom_outs1.bin: " << rpc_get_random_outs_resp.outs.size() << ", requested: " << selected_transfers.size()); + + std::vector scanty_outs; + for (COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& amount_outs : rpc_get_random_outs_resp.outs) + { + if (amount_outs.outs.size() < max_fake_outs_count) + scanty_outs.push_back(amount_outs); + } + THROW_IF_FALSE_WALLET_EX(scanty_outs.empty(), error::not_enough_outs_to_mix, scanty_outs, max_fake_outs_count); } - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(false, "try_construct_tx failed with result: " << get_result_t_str(res) << " (" << res << ")" << - ", selected_transfers stats:\n" << - " outs: " << selected_transfers.size() << ENDL << - " amount min: " << print_money(amount_min) << ENDL << - " amount max: " << print_money(amount_max) << ENDL << - " amount avg: " << (selected_transfers.empty() ? std::string("n/a") : print_money(amount_sum / selected_transfers.size()))); + + currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); + ftp.tx_version = this->get_current_tx_version(); + bool is_hf4 = this->is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM); + if (!payment_id.empty()) + set_payment_id_to_tx(ftp.attachments, payment_id, is_hf4); + // put encrypted payer info into the extra + ftp.crypt_address = destination_addr; + + currency::create_and_add_tx_payer_to_container_from_address(ftp.extra, m_account.get_public_address(), get_top_block_height(), m_core_runtime_config); + + ftp.flags = 0; + // ftp.multisig_id -- not required + // ftp.prepared_destinations -- will be filled by prepare_tx_destinations + // ftp.selected_transfers -- needed only at stage of broadcasting or storing unsigned tx + ftp.shuffle = false; + // ftp.sources -- will be filled in try_construct_tx + ftp.spend_pub_key = m_account.get_public_address().spend_public_key; // needed for offline signing + ftp.tx_outs_attr = CURRENCY_TO_KEY_OUT_RELAXED; + ftp.unlock_time = 0; + + enum try_construct_result_t { rc_ok = 0, rc_too_few_outputs = 1, rc_too_many_outputs = 2, rc_create_tx_failed = 3 }; + auto get_result_t_str = [](try_construct_result_t t) -> const char* + { return t == rc_ok ? "rc_ok" : t == rc_too_few_outputs ? "rc_too_few_outputs" : t == rc_too_many_outputs ? "rc_too_many_outputs" : t == rc_create_tx_failed ? "rc_create_tx_failed" : "unknown"; }; + + auto try_construct_tx = [this, &selected_transfers, &rpc_get_random_outs_resp, &fake_outs_for_selected_transfers, &fee, &destination_addr] + (size_t st_index_upper_boundary, currency::finalize_tx_param& ftp, uint64_t& amount_swept) -> try_construct_result_t + { + amount_swept = 0; + ftp.gen_context = tx_generation_context{}; + ftp.sources.clear(); + ftp.prepared_destinations.clear(); + + // prepare inputs + ftp.sources.resize(st_index_upper_boundary); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(st_index_upper_boundary <= selected_transfers.size(), "index_upper_boundary = " << st_index_upper_boundary << ", selected_transfers.size() = " << selected_transfers.size()); + for (size_t st_index = 0; st_index < st_index_upper_boundary; ++st_index) + { + currency::tx_source_entry& src = ftp.sources[st_index]; + uint64_t tr_index = selected_transfers[st_index]; + transfer_details& td = m_transfers.at(tr_index); + src.transfer_index = tr_index; + src.amount = td.amount(); + amount_swept += src.amount; + + // populate src.outputs with mix-ins + if (rpc_get_random_outs_resp.outs.size()) + { + rpc_get_random_outs_resp.outs[st_index].outs.sort([](const out_entry& a, const out_entry& b) { return a.global_amount_index < b.global_amount_index; }); + for (out_entry& daemon_oe : rpc_get_random_outs_resp.outs[st_index].outs) + { + if (td.m_global_output_index == daemon_oe.global_amount_index) + continue; + src.outputs.emplace_back(daemon_oe.global_amount_index, daemon_oe.stealth_address, daemon_oe.concealing_point, daemon_oe.amount_commitment, daemon_oe.blinded_asset_id); + if (src.outputs.size() >= fake_outs_for_selected_transfers[tr_index]) + break; + } + } + + // insert real output into src.outputs + // TODO: bad design, we need to get rid of code duplicates below -- sowle + auto it_to_insert = std::find_if(src.outputs.begin(), src.outputs.end(), [&](const tx_output_entry& a) + { + if (a.out_reference.type().hash_code() == typeid(uint64_t).hash_code()) + return static_cast(boost::get(a.out_reference) >= td.m_global_output_index); + return false; // TODO: implement deterministics real output placement in case there're ref_by_id outs + }); + tx_output_entry real_oe = AUTO_VAL_INIT(real_oe); + txout_ref_v out_reference = td.m_global_output_index; // TODO: use ref_by_id when neccessary + std::vector::iterator interted_it = src.outputs.end(); + VARIANT_SWITCH_BEGIN(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]); + VARIANT_CASE_CONST(tx_out_bare, o) + { + VARIANT_SWITCH_BEGIN(o.target); + VARIANT_CASE_CONST(txout_to_key, o) + interted_it = src.outputs.emplace(it_to_insert, out_reference, o.key); + VARIANT_CASE_CONST(txout_htlc, htlc) + interted_it = src.outputs.emplace(it_to_insert, out_reference, htlc.pkey_refund); + VARIANT_CASE_OTHER() + { + WLT_THROW_IF_FALSE_WITH_CODE(false, + "Internal error: unexpected type of target: " << o.target.type().name(), + API_RETURN_CODE_INTERNAL_ERROR); + } + VARIANT_SWITCH_END(); + } + VARIANT_CASE_CONST(tx_out_zarcanum, o); + interted_it = src.outputs.emplace(it_to_insert, out_reference, o.stealth_address, o.concealing_point, o.amount_commitment, o.blinded_asset_id); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.is_zc(), "transfer #" << tr_index << ", amount: " << print_money_brief(td.amount()) << " is not a ZC"); + src.real_out_amount_blinding_mask = td.m_zc_info_ptr->amount_blinding_mask; + src.real_out_asset_id_blinding_mask = td.m_zc_info_ptr->asset_id_blinding_mask; + src.asset_id = td.m_zc_info_ptr->asset_id; + VARIANT_SWITCH_END(); + src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_ptx_wallet_info->m_tx); + src.real_output = interted_it - src.outputs.begin(); + src.real_output_in_tx_index = td.m_internal_output_index; + } + + if (amount_swept <= fee) + return rc_too_few_outputs; + + // try to construct a transaction + + assets_selection_context needed_money_map; + needed_money_map[currency::native_coin_asset_id] = {}; + const std::vector dsts({ tx_destination_entry(amount_swept - fee, destination_addr) }); + prepare_tx_destinations(needed_money_map, get_current_split_strategy(), tools::tx_dust_policy(), dsts, ftp.flags, ftp.prepared_destinations); + + currency::transaction tx = AUTO_VAL_INIT(tx); + crypto::secret_key tx_key = AUTO_VAL_INIT(tx_key); + try + { + finalize_transaction(ftp, tx, tx_key, false, false); + } + catch (error::tx_too_big&) + { + return rc_too_many_outputs; + } + catch (...) + { + return rc_create_tx_failed; + } + + return rc_ok; + }; + + size_t st_index_upper_boundary = std::min(selected_transfers.size(), estimated_max_inputs); + try_construct_result_t res = try_construct_tx(st_index_upper_boundary, ftp, amount_swept); + + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(res != rc_too_few_outputs, st_index_upper_boundary << " biggest unspent outputs have total amount of " << print_money_brief(amount_swept) + << " which is less than required fee: " << print_money_brief(fee) << ", transaction cannot be constructed"); + + if (res == rc_too_many_outputs) + { + WLT_LOG_L1("sweep_below: first try of try_construct_tx(" << st_index_upper_boundary << ") returned " << get_result_t_str(res)); + size_t low_bound = 0; + size_t high_bound = st_index_upper_boundary; + currency::finalize_tx_param ftp_ok = ftp; + for (;;) + { + if (low_bound + 1 >= high_bound) + { + st_index_upper_boundary = low_bound; + res = rc_ok; + ftp = ftp_ok; + break; + } + st_index_upper_boundary = (low_bound + high_bound) / 2; + try_construct_result_t res = try_construct_tx(st_index_upper_boundary, ftp, amount_swept); + WLT_LOG_L1("sweep_below: try_construct_tx(" << st_index_upper_boundary << ") returned " << get_result_t_str(res)); + if (res == rc_ok) + { + low_bound = st_index_upper_boundary; + ftp_ok = ftp; + } + else if (res == rc_too_many_outputs) + { + high_bound = st_index_upper_boundary; + } + else + break; + } + } + + if (res != rc_ok) + { + uint64_t amount_min = UINT64_MAX, amount_max = 0, amount_sum = 0; + for (auto& i : selected_transfers) + { + uint64_t amount = m_transfers.at(i).amount(); + amount_min = std::min(amount_min, amount); + amount_max = std::max(amount_max, amount); + amount_sum += amount; + } + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(false, "try_construct_tx failed with result: " << get_result_t_str(res) << " (" << res << ")" << + ", selected_transfers stats:\n" << + " outs: " << selected_transfers.size() << ENDL << + " amount min: " << print_money(amount_min) << ENDL << + " amount max: " << print_money(amount_max) << ENDL << + " amount avg: " << (selected_transfers.empty() ? std::string("n/a") : print_money(amount_sum / selected_transfers.size()))); + } + + // populate ftp.selected_transfers from ftp.sources + ftp.selected_transfers.clear(); + for (size_t i = 0; i < ftp.sources.size(); ++i) + ftp.selected_transfers.push_back(ftp.sources[i].transfer_index); + + outs_swept = ftp.sources.size(); + + + if (m_watch_only) + { + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(p_filename_or_unsigned_tx_blob_str != nullptr, "p_filename_or_unsigned_tx_blob_str is null"); + bool r = store_unsigned_tx_to_file_and_reserve_transfers(ftp, *p_filename_or_unsigned_tx_blob_str, p_filename_or_unsigned_tx_blob_str); + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "failed to store unsigned tx"); + return; + } + + mark_transfers_as_spent(ftp.selected_transfers, "sweep_below"); + + transaction local_tx; + transaction* p_tx = p_result_tx != nullptr ? p_result_tx : &local_tx; + *p_tx = transaction{}; + try + { + crypto::secret_key sk{}; + finalize_transaction(ftp, *p_tx, sk, true); + print_tx_sent_message(*p_tx, "(sweep_below)", get_tx_fee(*p_tx)); + } + catch (...) + { + clear_transfers_from_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, std::string("exception on sweep_below, tx id (might be wrong): ") + epee::string_tools::pod_to_hex(get_transaction_hash(*p_tx))); + throw; + } + + } - // populate ftp.selected_transfers from ftp.sources - ftp.selected_transfers.clear(); - for (size_t i = 0; i < ftp.sources.size(); ++i) - ftp.selected_transfers.push_back(ftp.sources[i].transfer_index); - - outs_swept = ftp.sources.size(); - - - if (m_watch_only) - { - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(p_filename_or_unsigned_tx_blob_str != nullptr, "p_filename_or_unsigned_tx_blob_str is null"); - bool r = store_unsigned_tx_to_file_and_reserve_transfers(ftp, *p_filename_or_unsigned_tx_blob_str, p_filename_or_unsigned_tx_blob_str); - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "failed to store unsigned tx"); - return; - } - - mark_transfers_as_spent(ftp.selected_transfers, "sweep_below"); - - transaction local_tx; - transaction* p_tx = p_result_tx != nullptr ? p_result_tx : &local_tx; - *p_tx = transaction{}; - try - { - crypto::secret_key sk{}; - finalize_transaction(ftp, *p_tx, sk, true); - print_tx_sent_message(*p_tx, "(sweep_below)", get_tx_fee(*p_tx)); - } - catch (...) - { - clear_transfers_from_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, std::string("exception on sweep_below, tx id (might be wrong): ") + epee::string_tools::pod_to_hex(get_transaction_hash(*p_tx))); - throw; - } - - -} - } // namespace tools diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 2fa7601b..5ccf43de 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -149,13 +149,15 @@ namespace tools uint64_t m_last_pow_block_h = 0; std::list> m_rollback_events; std::list > m_last_zc_global_indexs; // , biggest height comes in front + + std::atomic m_concise_mode = false; //in this mode the wallet don't keep spent entries in m_transfers as well as m_recent_transfers longer then 100 entries //variables that not being serialized std::atomic m_last_bc_timestamp = 0; uint64_t m_height_of_start_sync = 0; std::atomic m_last_sync_percent = 0; mutable uint64_t m_current_wallet_file_size = 0; - bool m_use_assets_whitelisting = true; + bool m_use_assets_whitelisting = true; mutable std::optional m_has_bare_unspent_outputs; // recalculated each time the balance() is called // variables that should be part of state data object but should not be stored during serialization @@ -201,7 +203,17 @@ namespace tools a & m_chain; a & m_minimum_height; a & m_amount_gindex_to_transfer_id; - a & m_transfers; + if (ver <= 167) + { + std::deque transfer_container_old; + a& transfer_container_old; + for (size_t i = 0; i != transfer_container_old.size(); i++){m_transfers[i] = transfer_container_old[i];} + } + else + { + a& m_transfers; + } + a & m_multisig_transfers; a & m_key_images; a & m_unconfirmed_txs; @@ -234,7 +246,12 @@ namespace tools { //workaround for m_last_zc_global_indexs holding invalid index for last item m_last_zc_global_indexs.pop_front(); - } + } + if (ver <= 167) + { + return; + } + a& m_concise_mode; } }; @@ -257,7 +274,7 @@ namespace tools bool is_pos_allowed = false; bool is_pos_sequence_factor_good = false; - uint64_t index = 0; // index in m_transfers + //uint64_t index = 0; // index in m_transfers uint64_t stake_unlock_time = 0; uint64_t height = 0; uint64_t starter_timestamp = 0; @@ -382,7 +399,7 @@ namespace tools void get_recent_transfers_history(std::vector& trs, size_t offset, size_t count, uint64_t& total, uint64_t& last_item_index, bool exclude_mining_txs = false, bool start_from_end = true); bool is_defragmentation_transaction(const wallet_public::wallet_transfer_info& wti); uint64_t get_recent_transfers_total_count(); - uint64_t get_transfer_entries_count(); + //uint64_t get_transfer_entries_count(); void get_unconfirmed_transfers(std::vector& trs, bool exclude_mining_txs = false); void init(const std::string& daemon_address = "http://localhost:8080"); bool deinit(); @@ -559,10 +576,11 @@ namespace tools currency::transaction &escrow_template_tx); bool check_connection(); + bool trim_transfers_and_history(const std::list& items_to_remove); // PoS mining - void do_pos_mining_prepare_entry(mining_context& cxt, size_t transfer_index); - bool do_pos_mining_iteration(mining_context& cxt, size_t transfer_index, uint64_t ts); + void do_pos_mining_prepare_entry(mining_context& cxt, const transfer_details& td); + bool do_pos_mining_iteration(mining_context& cxt, uint64_t ts); template //do refresh as external callback bool scan_pos(mining_context& cxt, std::atomic& stop, idle_condition_cb_t idle_condition_cb, const currency::core_runtime_config &runtime_config); bool fill_mining_context(mining_context& ctx); @@ -1064,7 +1082,7 @@ namespace tools if (tr_index != UINT64_MAX) { - transfer_details& td = m_transfers[tr_index]; + transfer_details& td = m_transfers.at(tr_index); ptc.total_balance_change[td.get_asset_id()] -= td.amount(); if (td.is_native_coin()) { @@ -1114,9 +1132,9 @@ namespace tools ts_middle -= ts_middle % POS_SCAN_STEP; uint64_t ts_window = std::min(ts_middle - ts_from, ts_to - ts_middle); - for (size_t transfer_index = 0; transfer_index != m_transfers.size(); transfer_index++) + for (auto it = m_transfers.begin(); it != m_transfers.end(); it++)//size_t transfer_index = 0; transfer_index != m_transfers.size(); transfer_index++) { - auto& tr = m_transfers[transfer_index]; + auto& tr = it->second; uint64_t stake_unlock_time = 0; if (!is_transfer_okay_for_pos(tr, cxt.zarcanum, stake_unlock_time)) @@ -1142,7 +1160,7 @@ namespace tools } }; - do_pos_mining_prepare_entry(cxt, transfer_index); + do_pos_mining_prepare_entry(cxt, tr); cxt.total_items_checked++; cxt.total_amount_checked += tr.amount(); while(step <= ts_window) @@ -1171,9 +1189,9 @@ namespace tools return false; cxt.iterations_processed++; - if (do_pos_mining_iteration(cxt, transfer_index, ts)) + if (do_pos_mining_iteration(cxt, tr, ts)) { - cxt.index = transfer_index; + cxt.index = it->first; cxt.stake_unlock_time = stake_unlock_time; cxt.status = API_RETURN_CODE_OK; return true; diff --git a/src/wallet/wallet2_base.h b/src/wallet/wallet2_base.h index c2ec860b..1917db17 100644 --- a/src/wallet/wallet2_base.h +++ b/src/wallet/wallet2_base.h @@ -510,7 +510,7 @@ namespace tools typedef std::unordered_multimap payment_container; - typedef std::deque transfer_container; + typedef std::map transfer_container; //typedef std::deque transfer_container; typedef std::unordered_map multisig_transfer_container; typedef std::unordered_map escrow_contracts_container; typedef std::map > free_amounts_cache_type; From 77a928c78aedc47bce5d76ae8e43f5a8d4b27cca Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sun, 1 Sep 2024 21:23:58 +0400 Subject: [PATCH 002/106] wallet compiled ok --- src/wallet/wallet2.cpp | 120 +++++++++++++++++++++++------------------ src/wallet/wallet2.h | 9 +++- 2 files changed, 75 insertions(+), 54 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index bf04aacc..844b14e0 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -59,8 +59,6 @@ using namespace currency; #define WALLET_TX_MAX_ALLOWED_FEE (COIN * 100) #define WALLET_FETCH_RANDOM_OUTS_SIZE 200 -#define WALLET_CONCISE_MODE_MAX_REORG_BLOCKS CURRENCY_BLOCKS_PER_DAY * 7 //week -#define WALLET_CONCISE_MODE_MAX_HISTORY_SIZE 500 @@ -2897,8 +2895,8 @@ namespace tools uint64_t tx_expiration_ts_median = get_tx_expiration_median(); handle_expiration_list(tx_expiration_ts_median); handle_contract_expirations(tx_expiration_ts_median); - m_found_free_amounts.clear(); + trim_wallet(); } @@ -2997,21 +2995,20 @@ namespace tools // rollback incoming transfers from detaching subchain { - auto it = std::find_if(m_transfers.begin(), m_transfers.end(), [&](const transfer_details& td){return td.m_ptx_wallet_info->m_block_height >= including_height; }); + auto it = std::find_if(m_transfers.begin(), m_transfers.end(), [&](const transfer_container::value_type& tr_e){return tr_e.second.m_ptx_wallet_info->m_block_height >= including_height; }); if (it != m_transfers.end()) { - size_t i_start = it - m_transfers.begin(); - for (const auto& tr : m_transfers) + for (; it!= m_transfers.end(); it++) { - uint64_t i = tr.first; + uint64_t i = it->first; //check for htlc - if (m_transfers.at(i).m_ptx_wallet_info->m_tx.vout[m_transfers.at(i).m_internal_output_index].type() == typeid(tx_out_bare) && - boost::get(m_transfers.at(i).m_ptx_wallet_info->m_tx.vout[m_transfers.at(i).m_internal_output_index]).target.type() == typeid(txout_htlc)) + if (it->second.m_ptx_wallet_info->m_tx.vout[it->second.m_internal_output_index].type() == typeid(tx_out_bare) && + boost::get(it->second.m_ptx_wallet_info->m_tx.vout[it->second.m_internal_output_index]).target.type() == typeid(txout_htlc)) { //need to find an entry in m_htlc and remove it - const txout_htlc& hltc = boost::get(boost::get(m_transfers.at(i).m_ptx_wallet_info->m_tx.vout[m_transfers.at(i).m_internal_output_index]).target); - uint64_t expiration_height = m_transfers.at(i).m_ptx_wallet_info->m_block_height + hltc.expiration; + const txout_htlc& hltc = boost::get(boost::get(it->second.m_ptx_wallet_info->m_tx.vout[it->second.m_internal_output_index]).target); + uint64_t expiration_height = it->second.m_ptx_wallet_info->m_block_height + hltc.expiration; auto pair_of_it = m_htlcs.equal_range(expiration_height); bool found = false; for (auto it = pair_of_it.first; it != pair_of_it.second; it++) @@ -3027,11 +3024,11 @@ namespace tools } - if (!(m_transfers.at(i).m_key_image == null_ki && is_watch_only())) + if (!(it->second.m_key_image == null_ki && is_watch_only())) { - auto it_ki = m_key_images.find(m_transfers.at(i).m_key_image); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it_ki != m_key_images.end(), "key image " << m_transfers.at(i).m_key_image << " not found"); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(m_transfers.at(i).m_ptx_wallet_info->m_block_height >= including_height, "transfer #" << i << " block height is less than " << including_height); + auto it_ki = m_key_images.find(it->second.m_key_image); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it_ki != m_key_images.end(), "key image " << it->second.m_key_image << " not found"); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it->second.m_ptx_wallet_info->m_block_height >= including_height, "transfer #" << i << " block height is less than " << including_height); m_key_images.erase(it_ki); } remove_transfer_from_amount_gindex_map(i); @@ -3050,10 +3047,10 @@ namespace tools //rollback spends // do not clear spent flag in spent transfers as corresponding txs are most likely in the pool // they will be moved into m_unconfirmed_txs for clearing in future (if tx will expire of removed from pool) - for (const auto& tr : m_transfers) + for (auto& tr_ : m_transfers) { - uint64_t i = tr.first; - auto& tr = m_transfers.at(i); + uint64_t i = tr_.first; + auto& tr = tr_.second; if (tr.m_spent_height >= including_height) { WLT_LOG_BLUE("Transfer [" << i << "] spent height: " << tr.m_spent_height << " -> 0, reason: detaching blockchain", LOG_LEVEL_1); @@ -3616,7 +3613,7 @@ namespace tools const auto& td = tr.second; if (!td.is_spent()) continue; // only spent transfers really need to be stored, because watch-only wallet will not be able to figure out they were spent otherwise - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.m_internal_output_index < td.m_ptx_wallet_info->m_tx.vout.size(), "invalid transfer #" << ti); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.m_internal_output_index < td.m_ptx_wallet_info->m_tx.vout.size(), "invalid transfer #" << tr.first); if (td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].type() != typeid(tx_out_bare)) continue; const currency::txout_target_v& out_t = boost::get(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]).target; @@ -3691,19 +3688,12 @@ namespace tools { mined = 0; m_has_bare_unspent_outputs = false; - std::list items_to_remove; - for (auto& td : m_transfers) + for (auto& tr : m_transfers) { + auto& td = tr.second; - if (td.is_spent()) - { - if (m_concise_mode && m_spent_height + WALLET_CONCISE_MODE_MAX_REORG_BLOCKS < m_chain.get_top_block_height()) - { - items_to_remove.push_back(td.first); - } - } - else if (td.is_spendable() || (td.is_reserved_for_escrow())) + if (td.is_spendable() || (td.is_reserved_for_escrow() && !td.is_spent())) { wallet_public::asset_balance_entry_base& e = balances[td.get_asset_id()]; e.total += td.amount(); @@ -3776,11 +3766,27 @@ namespace tools } + return true; + } + //---------------------------------------------------------------------------------------------------- + bool wallet2::trim_wallet() + { if (m_concise_mode) { - trim_transfers_and_history(items_to_remove); - } + std::list items_to_remove; + for (auto& tr : m_transfers) + { + if (tr.second.is_spent()) + { + if (m_concise_mode && tr.second.m_spent_height + m_wallet_concise_mode_max_reorg_blocks < m_chain.get_top_block_height()) + { + items_to_remove.push_back(tr.first); + } + } + } + return trim_transfers_and_history(items_to_remove); + } return true; } //---------------------------------------------------------------------------------------------------- @@ -4253,7 +4259,7 @@ namespace tools 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 - x + 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().view_secret_key); @@ -4668,10 +4674,10 @@ namespace tools for (const auto& tr : m_transfers) { - auto& tr = tr.second; + auto& td = tr.second; uint64_t stake_unlock_time = 0; - if (!is_transfer_okay_for_pos(tr, is_zarcanum_hf, stake_unlock_time)) + if (!is_transfer_okay_for_pos(td, is_zarcanum_hf, stake_unlock_time)) continue; ++counter; @@ -4683,10 +4689,10 @@ namespace tools bool wallet2::get_pos_entries(std::vector& entries) { bool is_zarcanum_hf = is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM); - for (const auto& tr : m_transfers) + for (const auto& td : m_transfers) { - uint64_t i = tr.first; - auto& tr = tr.second; + uint64_t i = td.first; + auto& tr = td.second; uint64_t stake_unlock_time = 0; if (!is_transfer_okay_for_pos(tr, is_zarcanum_hf, stake_unlock_time)) @@ -5067,7 +5073,7 @@ namespace tools amount_blinding_mask, m_account.get_keys().view_secret_key); } //------------------------------------------------------------------ - bool wallet2::do_pos_mining_iteration(mining_context& context, size_t transfer_index, uint64_t ts) + bool wallet2::do_pos_mining_iteration(mining_context& context, uint64_t ts) { return context.do_iteration(ts); } @@ -5648,10 +5654,15 @@ namespace tools { if (verbose) { - std::string res_buff; - local_transfers_struct lt(const_cast(m_transfers)); - epee::serialization::store_t_to_json(lt, res_buff); - ss << res_buff; + + ss << "{ \"transfers\": [" << ENDL; + for (const auto& tr : m_transfers) + { + uint64_t i = tr.first; + const transfer_details& td = tr.second; + ss << "{ \"i\": " << i << "," << ENDL; + ss << "\"entry\": " << epee::serialization::store_t_to_json(td) << "}," << ENDL; + } } else { @@ -6549,27 +6560,30 @@ namespace tools req.amounts.push_back(COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS3::offsets_distribution()); COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS3::offsets_distribution& rdisttib = req.amounts.back(); - auto it = m_transfers.begin() + i; - 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()); + auto it = m_transfers.find(i); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it != m_transfers.end(), + "internal error: index in m_tranfers " << i << " not found"); + + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it->second.m_ptx_wallet_info->m_tx.vout.size() > it->second.m_internal_output_index, + "m_internal_output_index = " << it->second.m_internal_output_index << + " is greater or equal to outputs count = " << it->second.m_ptx_wallet_info->m_tx.vout.size()); //rdisttib.own_global_index = it->m_global_output_index; //check if we have Zarcanum era output of pre-Zarcanum - if (it->is_zc()) + if (it->second.is_zc()) { if (this->is_auditable()) continue; //Zarcanum era rdisttib.amount = 0; //generate distribution in Zarcanum hardfork - build_distribution_for_input(rdisttib.global_offsets, it->m_global_output_index); + build_distribution_for_input(rdisttib.global_offsets, it->second.m_global_output_index); need_to_request = true; } else { //for prezarcanum era use flat distribution - rdisttib.amount = it->m_amount; + rdisttib.amount = it->second.m_amount; rdisttib.global_offsets.resize(fake_outputs_count + 1, 0); } } @@ -6623,12 +6637,14 @@ namespace tools size_t i = 0; for (uint64_t J : selected_indicies) { - auto it = m_transfers.begin() + J; + auto it = m_transfers.find(J); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it != m_transfers.end(), "internal error: J " << J << " not found in m_transfers"); + sources.push_back(AUTO_VAL_INIT(currency::tx_source_entry())); currency::tx_source_entry& src = sources.back(); - transfer_details& td = *it; - src.transfer_index = it - m_transfers.begin(); + transfer_details& td = it->second; + src.transfer_index = J; src.amount = td.amount(); src.asset_id = td.get_asset_id(); size_t fake_outputs_count = fake_outputs_count_; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 5ccf43de..b084b7fa 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -50,6 +50,8 @@ #define WALLET_DEFAULT_TX_SPENDABLE_AGE CURRENCY_HF4_MANDATORY_MIN_COINAGE #define WALLET_POS_MINT_CHECK_HEIGHT_INTERVAL 1 +#define WALLET_CONCISE_MODE_MAX_REORG_BLOCKS CURRENCY_BLOCKS_PER_DAY * 7 //week +#define WALLET_CONCISE_MODE_MAX_HISTORY_SIZE 500 const uint64_t WALLET_MINIMUM_HEIGHT_UNSET_CONST = std::numeric_limits::max(); @@ -274,7 +276,7 @@ namespace tools bool is_pos_allowed = false; bool is_pos_sequence_factor_good = false; - //uint64_t index = 0; // index in m_transfers + uint64_t index = 0; // index in m_transfers uint64_t stake_unlock_time = 0; uint64_t height = 0; uint64_t starter_timestamp = 0; @@ -577,6 +579,7 @@ namespace tools bool check_connection(); bool trim_transfers_and_history(const std::list& items_to_remove); + bool trim_wallet(); // PoS mining void do_pos_mining_prepare_entry(mining_context& cxt, const transfer_details& td); @@ -970,6 +973,8 @@ private: tools::wallet_public::wallet_vote_config m_votes_config; uint64_t m_last_known_daemon_height = 0; + uint64_t m_wallet_concise_mode_max_reorg_blocks = WALLET_CONCISE_MODE_MAX_REORG_BLOCKS; + //this needed to access wallets state in coretests, for creating abnormal blocks and tranmsactions friend class test_generator; @@ -1189,7 +1194,7 @@ namespace tools return false; cxt.iterations_processed++; - if (do_pos_mining_iteration(cxt, tr, ts)) + if (do_pos_mining_iteration(cxt, ts)) { cxt.index = it->first; cxt.stake_unlock_time = stake_unlock_time; From ca094054919662d9e605b4b017cd9006294f1ac3 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Mon, 2 Sep 2024 21:44:41 +0400 Subject: [PATCH 003/106] core tests working --- src/wallet/wallet2.cpp | 18 +++++++++--------- src/wallet/wallet2.h | 2 +- tests/core_tests/chaingen.cpp | 5 +++-- tests/core_tests/escrow_wallet_tests.cpp | 4 ++-- tests/core_tests/multiassets_test.cpp | 8 ++++---- tests/core_tests/multisig_wallet_tests.cpp | 2 +- 6 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 844b14e0..cefa36cf 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -802,7 +802,7 @@ namespace tools } ptc.employed_entries.receive.push_back(wallet_public::employed_tx_entry{ o , out.amount , out.asset_id }); - uint64_t new_index = m_transfers.empty() ? 0 : (--m_transfers.end())->first; + uint64_t new_index = m_transfers.empty() ? 0 : (--m_transfers.end())->first+1; auto rsp = m_transfers.insert(std::make_pair(new_index, boost::value_initialized())); transfer_details& td = rsp.first->second; td.m_ptx_wallet_info = pwallet_info; @@ -2995,11 +2995,11 @@ namespace tools // rollback incoming transfers from detaching subchain { - auto it = std::find_if(m_transfers.begin(), m_transfers.end(), [&](const transfer_container::value_type& tr_e){return tr_e.second.m_ptx_wallet_info->m_block_height >= including_height; }); - if (it != m_transfers.end()) + auto it_start = std::find_if(m_transfers.begin(), m_transfers.end(), [&](const transfer_container::value_type& tr_e){return tr_e.second.m_ptx_wallet_info->m_block_height >= including_height; }); + if (it_start != m_transfers.end()) { - for (; it!= m_transfers.end(); it++) + for (auto it = it_start; it!= m_transfers.end(); it++) { uint64_t i = it->first; //check for htlc @@ -3034,7 +3034,7 @@ namespace tools remove_transfer_from_amount_gindex_map(i); ++transfers_detached; } - m_transfers.erase(it, m_transfers.end()); + m_transfers.erase(it_start, m_transfers.end()); } } @@ -4458,10 +4458,10 @@ namespace tools return m_transfer_history.size(); } //---------------------------------------------------------------------------------------------------- - //uint64_t wallet2::get_transfer_entries_count() - //{ - // return m_transfers.size(); - //} + uint64_t wallet2::get_transfer_entries_count() + { + return m_transfers.size(); + } //---------------------------------------------------------------------------------------------------- template diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index b084b7fa..58f119e9 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -401,7 +401,7 @@ namespace tools void get_recent_transfers_history(std::vector& trs, size_t offset, size_t count, uint64_t& total, uint64_t& last_item_index, bool exclude_mining_txs = false, bool start_from_end = true); bool is_defragmentation_transaction(const wallet_public::wallet_transfer_info& wti); uint64_t get_recent_transfers_total_count(); - //uint64_t get_transfer_entries_count(); + uint64_t get_transfer_entries_count(); void get_unconfirmed_transfers(std::vector& trs, bool exclude_mining_txs = false); void init(const std::string& daemon_address = "http://localhost:8080"); bool deinit(); diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index 750b0474..09092b43 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -2172,12 +2172,13 @@ bool make_tx_multisig_to_key(const currency::transaction& source_tx, bool estimate_wallet_balance_blocked_for_escrow(const tools::wallet2& w, uint64_t& result, bool substruct_change_from_result /* = true */) { - std::deque transfers; + tools::transfer_container transfers; w.get_transfers(transfers); result = 0; - for (const tools::transfer_details& td : transfers) + for (const auto& tr : transfers) { + const tools::transfer_details& td = tr.second; if (td.m_flags == (WALLET_TRANSFER_DETAIL_FLAG_BLOCKED | WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION)) result += td.amount(); } diff --git a/tests/core_tests/escrow_wallet_tests.cpp b/tests/core_tests/escrow_wallet_tests.cpp index b4bfaca3..ea32cfd1 100644 --- a/tests/core_tests/escrow_wallet_tests.cpp +++ b/tests/core_tests/escrow_wallet_tests.cpp @@ -780,7 +780,7 @@ bool escrow_proposal_expiration::c1(currency::core& c, size_t ev_index, const st uint64_t alice_post_proposal_balance = alice_wlt->balance(); uint64_t alice_post_proposal_balance_expected = alice_start_balance - TESTS_DEFAULT_FEE; CHECK_AND_ASSERT_MES(alice_post_proposal_balance == alice_post_proposal_balance_expected, false, "Incorrect alice_post_proposal_balance: " << print_money(alice_post_proposal_balance) << ", expected: " << print_money(alice_post_proposal_balance_expected)); - std::deque transfers; + tools::transfer_container transfers; alice_wlt->get_transfers(transfers); CHECK_AND_ASSERT_MES(transfers.size() == 2 && ( (transfers[0].is_spent() && (transfers[1].m_flags & (WALLET_TRANSFER_DETAIL_FLAG_BLOCKED | WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION))) || @@ -2283,7 +2283,7 @@ bool escrow_proposal_not_enough_money::c1(currency::core& c, size_t ev_index, co CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt.get(), "Alice", MK_TEST_COINS(30), 0, MK_TEST_COINS(30), 0, 0), false, ""); - std::deque transfers; + tools::transfer_container transfers; alice_wlt->get_transfers(transfers); CHECK_AND_ASSERT_MES(transfers.size() == 1, false, "Incorrect transfers size: " << transfers.size()); diff --git a/tests/core_tests/multiassets_test.cpp b/tests/core_tests/multiassets_test.cpp index 94a938d2..afe029e8 100644 --- a/tests/core_tests/multiassets_test.cpp +++ b/tests/core_tests/multiassets_test.cpp @@ -533,7 +533,7 @@ bool assets_and_explicit_native_coins_in_outs::c2_alice_deploys_asset(currency:: // make sure Alice has two UTXO now tools::transfer_container transfers{}; alice_wlt->get_transfers(transfers); - size_t unspent_transfers = std::count_if(transfers.begin(), transfers.end(), [](const tools::transfer_details& tr){ return !tr.is_spent(); }); + size_t unspent_transfers = std::count_if(transfers.begin(), transfers.end(), [](const auto& tr){ return !tr.second.is_spent(); }); CHECK_AND_ASSERT_MES(unspent_transfers == 2, false, "unexpected number of Alice's unspent transfers: " << unspent_transfers); asset_descriptor_base adb{}; @@ -692,15 +692,15 @@ bool asset_depoyment_and_few_zc_utxos::c1(currency::core& c, size_t ev_index, co CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt, "Alice", m_alice_initial_balance, 0, m_alice_initial_balance, 0, 0), false, ""); // make sure Alice has correct UTXO wallet structure - tools::transfer_container transfers{}; + tools::transfer_container transfers; alice_wlt->get_transfers(transfers); size_t zc_unspent_outs = 0, unspent_outs = 0; for(auto& td : transfers) { - if (!td.is_spent()) + if (!td.second.is_spent()) { ++unspent_outs; - if (td.is_zc()) + if (td.second.is_zc()) ++zc_unspent_outs; } } diff --git a/tests/core_tests/multisig_wallet_tests.cpp b/tests/core_tests/multisig_wallet_tests.cpp index 2801b1fc..ef4f0c34 100644 --- a/tests/core_tests/multisig_wallet_tests.cpp +++ b/tests/core_tests/multisig_wallet_tests.cpp @@ -2571,7 +2571,7 @@ bool multisig_unconfirmed_transfer_and_multiple_scan_pool_calls::c1(currency::co LOG_PRINT_YELLOW("%%%%% tx " << get_transaction_hash(tx) << " is spending multisig output " << multisig_id, LOG_LEVEL_0); bool stub; - std::deque transfers; + tools::transfer_container transfers; std::vector unconfirmed_transfers; alice_wlt->scan_tx_pool(stub); From 9c2e2bb0f8428aaf725baf2b31db7321d8a13aab Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 4 Sep 2024 23:04:44 +0400 Subject: [PATCH 004/106] multiple fixes --- src/simplewallet/simplewallet.cpp | 8 ++- src/wallet/wallet2.cpp | 39 +++++++++--- src/wallet/wallet2.h | 14 +++-- tests/core_tests/atomic_tests.cpp | 8 +-- tests/core_tests/chaingen_main.cpp | 1 + ...umulative_diificulty_adjustments_tests.cpp | 4 +- tests/core_tests/escrow_wallet_tests.cpp | 10 +-- .../isolate_auditable_and_proof.cpp | 8 +-- tests/core_tests/wallet_tests.cpp | 61 +++++++++++++++++++ tests/core_tests/wallet_tests.h | 7 +++ .../core_concurrency_test.cpp | 2 +- 11 files changed, 129 insertions(+), 33 deletions(-) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 9d842fdf..01521feb 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -1201,7 +1201,7 @@ bool simple_wallet::show_staking_history(const std::vector& args) bool transfers_found = false; for (auto it = transfers.rbegin(); it != transfers.rend(); it++) { - const auto& td = *it; + const auto& td = it->second; if (timestamp && td.m_ptx_wallet_info->m_block_timestamp < timestamp) break; @@ -1263,8 +1263,9 @@ bool simple_wallet::show_incoming_transfers(const std::vector& args m_wallet->get_transfers(transfers); bool transfers_found = false; - for (const auto& td : transfers) + for (const auto& tr : transfers) { + const auto& td = tr.second; if (!filter || available != static_cast(td.m_flags&WALLET_TRANSFER_DETAIL_FLAG_SPENT)) { if (!transfers_found) @@ -1307,8 +1308,9 @@ bool simple_wallet::show_incoming_transfers_counts(const std::vector& stop) + void wallet2::pull_blocks(size_t& blocks_added, std::atomic& stop, bool& full_reset_needed) { blocks_added = 0; currency::COMMAND_RPC_GET_BLOCKS_DIRECT::request req = AUTO_VAL_INIT(req); @@ -2028,12 +2028,16 @@ namespace tools "wrong daemon response: m_start_height=" + std::to_string(res.start_height) + " not less than local blockchain size=" + std::to_string(get_blockchain_current_size())); - handle_pulled_blocks(blocks_added, stop, res); + handle_pulled_blocks(blocks_added, stop, res, full_reset_needed); + if (full_reset_needed) + { + reset_all(); + } } //---------------------------------------------------------------------------------------------------- void wallet2::handle_pulled_blocks(size_t& blocks_added, std::atomic& stop, - currency::COMMAND_RPC_GET_BLOCKS_DIRECT::response& res) + currency::COMMAND_RPC_GET_BLOCKS_DIRECT::response& res, bool& wallet_reset_needed) { size_t current_index = res.start_height; m_last_known_daemon_height = res.current_height; @@ -2116,6 +2120,11 @@ namespace tools } //TODO: take into account date of wallet creation //reorganize + if (m_concise_mode && m_chain.get_blockchain_current_size() - (last_matched_index+1) > m_wallet_concise_mode_max_reorg_blocks) + { + wallet_reset_needed = true; + return; + } detach_blockchain(last_matched_index + 1); process_new_blockchain_entry(bl, bl_entry, bl_id, height); ++blocks_added; @@ -2853,6 +2862,7 @@ namespace tools blocks_fetched = 0; size_t added_blocks = 0; size_t try_count = 0; + size_t reset_count = 0; crypto::hash last_tx_hash_id = m_transfers.size() ? get_transaction_hash((--m_transfers.end())->second.m_ptx_wallet_info->m_tx) : null_hash; m_height_of_start_sync = get_blockchain_current_size(); m_last_sync_percent = 0; @@ -2860,7 +2870,20 @@ namespace tools { try { - pull_blocks(added_blocks, stop); + bool full_reset_needed = false; + pull_blocks(added_blocks, stop, full_reset_needed); + if (full_reset_needed) + { + if (reset_count > 1) + { + WLT_LOG_L0("Intenral error: reset_count infinit loop catch"); + if (m_wcallback) + m_wcallback->on_message(tools::i_wallet2_callback::ms_red, "Internal error: reset_count infinite loop catch"); + return; + } + reset_count++; + continue; + } blocks_fetched += added_blocks; if (!added_blocks) break; @@ -3802,10 +3825,10 @@ namespace tools } //delete from recent_history - if (m_transfer_history.size() > WALLET_CONCISE_MODE_MAX_HISTORY_SIZE) - { - m_transfer_history.erase(m_transfer_history.begin(), m_transfer_history.end() - WALLET_CONCISE_MODE_MAX_HISTORY_SIZE); - } + //if (m_transfer_history.size() > WALLET_CONCISE_MODE_MAX_HISTORY_SIZE) + //{ + // m_transfer_history.erase(m_transfer_history.begin(), m_transfer_history.end() - WALLET_CONCISE_MODE_MAX_HISTORY_SIZE); + //} return true; } diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 58f119e9..f8922d54 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -151,8 +151,7 @@ namespace tools uint64_t m_last_pow_block_h = 0; std::list> m_rollback_events; std::list > m_last_zc_global_indexs; // , biggest height comes in front - - std::atomic m_concise_mode = false; //in this mode the wallet don't keep spent entries in m_transfers as well as m_recent_transfers longer then 100 entries + //variables that not being serialized std::atomic m_last_bc_timestamp = 0; @@ -253,7 +252,7 @@ namespace tools { return; } - a& m_concise_mode; + } }; @@ -754,6 +753,8 @@ namespace tools bool is_in_hardfork_zone(uint64_t hardfork_index) const; //performance inefficient call, suitable only for rare ocasions or super lazy developers bool proxy_to_daemon(const std::string& uri, const std::string& body, int& response_code, std::string& response_body); + void set_concise_mode(bool enabled) { m_concise_mode = enabled; } + void set_concise_mode_reorg_max_reorg_blocks(uint64_t max_blocks) { m_wallet_concise_mode_max_reorg_blocks = max_blocks; } construct_tx_param get_default_construct_tx_param(); @@ -788,7 +789,7 @@ private: bool on_idle(); void unserialize_block_complete_entry(const currency::COMMAND_RPC_GET_BLOCKS_FAST::response& serialized, currency::COMMAND_RPC_GET_BLOCKS_DIRECT::response& unserialized); - void pull_blocks(size_t& blocks_added, std::atomic& stop); + void pull_blocks(size_t& blocks_added, std::atomic& stop, bool& full_reset_needed); bool prepare_free_transfers_cache(uint64_t fake_outputs_count); bool select_transfers(assets_selection_context& needed_money_map, size_t fake_outputs_count, uint64_t dust, std::vector& selected_indicies); void add_transfers_to_transfers_cache(const std::vector& indexs); @@ -809,7 +810,7 @@ private: void add_to_last_zc_global_indexs(uint64_t h, uint64_t last_zc_output_index); uint64_t get_actual_zc_global_index(); void handle_pulled_blocks(size_t& blocks_added, std::atomic& stop, - currency::COMMAND_RPC_GET_BLOCKS_DIRECT::response& blocks); + currency::COMMAND_RPC_GET_BLOCKS_DIRECT::response& blocks, bool& full_reset_needed); std::string get_alias_for_address(const std::string& addr); std::vector get_aliases_for_address(const std::string& addr); bool is_connected_to_net(); @@ -972,8 +973,9 @@ private: std::string m_votes_config_path; tools::wallet_public::wallet_vote_config m_votes_config; + std::atomic m_concise_mode = true; //in this mode the wallet don't keep spent entries in m_transfers as well as m_recent_transfers longer then 100 entries uint64_t m_last_known_daemon_height = 0; - uint64_t m_wallet_concise_mode_max_reorg_blocks = WALLET_CONCISE_MODE_MAX_REORG_BLOCKS; + uint64_t m_wallet_concise_mode_max_reorg_blocks = 5;//WALLET_CONCISE_MODE_MAX_REORG_BLOCKS; //this needed to access wallets state in coretests, for creating abnormal blocks and tranmsactions diff --git a/tests/core_tests/atomic_tests.cpp b/tests/core_tests/atomic_tests.cpp index f04d984a..bff1c87a 100644 --- a/tests/core_tests/atomic_tests.cpp +++ b/tests/core_tests/atomic_tests.cpp @@ -52,7 +52,7 @@ bool atomic_base_test::generate(std::vector& events) const test_core_time::adjust(m_genesis_timestamp); - epee::debug::get_set_enable_assert(true, true); + //epee::debug::get_set_enable_assert(true, true); currency::account_base genesis_acc; genesis_acc.generate(); @@ -68,7 +68,7 @@ bool atomic_base_test::generate(std::vector& events) const REWIND_BLOCKS_N(events, blk_0r, blk_0, m_mining_accunt, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 5); DO_CALLBACK(events, "c1"); - epee::debug::get_set_enable_assert(true, false); + //epee::debug::get_set_enable_assert(true, false); return true; } @@ -78,8 +78,8 @@ bool atomic_base_test::generate(std::vector& events) const bool atomic_simple_test::c1(currency::core& c, size_t ev_index, const std::vector& events) { - epee::debug::get_set_enable_assert(true, true); - misc_utils::auto_scope_leave_caller scope_exit_handler = misc_utils::create_scope_leave_handler([&](){epee::debug::get_set_enable_assert(true, false); }); + //epee::debug::get_set_enable_assert(true, true); + //misc_utils::auto_scope_leave_caller scope_exit_handler = misc_utils::create_scope_leave_handler([&](){epee::debug::get_set_enable_assert(true, false); }); /* diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index d31857a2..5aceba54 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -1293,6 +1293,7 @@ int main(int argc, char* argv[]) GENERATE_AND_PLAY_HF(asset_emission_and_unconfirmed_balance, "4-*"); GENERATE_AND_PLAY_HF(pos_fuse_test, "4-*"); + GENERATE_AND_PLAY_HF(wallet_reorganize_and_trim_test, "4-*"); diff --git a/tests/core_tests/cumulative_diificulty_adjustments_tests.cpp b/tests/core_tests/cumulative_diificulty_adjustments_tests.cpp index 35040e15..7e7c40b4 100644 --- a/tests/core_tests/cumulative_diificulty_adjustments_tests.cpp +++ b/tests/core_tests/cumulative_diificulty_adjustments_tests.cpp @@ -14,7 +14,7 @@ using namespace currency; cumulative_difficulty_adjustment_test::cumulative_difficulty_adjustment_test() { - epee::debug::get_set_enable_assert(true, true); + //epee::debug::get_set_enable_assert(true, true); REGISTER_CALLBACK_METHOD(cumulative_difficulty_adjustment_test, configure_core); REGISTER_CALLBACK_METHOD(cumulative_difficulty_adjustment_test, configure_check_height1); REGISTER_CALLBACK_METHOD(cumulative_difficulty_adjustment_test, memorize_main_chain); @@ -25,7 +25,7 @@ cumulative_difficulty_adjustment_test::cumulative_difficulty_adjustment_test() } cumulative_difficulty_adjustment_test::~cumulative_difficulty_adjustment_test() { - epee::debug::get_set_enable_assert(true, false); + //epee::debug::get_set_enable_assert(true, false); } #define FIRST_ALIAS_NAME "first" #define SECOND_ALIAS_NAME "second" diff --git a/tests/core_tests/escrow_wallet_tests.cpp b/tests/core_tests/escrow_wallet_tests.cpp index ea32cfd1..b511aaab 100644 --- a/tests/core_tests/escrow_wallet_tests.cpp +++ b/tests/core_tests/escrow_wallet_tests.cpp @@ -32,7 +32,7 @@ escrow_wallet_test::escrow_wallet_test() bool escrow_wallet_test::generate(std::vector& events) const { - epee::debug::get_set_enable_assert(true, true); + //epee::debug::get_set_enable_assert(true, true); currency::account_base genesis_acc; genesis_acc.generate(); @@ -47,7 +47,7 @@ bool escrow_wallet_test::generate(std::vector& events) const DO_CALLBACK(events, "c1"); - epee::debug::get_set_enable_assert(true, false); + //epee::debug::get_set_enable_assert(true, false); return true; } @@ -272,8 +272,8 @@ bool escrow_wallet_test::exec_test_with_cancel_release_type(currency::core& c, c bool escrow_wallet_test::c1(currency::core& c, size_t ev_index, const std::vector& events) { - epee::debug::get_set_enable_assert(true, true); - misc_utils::auto_scope_leave_caller scope_exit_handler = misc_utils::create_scope_leave_handler([&](){epee::debug::get_set_enable_assert(true, false); }); + //epee::debug::get_set_enable_assert(true, true); + //misc_utils::auto_scope_leave_caller scope_exit_handler = misc_utils::create_scope_leave_handler([&](){epee::debug::get_set_enable_assert(true, false); }); bool r = exec_test_with_cancel_release_type(c, events); if (!r) @@ -287,7 +287,7 @@ bool escrow_wallet_test::c1(currency::core& c, size_t ev_index, const std::vecto if (!r) return false; - epee::debug::get_set_enable_assert(true, false); + //epee::debug::get_set_enable_assert(true, false); return r; } diff --git a/tests/core_tests/isolate_auditable_and_proof.cpp b/tests/core_tests/isolate_auditable_and_proof.cpp index b363ba12..f676c8a1 100644 --- a/tests/core_tests/isolate_auditable_and_proof.cpp +++ b/tests/core_tests/isolate_auditable_and_proof.cpp @@ -32,7 +32,7 @@ bool isolate_auditable_and_proof::generate(std::vector& events test_core_time::adjust(m_genesis_timestamp); - epee::debug::get_set_enable_assert(true, true); + //epee::debug::get_set_enable_assert(true, true); currency::account_base genesis_acc; genesis_acc.generate(); @@ -47,7 +47,7 @@ bool isolate_auditable_and_proof::generate(std::vector& events REWIND_BLOCKS_N(events, blk_0r, blk_0, m_mining_accunt, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 15); DO_CALLBACK(events, "c1"); - epee::debug::get_set_enable_assert(true, false); + //epee::debug::get_set_enable_assert(true, false); return true; } @@ -61,8 +61,8 @@ bool isolate_auditable_and_proof::configure_core(currency::core& c, size_t ev_in bool isolate_auditable_and_proof::c1(currency::core& c, size_t ev_index, const std::vector& events) { - epee::debug::get_set_enable_assert(true, true); - misc_utils::auto_scope_leave_caller scope_exit_handler = misc_utils::create_scope_leave_handler([&](){epee::debug::get_set_enable_assert(true, false); }); + //epee::debug::get_set_enable_assert(true, true); + //misc_utils::auto_scope_leave_caller scope_exit_handler = misc_utils::create_scope_leave_handler([&](){epee::debug::get_set_enable_assert(true, false); }); LOG_PRINT_MAGENTA("Mining Address: " << currency::get_account_address_as_str(m_mining_accunt.get_public_address()), LOG_LEVEL_0); diff --git a/tests/core_tests/wallet_tests.cpp b/tests/core_tests/wallet_tests.cpp index cba1fadb..e6bc9f5b 100644 --- a/tests/core_tests/wallet_tests.cpp +++ b/tests/core_tests/wallet_tests.cpp @@ -3908,3 +3908,64 @@ bool block_template_blacklist_test::c1(currency::core& c, size_t ev_index, const return true; } + +wallet_reorganize_and_trim_test::wallet_reorganize_and_trim_test() +{ + REGISTER_CALLBACK_METHOD(wallet_reorganize_and_trim_test, c1); +} + + +bool wallet_reorganize_and_trim_test::generate(std::vector& events) const +{ + uint64_t ts = test_core_time::get_time(); + m_accounts.resize(1); + account_base preminer_acc; + preminer_acc.generate(); + preminer_acc.set_createtime(ts); + account_base& miner_acc = m_accounts[MINER_ACC_IDX]; miner_acc.generate(); miner_acc.set_createtime(ts); + MAKE_GENESIS_BLOCK(events, blk_0, preminer_acc, ts); + DO_CALLBACK(events, "configure_core"); + + MAKE_NEXT_BLOCK(events, blk_1, blk_0, preminer_acc); + + REWIND_BLOCKS_N_WITH_TIME(events, blk_1r, blk_1, miner_acc, 2 * CURRENCY_MINED_MONEY_UNLOCK_WINDOW - 1); + + DO_CALLBACK(events, "c1"); + return true; + +} +bool wallet_reorganize_and_trim_test::c1(currency::core& c, size_t ev_index, const std::vector& events) +{ + std::shared_ptr miner_wlt = init_playtime_test_wallet(events, c, MINER_ACC_IDX); + //mine_next_pow_blocks_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c, 2); +#define WALLET_REORGANIZE_AND_TRIM_TEST_REORG_SIZE 10 + miner_wlt->set_concise_mode(true); + miner_wlt->set_concise_mode_reorg_max_reorg_blocks(6); + + account_base acc; + acc.generate(); + std::shared_ptr alice = init_playtime_test_wallet(events, c, acc); + miner_wlt->refresh(); + miner_wlt->transfer(COIN, alice->get_account().get_public_address()); + mine_next_pow_blocks_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c, 2); + + mine_next_pow_blocks_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c, WALLET_REORGANIZE_AND_TRIM_TEST_REORG_SIZE); + uint64_t h1 = c.get_blockchain_storage().get_top_block_height(); + miner_wlt->refresh(); + uint64_t unlocked = 0; + uint64_t total = miner_wlt->balance(unlocked); + + c.get_blockchain_storage().truncate_blockchain(c.get_blockchain_storage().get_top_block_height() - (WALLET_REORGANIZE_AND_TRIM_TEST_REORG_SIZE-1)); + mine_next_pow_blocks_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c, 10); + uint64_t h2 = c.get_blockchain_storage().get_top_block_height(); + miner_wlt->refresh(); + uint64_t unlocked2 = 0; + uint64_t total2 = miner_wlt->balance(unlocked2); + if (unlocked2 != unlocked || total2 != total) + { + CHECK_AND_ASSERT_MES(false, false, "wallet concise mode check failed"); + } + return true; +} + + diff --git a/tests/core_tests/wallet_tests.h b/tests/core_tests/wallet_tests.h index cad3871e..ab7a109c 100644 --- a/tests/core_tests/wallet_tests.h +++ b/tests/core_tests/wallet_tests.h @@ -301,4 +301,11 @@ struct block_template_blacklist_test : public wallet_test block_template_blacklist_test(); bool generate(std::vector& events) const; bool c1(currency::core& c, size_t ev_index, const std::vector& events); +}; + +struct wallet_reorganize_and_trim_test : public wallet_test +{ + wallet_reorganize_and_trim_test(); + bool generate(std::vector& events) const; + bool c1(currency::core& c, size_t ev_index, const std::vector& events); }; \ No newline at end of file diff --git a/tests/functional_tests/core_concurrency_test.cpp b/tests/functional_tests/core_concurrency_test.cpp index c0b70781..3fcbc00c 100644 --- a/tests/functional_tests/core_concurrency_test.cpp +++ b/tests/functional_tests/core_concurrency_test.cpp @@ -450,7 +450,7 @@ namespace boost bool core_concurrency_test(boost::program_options::variables_map& vm, size_t wthreads, size_t rthreads, size_t blocks_count) { log_space::get_set_log_detalisation_level(true, LOG_LEVEL_0); - //epee::debug::get_set_enable_assert(true, false); + epee::debug::get_set_enable_assert(true, false); log_space::get_set_need_thread_id(true, true); cct_accounts_t accounts(s_wallets_total_count); From 1eeb9a4a818606d4da711d2af8a150119603d11f Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sun, 8 Sep 2024 22:42:31 +0400 Subject: [PATCH 005/106] wallet truncating fixed all core tests --- src/wallet/wallet2.cpp | 48 +++++++++++++++---- src/wallet/wallet2.h | 18 ++++--- src/wallet/wallet2_base.h | 3 ++ src/wallet/wallet_errors.h | 4 ++ .../escrow_wallet_altchain_test.cpp | 6 ++- tests/core_tests/wallet_test_core_proxy.cpp | 2 +- tests/core_tests/wallet_tests.cpp | 3 ++ 7 files changed, 66 insertions(+), 18 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index e92c9888..5dcb638e 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -842,7 +842,7 @@ namespace tools { const currency::txout_htlc& hltc = out_get_htlc(out_v); //mark this as spent - td.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; + td.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT|WALLET_TRANSFER_DETAIL_CONCISE_MODE_PRESERVE; //create entry for htlc input htlc_expiration_trigger het = AUTO_VAL_INIT(het); het.is_wallet_owns_redeem = (out_key == hltc.pkey_redeem) ? true : false; @@ -2028,10 +2028,27 @@ namespace tools "wrong daemon response: m_start_height=" + std::to_string(res.start_height) + " not less than local blockchain size=" + std::to_string(get_blockchain_current_size())); - handle_pulled_blocks(blocks_added, stop, res, full_reset_needed); + try + { + handle_pulled_blocks(blocks_added, stop, res, full_reset_needed); + } + catch (const tools::error::wallet_error_resync_needed& /*v*/) + { + full_reset_needed = true; + m_full_resync_requested_at_h = get_blockchain_current_size() - blocks_added; + } + if (full_reset_needed) { + //back up m_unconfirmed_txs + //back up std::unordered_map m_tx_keys; + unconfirmed_txs_container tmp_unconfirmed = m_unconfirmed_txs; + tx_secrete_keys_container tmp_secrete_keys = m_tx_keys; + crypto::hash genesis = m_chain.get_genesis(); reset_all(); + m_chain.set_genesis(genesis); + m_unconfirmed_txs = tmp_unconfirmed; + m_tx_keys = tmp_secrete_keys; } } @@ -2120,8 +2137,9 @@ namespace tools } //TODO: take into account date of wallet creation //reorganize - if (m_concise_mode && m_chain.get_blockchain_current_size() - (last_matched_index+1) > m_wallet_concise_mode_max_reorg_blocks) + if (m_concise_mode && m_chain.get_blockchain_current_size() - (last_matched_index) > m_wallet_concise_mode_max_reorg_blocks) { + m_full_resync_requested_at_h = m_chain.get_blockchain_current_size() - (last_matched_index + 1); wallet_reset_needed = true; return; } @@ -2858,6 +2876,7 @@ namespace tools { load_whitelisted_tokens_if_not_loaded(); + bool had_full_reset = false; received_money = false; blocks_fetched = 0; size_t added_blocks = 0; @@ -2882,6 +2901,8 @@ namespace tools return; } reset_count++; + m_height_of_start_sync = 0; + had_full_reset = true; continue; } blocks_fetched += added_blocks; @@ -2919,7 +2940,12 @@ namespace tools handle_expiration_list(tx_expiration_ts_median); handle_contract_expirations(tx_expiration_ts_median); m_found_free_amounts.clear(); - trim_wallet(); + truncate_wallet(); + } + if (had_full_reset) + { + blocks_fetched = get_blockchain_current_size() - m_full_resync_requested_at_h; + m_full_resync_requested_at_h = 0; } @@ -3792,28 +3818,28 @@ namespace tools return true; } //---------------------------------------------------------------------------------------------------- - bool wallet2::trim_wallet() + bool wallet2::truncate_wallet() { if (m_concise_mode) { std::list items_to_remove; for (auto& tr : m_transfers) { - if (tr.second.is_spent()) + if (tr.second.is_spent() && tr.second.m_spent_height != 0 && !(tr.second.m_flags & WALLET_TRANSFER_DETAIL_CONCISE_MODE_PRESERVE) ) { - if (m_concise_mode && tr.second.m_spent_height + m_wallet_concise_mode_max_reorg_blocks < m_chain.get_top_block_height()) + if (tr.second.m_spent_height + m_wallet_concise_mode_max_reorg_blocks < m_chain.get_top_block_height()) { items_to_remove.push_back(tr.first); } } } - return trim_transfers_and_history(items_to_remove); + return truncate_transfers_and_history(items_to_remove); } return true; } //---------------------------------------------------------------------------------------------------- - bool wallet2::trim_transfers_and_history(const std::list& items_to_remove) + bool wallet2::truncate_transfers_and_history(const std::list& items_to_remove) { //delete from m_transfers for (auto item : items_to_remove) @@ -6070,6 +6096,10 @@ namespace tools { for (auto htlc_entry : m_active_htlcs_txid) { + //auto it = m_transfers.find(htlc_entry.second); + //if (it == m_transfers.end()) + // continue; + //const transfer_details& td = it->second; const transfer_details& td = m_transfers.at(htlc_entry.second); if (only_redeem_txs && !(td.m_flags & WALLET_TRANSFER_DETAIL_FLAG_HTLC_REDEEM)) { diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index f8922d54..25220784 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -136,9 +136,9 @@ namespace tools std::unordered_map m_key_images; std::vector m_transfer_history; std::unordered_map m_unconfirmed_in_transfers; - std::unordered_map m_unconfirmed_txs; + unconfirmed_txs_container m_unconfirmed_txs; std::unordered_set m_unconfirmed_multisig_transfers; - std::unordered_map m_tx_keys; + tx_secrete_keys_container m_tx_keys; std::unordered_map m_own_asset_descriptors; std::unordered_map m_custom_assets; //assets that manually added by user mutable std::unordered_map m_whitelisted_assets; //assets that whitelisted @@ -577,8 +577,8 @@ namespace tools currency::transaction &escrow_template_tx); bool check_connection(); - bool trim_transfers_and_history(const std::list& items_to_remove); - bool trim_wallet(); + bool truncate_transfers_and_history(const std::list& items_to_remove); + bool truncate_wallet(); // PoS mining void do_pos_mining_prepare_entry(mining_context& cxt, const transfer_details& td); @@ -976,7 +976,7 @@ private: std::atomic m_concise_mode = true; //in this mode the wallet don't keep spent entries in m_transfers as well as m_recent_transfers longer then 100 entries uint64_t m_last_known_daemon_height = 0; uint64_t m_wallet_concise_mode_max_reorg_blocks = 5;//WALLET_CONCISE_MODE_MAX_REORG_BLOCKS; - + uint64_t m_full_resync_requested_at_h = 0; //this needed to access wallets state in coretests, for creating abnormal blocks and tranmsactions friend class test_generator; @@ -1089,7 +1089,13 @@ namespace tools if (tr_index != UINT64_MAX) { - transfer_details& td = m_transfers.at(tr_index); + auto it_tr = m_transfers.find(tr_index); + if (it_tr == m_transfers.end()) + { + throw tools::error::wallet_error_resync_needed(); + } + transfer_details& td = it_tr->second; + ptc.total_balance_change[td.get_asset_id()] -= td.amount(); if (td.is_native_coin()) { diff --git a/src/wallet/wallet2_base.h b/src/wallet/wallet2_base.h index 1917db17..116a27e1 100644 --- a/src/wallet/wallet2_base.h +++ b/src/wallet/wallet2_base.h @@ -52,6 +52,7 @@ #define WALLET_TRANSFER_DETAIL_FLAG_MINED_TRANSFER uint32_t(1 << 3) #define WALLET_TRANSFER_DETAIL_FLAG_COLD_SIG_RESERVATION uint32_t(1 << 4) // transfer is reserved for cold-signing (unsigned tx was created and passed for signing) #define WALLET_TRANSFER_DETAIL_FLAG_HTLC_REDEEM uint32_t(1 << 5) // for htlc keeps info if this htlc belong as redeem or as refund +#define WALLET_TRANSFER_DETAIL_CONCISE_MODE_PRESERVE uint32_t(1 << 6) // do not truncate this output with CONCISE mode @@ -516,6 +517,8 @@ namespace tools typedef std::map > free_amounts_cache_type; typedef std::unordered_map free_assets_amounts_cache_type; typedef std::unordered_map, uint64_t> amount_gindex_to_transfer_id_container; // maps [amount; gindex] -> tid + typedef std::unordered_map tx_secrete_keys_container; + typedef std::unordered_map unconfirmed_txs_container; }// namespace tools diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index f8b4e7fe..636f7bf6 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -321,6 +321,10 @@ namespace tools const currency::transaction m_tx; }; //---------------------------------------------------------------------------------------------------- + struct wallet_error_resync_needed + { + }; + struct tx_parse_error : public refresh_error { explicit tx_parse_error(std::string&& loc, const currency::blobdata& tx_blob) diff --git a/tests/core_tests/escrow_wallet_altchain_test.cpp b/tests/core_tests/escrow_wallet_altchain_test.cpp index a59063c2..514ebc82 100644 --- a/tests/core_tests/escrow_wallet_altchain_test.cpp +++ b/tests/core_tests/escrow_wallet_altchain_test.cpp @@ -317,7 +317,8 @@ bool escrow_altchain_meta_impl::c1(currency::core& c, size_t ev_index, const std alice_wlt->scan_tx_pool(stub); size_t blocks_fetched = 0; alice_wlt->refresh(blocks_fetched); - CHECK_AND_ASSERT_MES(blocks_fetched == se.expected_blocks, false, "Alice got " << blocks_fetched << " after refresh, but " << se.expected_blocks << " is expected"); + //fetched blocks disabled since resync might happened on different situation and number of blocks_fetched might be unexpected + //CHECK_AND_ASSERT_MES(blocks_fetched == se.expected_blocks, false, "Alice got " << blocks_fetched << " after refresh, but " << se.expected_blocks << " is expected"); LOG_PRINT_GREEN("Alice's transfers:" << ENDL << alice_wlt->dump_trunsfers(), LOG_LEVEL_1); if (se.a_balance != UINT64_MAX) { @@ -335,7 +336,8 @@ bool escrow_altchain_meta_impl::c1(currency::core& c, size_t ev_index, const std bob_wlt->scan_tx_pool(stub); blocks_fetched = 0; bob_wlt->refresh(blocks_fetched); - CHECK_AND_ASSERT_MES(blocks_fetched == se.expected_blocks, false, "Bob got " << blocks_fetched << " after refresh, but " << se.expected_blocks << " is expected"); + //fetched blocks disabled since resync might happened on different situation and number of blocks_fetched might be unexpected + //CHECK_AND_ASSERT_MES(blocks_fetched == se.expected_blocks, false, "Bob got " << blocks_fetched << " after refresh, but " << se.expected_blocks << " is expected"); LOG_PRINT_GREEN("Bob's transfers:" << ENDL << bob_wlt->dump_trunsfers(), LOG_LEVEL_1); if (se.b_balance != UINT64_MAX) { diff --git a/tests/core_tests/wallet_test_core_proxy.cpp b/tests/core_tests/wallet_test_core_proxy.cpp index db0fd0fc..b7da7b2e 100644 --- a/tests/core_tests/wallet_test_core_proxy.cpp +++ b/tests/core_tests/wallet_test_core_proxy.cpp @@ -89,7 +89,7 @@ bool wallet_test_core_proxy::call_COMMAND_RPC_GET_BLOCKS_FAST(const currency::CO rsp.current_height = m_blocks.size(); rsp.status = API_RETURN_CODE_OK; - if (!m_first_call) + if (!m_first_call && rsp.start_height != 0 /*second condition needed for re-sync in concise_mode*/) { m_first_call = true; return true; // respond with empty blocks on second call to gracefully stop wallet refreshing diff --git a/tests/core_tests/wallet_tests.cpp b/tests/core_tests/wallet_tests.cpp index e6bc9f5b..bb822de1 100644 --- a/tests/core_tests/wallet_tests.cpp +++ b/tests/core_tests/wallet_tests.cpp @@ -1490,6 +1490,9 @@ bool gen_wallet_decrypted_attachments::generate(std::vector& e REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); CREATE_TEST_WALLET(alice_wlt, alice_acc, blk_0); + //disable concise because this test count on on_transfer callbacks and resync cause firing on_transfer() for previous transactions + alice_wlt->set_concise_mode(false); + REFRESH_TEST_WALLET_AT_GEN_TIME(events, alice_wlt, blk_0r, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); // these attachments will be used across all the transactions in this test From 2a2987b17ad252c24320e6ebbfd369506327f7f8 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Mon, 9 Sep 2024 18:26:16 +0400 Subject: [PATCH 006/106] concise mode: last setings for production and tests --- src/wallet/wallet2.cpp | 8 ++++---- src/wallet/wallet2.h | 12 +++++++++--- tests/core_tests/chaingen.cpp | 2 ++ tests/core_tests/chaingen.h | 6 ++++-- tests/core_tests/wallet_tests_basic.h | 2 ++ 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 5dcb638e..63660085 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -3851,10 +3851,10 @@ namespace tools } //delete from recent_history - //if (m_transfer_history.size() > WALLET_CONCISE_MODE_MAX_HISTORY_SIZE) - //{ - // m_transfer_history.erase(m_transfer_history.begin(), m_transfer_history.end() - WALLET_CONCISE_MODE_MAX_HISTORY_SIZE); - //} + if (m_truncate_history_max_entries != 0 && m_transfer_history.size() > m_truncate_history_max_entries) + { + m_transfer_history.erase(m_transfer_history.begin(), m_transfer_history.end() - m_truncate_history_max_entries); + } return true; } diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 25220784..af807321 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -51,7 +51,7 @@ #define WALLET_DEFAULT_TX_SPENDABLE_AGE CURRENCY_HF4_MANDATORY_MIN_COINAGE #define WALLET_POS_MINT_CHECK_HEIGHT_INTERVAL 1 #define WALLET_CONCISE_MODE_MAX_REORG_BLOCKS CURRENCY_BLOCKS_PER_DAY * 7 //week -#define WALLET_CONCISE_MODE_MAX_HISTORY_SIZE 500 +#define WALLET_CONCISE_MODE_MOBILE_MAX_HISTORY_SIZE 500 const uint64_t WALLET_MINIMUM_HEIGHT_UNSET_CONST = std::numeric_limits::max(); @@ -755,6 +755,7 @@ namespace tools bool proxy_to_daemon(const std::string& uri, const std::string& body, int& response_code, std::string& response_body); void set_concise_mode(bool enabled) { m_concise_mode = enabled; } void set_concise_mode_reorg_max_reorg_blocks(uint64_t max_blocks) { m_wallet_concise_mode_max_reorg_blocks = max_blocks; } + void set_concise_mode_truncate_history(uint64_t max_entries) { m_truncate_history_max_entries = max_entries; } construct_tx_param get_default_construct_tx_param(); @@ -975,9 +976,14 @@ private: std::atomic m_concise_mode = true; //in this mode the wallet don't keep spent entries in m_transfers as well as m_recent_transfers longer then 100 entries uint64_t m_last_known_daemon_height = 0; - uint64_t m_wallet_concise_mode_max_reorg_blocks = 5;//WALLET_CONCISE_MODE_MAX_REORG_BLOCKS; + uint64_t m_wallet_concise_mode_max_reorg_blocks = WALLET_CONCISE_MODE_MAX_REORG_BLOCKS; uint64_t m_full_resync_requested_at_h = 0; - + uint64_t m_truncate_history_max_entries +#ifdef MOBILE_WALLET_BUILD + = WALLET_CONCISE_MODE_MOBILE_MAX_HISTORY_SIZE; +#else + = 0; +#endif //this needed to access wallets state in coretests, for creating abnormal blocks and tranmsactions friend class test_generator; }; // class wallet2 diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index 09092b43..1e108324 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -997,6 +997,8 @@ bool test_generator::init_test_wallet(const currency::account_base& account, con w->set_genesis(genesis_hash); w->set_core_proxy(m_wallet_test_core_proxy); w->set_disable_tor_relay(true); + w->set_concise_mode(true); + w->set_concise_mode_reorg_max_reorg_blocks(TESTS_CONCISE_MODE_REORG_MAX_REORG_BLOCK); result = w; return true; diff --git a/tests/core_tests/chaingen.h b/tests/core_tests/chaingen.h index d8d49100..976a6222 100644 --- a/tests/core_tests/chaingen.h +++ b/tests/core_tests/chaingen.h @@ -19,8 +19,10 @@ #define TESTS_DEFAULT_FEE ((uint64_t)TX_DEFAULT_FEE) #define MK_TEST_COINS(amount) (static_cast(amount) * TX_DEFAULT_FEE) -#define TESTS_POS_CONFIG_MIN_COINSTAKE_AGE 4 -#define TESTS_POS_CONFIG_POS_MINIMUM_HEIGH 4 +#define TESTS_POS_CONFIG_MIN_COINSTAKE_AGE 4 +#define TESTS_POS_CONFIG_POS_MINIMUM_HEIGH 4 +#define TESTS_CONCISE_MODE_REORG_MAX_REORG_BLOCK 5 + namespace concolor { diff --git a/tests/core_tests/wallet_tests_basic.h b/tests/core_tests/wallet_tests_basic.h index 6515b4a7..923376c1 100644 --- a/tests/core_tests/wallet_tests_basic.h +++ b/tests/core_tests/wallet_tests_basic.h @@ -57,6 +57,8 @@ struct wallet_test : virtual public test_chain_unit_enchanced w->set_genesis(genesis_hash); w->set_core_proxy(m_core_proxy); w->set_disable_tor_relay(true); + w->set_concise_mode(true); + w->set_concise_mode_reorg_max_reorg_blocks(TESTS_CONCISE_MODE_REORG_MAX_REORG_BLOCK); return w; } From 4e750bf1b0d833368c7654b11b04b2454db09868 Mon Sep 17 00:00:00 2001 From: sowle Date: Tue, 8 Oct 2024 03:46:40 +0200 Subject: [PATCH 007/106] an attempt to make the merge less freaking crazy --- src/wallet/wallet2.cpp | 15708 +++++++++++++++++++-------------------- 1 file changed, 7854 insertions(+), 7854 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 63660085..3e838872 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -126,673 +126,651 @@ namespace tools } return max_unlock_time; } - //---------------------------------------------------------------------------------------------------- - std::string wallet2::transfer_flags_to_str(uint32_t flags) +//---------------------------------------------------------------------------------------------------- +std::string wallet2::transfer_flags_to_str(uint32_t flags) +{ + std::string result(5, ' '); + if (flags & WALLET_TRANSFER_DETAIL_FLAG_SPENT) + result[0] = 's'; + if (flags & WALLET_TRANSFER_DETAIL_FLAG_BLOCKED) + result[1] = 'b'; + if (flags & WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION) + result[2] = 'e'; + if (flags & WALLET_TRANSFER_DETAIL_FLAG_MINED_TRANSFER) + result[3] = 'm'; + if (flags & WALLET_TRANSFER_DETAIL_FLAG_COLD_SIG_RESERVATION) + result[4] = 'c'; + return result; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::init(const std::string& daemon_address) +{ + //m_miner_text_info = PROJECT_VERSION_LONG; + m_core_proxy->set_connection_addr(daemon_address); + bool connected = m_core_proxy->check_connection(); + if (!daemon_address.empty()) { - std::string result(5, ' '); - if (flags & WALLET_TRANSFER_DETAIL_FLAG_SPENT) - result[0] = 's'; - if (flags & WALLET_TRANSFER_DETAIL_FLAG_BLOCKED) - result[1] = 'b'; - if (flags & WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION) - result[2] = 'e'; - if (flags & WALLET_TRANSFER_DETAIL_FLAG_MINED_TRANSFER) - result[3] = 'm'; - if (flags & WALLET_TRANSFER_DETAIL_FLAG_COLD_SIG_RESERVATION) - result[4] = 'c'; - return result; + WLT_LOG_L0("daemon address: " << daemon_address); + WLT_LOG_L1((connected ? "" : "not ") << "connected to daemon"); } - //---------------------------------------------------------------------------------------------------- - void wallet2::init(const std::string& daemon_address) - { - //m_miner_text_info = PROJECT_VERSION_LONG; - m_core_proxy->set_connection_addr(daemon_address); - bool connected = m_core_proxy->check_connection(); - if (!daemon_address.empty()) - { - WLT_LOG_L0("daemon address: " << daemon_address); - WLT_LOG_L1((connected ? "" : "not ") << "connected to daemon"); - } - std::stringstream ss; - const tools::wallet_public::wallet_vote_config& votes = this->get_current_votes(); - if (votes.entries.size()) + std::stringstream ss; + const tools::wallet_public::wallet_vote_config& votes = this->get_current_votes(); + if (votes.entries.size()) + { + ss << "VOTING SET LOADED:"; + for (const auto& e : votes.entries) { - ss << "VOTING SET LOADED:"; - for (const auto& e : votes.entries) - { - ss << "\t\t" << e.proposal_id << "\t\t" << (e.vote ? "1" : "0") << "\t\t(" << e.h_start << " - " << e.h_end << ")"; - } - WLT_LOG_L0(ss.str()); + ss << "\t\t" << e.proposal_id << "\t\t" << (e.vote ? "1" : "0") << "\t\t(" << e.h_start << " - " << e.h_end << ")"; + } + WLT_LOG_L0(ss.str()); + } +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::set_core_proxy(const std::shared_ptr& proxy) +{ + THROW_IF_TRUE_WALLET_EX(!proxy, error::wallet_internal_error, "Trying to set null core proxy."); + m_core_proxy = proxy; + return true; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::set_defragmentation_tx_settings(bool enabled, uint64_t min_outs, uint64_t max_outs, uint64_t max_allowed_amount, size_t decoys_count) +{ + m_defragmentation_tx_enabled = enabled; + m_min_utxo_count_for_defragmentation_tx = min_outs; + m_max_utxo_count_for_defragmentation_tx = max_outs; + m_max_allowed_output_amount_for_defragmentation_tx = max_allowed_amount; + m_decoys_count_for_defragmentation_tx = decoys_count; + if (enabled) + { + WLT_LOG_L0("Defragmentation tx creation is enabled, settings: min outs: " << min_outs << ", max outs: " << max_outs << ", max amount: " << print_money_brief(max_allowed_amount) << + ", decoys: " << (decoys_count != SIZE_MAX ? epee::string_tools::num_to_string_fast(decoys_count) : std::string("default"))); + } + else + { + WLT_LOG_L0("Defragmentation tx creation is disabled"); + } +} +//---------------------------------------------------------------------------------------------------- +std::shared_ptr wallet2::get_core_proxy() +{ + return m_core_proxy; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::get_transfer_info_by_key_image(const crypto::key_image& ki, transfer_details& td, size_t& i) +{ + auto it = m_key_images.find(ki); + if (it == m_key_images.end()) + { + return false; + } + //THROW_IF_FALSE_WALLET_EX(it->second < m_transfers.size(), error::wallet_internal_error, "wrong out in transaction: internal index"); + td = m_transfers.at(it->second); + i = it->second; + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::get_transfer_info_by_index(size_t i, transfer_details& td) +{ + //WLT_CHECK_AND_ASSERT_MES(i < m_transfers.size(), false, "wrong out in transaction: internal index, m_transfers.size()=" << m_transfers.size()); + td = m_transfers.at(i); + return true; +} +//---------------------------------------------------------------------------------------------------- +size_t wallet2::scan_for_collisions(std::unordered_map >& key_images) +{ + size_t count = 0; + for (const auto& tr : m_transfers) + { + key_images[tr.second.m_key_image].push_back(tr.first); + if (key_images[tr.second.m_key_image].size() > 1) + count++; + } + return count; +} +//---------------------------------------------------------------------------------------------------- +size_t wallet2::fix_collisions() +{ + std::unordered_map > key_images; + scan_for_collisions(key_images); + size_t count = 0; + for (auto& coll_entry : key_images) + { + if (coll_entry.second.size() < 2) + continue; + + currency::COMMAND_RPC_CHECK_KEYIMAGES::request req_ki = AUTO_VAL_INIT(req_ki); + req_ki.images.push_back(coll_entry.first); + currency::COMMAND_RPC_CHECK_KEYIMAGES::response rsp_ki = AUTO_VAL_INIT(rsp_ki); + bool r = m_core_proxy->call_COMMAND_RPC_COMMAND_RPC_CHECK_KEYIMAGES(req_ki, rsp_ki); + THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "unable to get spent key image info for keyimage: " << coll_entry.first); + THROW_IF_FALSE_WALLET_INT_ERR_EX(rsp_ki.images_stat.size() == 1, "unable to get spent key image info for keyimage: " << coll_entry.first << "keyimages size()=" << rsp_ki.images_stat.size()); + THROW_IF_FALSE_WALLET_INT_ERR_EX(*rsp_ki.images_stat.begin() != 0, "unable to get spent key image info for keyimage: " << coll_entry.first << "keyimages [0]=0"); + + + for (auto it = coll_entry.second.begin(); it != coll_entry.second.end(); it++) + { + m_transfers.at(*it).m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; + m_transfers.at(*it).m_spent_height = *rsp_ki.images_stat.begin(); + WLT_LOG_L0("Fixed collision for key image " << coll_entry.first << " transfer " << count); + count++; } } - //---------------------------------------------------------------------------------------------------- - bool wallet2::set_core_proxy(const std::shared_ptr& proxy) + + return count; +} +//---------------------------------------------------------------------------------------------------- +size_t wallet2::scan_for_transaction_entries(const crypto::hash& tx_id, const crypto::key_image& ki, std::list& details) +{ + bool check_ki = ki != currency::null_ki; + bool check_tx_id = tx_id != currency::null_hash; + + for (auto it = m_transfers.begin(); it != m_transfers.end(); it++) { - THROW_IF_TRUE_WALLET_EX(!proxy, error::wallet_internal_error, "Trying to set null core proxy."); - m_core_proxy = proxy; - return true; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::set_defragmentation_tx_settings(bool enabled, uint64_t min_outs, uint64_t max_outs, uint64_t max_allowed_amount, size_t decoys_count) - { - m_defragmentation_tx_enabled = enabled; - m_min_utxo_count_for_defragmentation_tx = min_outs; - m_max_utxo_count_for_defragmentation_tx = max_outs; - m_max_allowed_output_amount_for_defragmentation_tx = max_allowed_amount; - m_decoys_count_for_defragmentation_tx = decoys_count; - if (enabled) + if (check_ki && it->second.m_key_image == ki) { - WLT_LOG_L0("Defragmentation tx creation is enabled, settings: min outs: " << min_outs << ", max outs: " << max_outs << ", max amount: " << print_money_brief(max_allowed_amount) << - ", decoys: " << (decoys_count != SIZE_MAX ? epee::string_tools::num_to_string_fast(decoys_count) : std::string("default"))); + details.push_back(it->second); + } + if (check_tx_id && get_transaction_hash(it->second.m_ptx_wallet_info->m_tx) == tx_id) + { + details.push_back(it->second); + } + } + return details.size(); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::fetch_tx_global_indixes(const currency::transaction& tx, std::vector& goutputs_indexes) +{ + std::list> txs; + txs.push_back(tx); + std::vector > res; + fetch_tx_global_indixes(txs, res); + THROW_IF_FALSE_WALLET_INT_ERR_EX(res.size() == 1, "fetch_tx_global_indixes for single entry returned wrong result: res.size()=" << res.size()); + goutputs_indexes = res[0]; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::fetch_tx_global_indixes(const std::list>& txs, std::vector>& goutputs_indexes) +{ + currency::COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::request req = AUTO_VAL_INIT(req); + currency::COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::response res = AUTO_VAL_INIT(res); + for (auto& tx : txs) + { + req.txids.push_back(get_transaction_hash(tx)); + } + + bool r = m_core_proxy->call_COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES(req, res); + THROW_IF_TRUE_WALLET_EX(!r, error::no_connection_to_daemon, "get_o_indexes.bin"); + THROW_IF_TRUE_WALLET_EX(res.status == API_RETURN_CODE_BUSY, error::daemon_busy, "get_o_indexes.bin"); + THROW_IF_TRUE_WALLET_EX(res.status != API_RETURN_CODE_OK, error::get_out_indices_error, res.status); + THROW_IF_FALSE_WALLET_INT_ERR_EX(res.tx_global_outs.size() == txs.size(), "res.tx_global_outs.size()(" << res.tx_global_outs.size() + << ") == txs.size()(" << txs.size() << ")"); + goutputs_indexes.resize(txs.size()); + auto it_resp = res.tx_global_outs.begin(); + auto it_txs = txs.begin(); + size_t i = 0; + for (; it_resp != res.tx_global_outs.end();) + { + THROW_IF_FALSE_WALLET_INT_ERR_EX(it_resp->v.size() == it_txs->get().vout.size(), + "transactions outputs size=" << it_txs->get().vout.size() << + " not match with COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES response[i] size=" << it_resp->v.size()); + + goutputs_indexes[i] = it_resp->v; + it_resp++; it_txs++; i++; + } +} + +bool out_is_to_key(const currency::tx_out_v& out_t) +{ + if (out_t.type() == typeid(currency::tx_out_bare)) + { + return boost::get(out_t).target.type() == typeid(currency::txout_to_key); + } + return false; +} + +bool out_is_multisig(const currency::tx_out_v& out_t) +{ + if (out_t.type() == typeid(currency::tx_out_bare)) + { + return boost::get(out_t).target.type() == typeid(currency::txout_multisig); + } + return false; +} + +bool out_is_to_htlc(const currency::tx_out_v& out_t) +{ + if (out_t.type() == typeid(currency::tx_out_bare)) + { + return boost::get(out_t).target.type() == typeid(currency::txout_htlc); + } + return false; +} + +bool out_is_zc(const currency::tx_out_v& out_t) +{ + return out_t.type() == typeid(currency::tx_out_zarcanum); +} + +const currency::txout_htlc& out_get_htlc(const currency::tx_out_v& out_t) +{ + return boost::get(boost::get(out_t).target); +} + +const crypto::public_key& wallet2::out_get_pub_key(const currency::tx_out_v& out_t, std::list& htlc_info_list) +{ + if (out_t.type() == typeid(tx_out_bare)) + { + const currency::tx_out_bare& out = boost::get(out_t); + if (out.target.type() == typeid(currency::txout_to_key)) + { + return boost::get(out.target).key; } else { - WLT_LOG_L0("Defragmentation tx creation is disabled"); - } - } - //---------------------------------------------------------------------------------------------------- - std::shared_ptr wallet2::get_core_proxy() - { - return m_core_proxy; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::get_transfer_info_by_key_image(const crypto::key_image& ki, transfer_details& td, size_t& i) - { - auto it = m_key_images.find(ki); - if (it == m_key_images.end()) - { - return false; - } - //THROW_IF_FALSE_WALLET_EX(it->second < m_transfers.size(), error::wallet_internal_error, "wrong out in transaction: internal index"); - td = m_transfers.at(it->second); - i = it->second; - return true; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::get_transfer_info_by_index(size_t i, transfer_details& td) - { - //WLT_CHECK_AND_ASSERT_MES(i < m_transfers.size(), false, "wrong out in transaction: internal index, m_transfers.size()=" << m_transfers.size()); - td = m_transfers.at(i); - return true; - } - //---------------------------------------------------------------------------------------------------- - size_t wallet2::scan_for_collisions(std::unordered_map >& key_images) - { - size_t count = 0; - for (const auto& tr : m_transfers) - { - key_images[tr.second.m_key_image].push_back(tr.first); - if (key_images[tr.second.m_key_image].size() > 1) - count++; - } - return count; - } - //---------------------------------------------------------------------------------------------------- - size_t wallet2::fix_collisions() - { - std::unordered_map > key_images; - scan_for_collisions(key_images); - size_t count = 0; - for (auto& coll_entry : key_images) - { - if (coll_entry.second.size() < 2) - continue; - - currency::COMMAND_RPC_CHECK_KEYIMAGES::request req_ki = AUTO_VAL_INIT(req_ki); - req_ki.images.push_back(coll_entry.first); - currency::COMMAND_RPC_CHECK_KEYIMAGES::response rsp_ki = AUTO_VAL_INIT(rsp_ki); - bool r = m_core_proxy->call_COMMAND_RPC_COMMAND_RPC_CHECK_KEYIMAGES(req_ki, rsp_ki); - THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "unable to get spent key image info for keyimage: " << coll_entry.first); - THROW_IF_FALSE_WALLET_INT_ERR_EX(rsp_ki.images_stat.size() == 1, "unable to get spent key image info for keyimage: " << coll_entry.first << "keyimages size()=" << rsp_ki.images_stat.size()); - THROW_IF_FALSE_WALLET_INT_ERR_EX(*rsp_ki.images_stat.begin() != 0, "unable to get spent key image info for keyimage: " << coll_entry.first << "keyimages [0]=0"); - - - for (auto it = coll_entry.second.begin(); it != coll_entry.second.end(); it++) + THROW_IF_FALSE_WALLET_INT_ERR_EX(out.target.type() == typeid(currency::txout_htlc), "Unexpected out type in target wallet: " << out.target.type().name()); + THROW_IF_FALSE_WALLET_INT_ERR_EX(htlc_info_list.size() > 0, "Found txout_htlc out but htlc_info_list is empty"); + bool hltc_our_out_is_before_expiration = htlc_info_list.front().hltc_our_out_is_before_expiration; + htlc_info_list.pop_front(); + if (hltc_our_out_is_before_expiration) { - m_transfers.at(*it).m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; - m_transfers.at(*it).m_spent_height = *rsp_ki.images_stat.begin(); - WLT_LOG_L0("Fixed collision for key image " << coll_entry.first << " transfer " << count); - count++; - } - } - - return count; - } - //---------------------------------------------------------------------------------------------------- - size_t wallet2::scan_for_transaction_entries(const crypto::hash& tx_id, const crypto::key_image& ki, std::list& details) - { - bool check_ki = ki != currency::null_ki; - bool check_tx_id = tx_id != currency::null_hash; - - for (auto it = m_transfers.begin(); it != m_transfers.end(); it++) - { - if (check_ki && it->second.m_key_image == ki) - { - details.push_back(it->second); - } - if (check_tx_id && get_transaction_hash(it->second.m_ptx_wallet_info->m_tx) == tx_id) - { - details.push_back(it->second); - } - } - return details.size(); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::fetch_tx_global_indixes(const currency::transaction& tx, std::vector& goutputs_indexes) - { - std::list> txs; - txs.push_back(tx); - std::vector > res; - fetch_tx_global_indixes(txs, res); - THROW_IF_FALSE_WALLET_INT_ERR_EX(res.size() == 1, "fetch_tx_global_indixes for single entry returned wrong result: res.size()=" << res.size()); - goutputs_indexes = res[0]; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::fetch_tx_global_indixes(const std::list>& txs, std::vector>& goutputs_indexes) - { - currency::COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::request req = AUTO_VAL_INIT(req); - currency::COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES::response res = AUTO_VAL_INIT(res); - for (auto& tx : txs) - { - req.txids.push_back(get_transaction_hash(tx)); - } - - bool r = m_core_proxy->call_COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES(req, res); - THROW_IF_TRUE_WALLET_EX(!r, error::no_connection_to_daemon, "get_o_indexes.bin"); - THROW_IF_TRUE_WALLET_EX(res.status == API_RETURN_CODE_BUSY, error::daemon_busy, "get_o_indexes.bin"); - THROW_IF_TRUE_WALLET_EX(res.status != API_RETURN_CODE_OK, error::get_out_indices_error, res.status); - THROW_IF_FALSE_WALLET_INT_ERR_EX(res.tx_global_outs.size() == txs.size(), "res.tx_global_outs.size()(" << res.tx_global_outs.size() - << ") == txs.size()(" << txs.size() << ")"); - goutputs_indexes.resize(txs.size()); - auto it_resp = res.tx_global_outs.begin(); - auto it_txs = txs.begin(); - size_t i = 0; - for (; it_resp != res.tx_global_outs.end();) - { - THROW_IF_FALSE_WALLET_INT_ERR_EX(it_resp->v.size() == it_txs->get().vout.size(), - "transactions outputs size=" << it_txs->get().vout.size() << - " not match with COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES response[i] size=" << it_resp->v.size()); - - goutputs_indexes[i] = it_resp->v; - it_resp++; it_txs++; i++; - } - } - - bool out_is_to_key(const currency::tx_out_v& out_t) - { - if (out_t.type() == typeid(currency::tx_out_bare)) - { - return boost::get(out_t).target.type() == typeid(currency::txout_to_key); - } - return false; - } - - bool out_is_multisig(const currency::tx_out_v& out_t) - { - if (out_t.type() == typeid(currency::tx_out_bare)) - { - return boost::get(out_t).target.type() == typeid(currency::txout_multisig); - } - return false; - } - - bool out_is_to_htlc(const currency::tx_out_v& out_t) - { - if (out_t.type() == typeid(currency::tx_out_bare)) - { - return boost::get(out_t).target.type() == typeid(currency::txout_htlc); - } - return false; - } - - bool out_is_zc(const currency::tx_out_v& out_t) - { - return out_t.type() == typeid(currency::tx_out_zarcanum); - } - - const currency::txout_htlc& out_get_htlc(const currency::tx_out_v& out_t) - { - return boost::get(boost::get(out_t).target); - } - - const crypto::public_key& wallet2::out_get_pub_key(const currency::tx_out_v& out_t, std::list& htlc_info_list) - { - if (out_t.type() == typeid(tx_out_bare)) - { - const currency::tx_out_bare& out = boost::get(out_t); - if (out.target.type() == typeid(currency::txout_to_key)) - { - return boost::get(out.target).key; + return boost::get(out.target).pkey_redeem; } else { - THROW_IF_FALSE_WALLET_INT_ERR_EX(out.target.type() == typeid(currency::txout_htlc), "Unexpected out type in target wallet: " << out.target.type().name()); - THROW_IF_FALSE_WALLET_INT_ERR_EX(htlc_info_list.size() > 0, "Found txout_htlc out but htlc_info_list is empty"); - bool hltc_our_out_is_before_expiration = htlc_info_list.front().hltc_our_out_is_before_expiration; - htlc_info_list.pop_front(); - if (hltc_our_out_is_before_expiration) + return boost::get(out.target).pkey_refund; + } + } + } + else + { + THROW_IF_FALSE_WALLET_INT_ERR_EX(out_t.type() == typeid(currency::tx_out_zarcanum), "Unexpected out type im wallet: " << out_t.type().name()); + return boost::get(out_t).stealth_address; + } +} +//---------------------------------------------------------------------------------------------------- +void wallet2::process_ado_in_new_transaction(const currency::asset_descriptor_operation& ado, process_transaction_context& ptc) +{ + do + { + crypto::public_key asset_id{}; + if (ado.operation_type != ASSET_DESCRIPTOR_OPERATION_UNDEFINED) + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(get_or_calculate_asset_id(ado, nullptr, &asset_id), "get_or_calculate_asset_id failed"); + + if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER) + { + if (ado.descriptor.owner != m_account.get_public_address().spend_public_key) + break; + + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(m_own_asset_descriptors.count(asset_id) == 0, "asset with asset_id " << asset_id << " has already been registered in the wallet as own asset"); + wallet_own_asset_context& asset_context = m_own_asset_descriptors[asset_id]; + epee::misc_utils::cast_assign_a_to_b(asset_context, ado.descriptor); + //*static_cast(&asset_context) = ado.descriptor; + + std::stringstream ss; + ss << "New Asset Registered:" + << ENDL << "asset id: " << asset_id + << ENDL << "Name: " << asset_context.full_name + << ENDL << "Ticker: " << asset_context.ticker + << ENDL << "Total Max Supply: " << print_asset_money(asset_context.total_max_supply, asset_context.decimal_point) + << ENDL << "Current Supply: " << print_asset_money(asset_context.current_supply, asset_context.decimal_point) + << ENDL << "Decimal Point: " << (int)asset_context.decimal_point; + + + add_rollback_event(ptc.height, asset_register_event{ asset_id }); + WLT_LOG_MAGENTA(ss.str(), LOG_LEVEL_0); + if (m_wcallback) + m_wcallback->on_message(i_wallet2_callback::ms_yellow, ss.str()); + } + else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT || ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN) + { + auto it = m_own_asset_descriptors.find(asset_id); + if (it == m_own_asset_descriptors.end()) + break; + //asset had been updated + add_rollback_event(ptc.height, asset_update_event{ it->first, it->second }); + epee::misc_utils::cast_assign_a_to_b(it->second, ado.descriptor); + + } + else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE) + { + auto it = m_own_asset_descriptors.find(asset_id); + if (it == m_own_asset_descriptors.end()) + { + if (ado.descriptor.owner == m_account.get_public_address().spend_public_key) { - return boost::get(out.target).pkey_redeem; + // ownership of the asset acquired + + wallet_own_asset_context& asset_context = m_own_asset_descriptors[asset_id]; + epee::misc_utils::cast_assign_a_to_b(asset_context, ado.descriptor); + + std::stringstream ss; + ss << "Asset ownership acquired:" + << ENDL << "asset id: " << asset_id + << ENDL << "Name: " << ado.descriptor.full_name + << ENDL << "Ticker: " << ado.descriptor.ticker + << ENDL << "Total Max Supply: " << print_asset_money(ado.descriptor.total_max_supply, ado.descriptor.decimal_point) + << ENDL << "Current Supply: " << print_asset_money(ado.descriptor.current_supply, ado.descriptor.decimal_point) + << ENDL << "Decimal Point: " << (int)ado.descriptor.decimal_point; + + + add_rollback_event(ptc.height, asset_register_event{ asset_id }); + WLT_LOG_MAGENTA(ss.str(), LOG_LEVEL_0); + if (m_wcallback) + m_wcallback->on_message(i_wallet2_callback::ms_yellow, ss.str()); } else { - return boost::get(out.target).pkey_refund; + // update event of the asset that we not control, skip + break; } } - } - else - { - THROW_IF_FALSE_WALLET_INT_ERR_EX(out_t.type() == typeid(currency::tx_out_zarcanum), "Unexpected out type im wallet: " << out_t.type().name()); - return boost::get(out_t).stealth_address; - } - } - //---------------------------------------------------------------------------------------------------- - void wallet2::process_ado_in_new_transaction(const currency::asset_descriptor_operation& ado, process_transaction_context& ptc) - { - do - { - crypto::public_key asset_id{}; - if (ado.operation_type != ASSET_DESCRIPTOR_OPERATION_UNDEFINED) - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(get_or_calculate_asset_id(ado, nullptr, &asset_id), "get_or_calculate_asset_id failed"); - - if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER) + else { - if (ado.descriptor.owner != m_account.get_public_address().spend_public_key) - break; - - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(m_own_asset_descriptors.count(asset_id) == 0, "asset with asset_id " << asset_id << " has already been registered in the wallet as own asset"); - wallet_own_asset_context& asset_context = m_own_asset_descriptors[asset_id]; - epee::misc_utils::cast_assign_a_to_b(asset_context, ado.descriptor); - //*static_cast(&asset_context) = ado.descriptor; - - std::stringstream ss; - ss << "New Asset Registered:" - << ENDL << "asset id: " << asset_id - << ENDL << "Name: " << asset_context.full_name - << ENDL << "Ticker: " << asset_context.ticker - << ENDL << "Total Max Supply: " << print_asset_money(asset_context.total_max_supply, asset_context.decimal_point) - << ENDL << "Current Supply: " << print_asset_money(asset_context.current_supply, asset_context.decimal_point) - << ENDL << "Decimal Point: " << (int)asset_context.decimal_point; - - - add_rollback_event(ptc.height, asset_register_event{ asset_id }); - WLT_LOG_MAGENTA(ss.str(), LOG_LEVEL_0); - if (m_wcallback) - m_wcallback->on_message(i_wallet2_callback::ms_yellow, ss.str()); - } - else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT || ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN) - { - auto it = m_own_asset_descriptors.find(asset_id); - if (it == m_own_asset_descriptors.end()) - break; - //asset had been updated - add_rollback_event(ptc.height, asset_update_event{ it->first, it->second }); - epee::misc_utils::cast_assign_a_to_b(it->second, ado.descriptor); - - } - else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE) - { - auto it = m_own_asset_descriptors.find(asset_id); - if (it == m_own_asset_descriptors.end()) + //update event for asset that we control, check if ownership is still ours + if (ado.descriptor.owner != m_account.get_public_address().spend_public_key && !it->second.thirdparty_custody) { - if (ado.descriptor.owner == m_account.get_public_address().spend_public_key) - { - // ownership of the asset acquired + //ownership of the asset had been transfered + add_rollback_event(ptc.height, asset_unown_event{ it->first, it->second }); + m_own_asset_descriptors.erase(it); - wallet_own_asset_context& asset_context = m_own_asset_descriptors[asset_id]; - epee::misc_utils::cast_assign_a_to_b(asset_context, ado.descriptor); + std::stringstream ss; + ss << "Asset ownership lost:" + << ENDL << "asset id: " << asset_id + << ENDL << "New owner: " << ado.descriptor.owner + << ENDL << "Name: " << ado.descriptor.full_name + << ENDL << "Ticker: " << ado.descriptor.ticker + << ENDL << "Total Max Supply: " << print_asset_money(ado.descriptor.total_max_supply, ado.descriptor.decimal_point) + << ENDL << "Current Supply: " << print_asset_money(ado.descriptor.current_supply, ado.descriptor.decimal_point) + << ENDL << "Decimal Point: " << (int)ado.descriptor.decimal_point; - std::stringstream ss; - ss << "Asset ownership acquired:" - << ENDL << "asset id: " << asset_id - << ENDL << "Name: " << ado.descriptor.full_name - << ENDL << "Ticker: " << ado.descriptor.ticker - << ENDL << "Total Max Supply: " << print_asset_money(ado.descriptor.total_max_supply, ado.descriptor.decimal_point) - << ENDL << "Current Supply: " << print_asset_money(ado.descriptor.current_supply, ado.descriptor.decimal_point) - << ENDL << "Decimal Point: " << (int)ado.descriptor.decimal_point; - - - add_rollback_event(ptc.height, asset_register_event{ asset_id }); - WLT_LOG_MAGENTA(ss.str(), LOG_LEVEL_0); - if (m_wcallback) - m_wcallback->on_message(i_wallet2_callback::ms_yellow, ss.str()); - } - else - { - // update event of the asset that we not control, skip - break; - } + add_rollback_event(ptc.height, asset_register_event{ asset_id }); + WLT_LOG_MAGENTA(ss.str(), LOG_LEVEL_0); + if (m_wcallback) + m_wcallback->on_message(i_wallet2_callback::ms_yellow, ss.str()); } else { - //update event for asset that we control, check if ownership is still ours - if (ado.descriptor.owner != m_account.get_public_address().spend_public_key && !it->second.thirdparty_custody) - { - //ownership of the asset had been transfered - add_rollback_event(ptc.height, asset_unown_event{ it->first, it->second }); - m_own_asset_descriptors.erase(it); + //just an update of the asset + add_rollback_event(ptc.height, asset_update_event{ it->first, it->second }); + epee::misc_utils::cast_assign_a_to_b(it->second, ado.descriptor); - std::stringstream ss; - ss << "Asset ownership lost:" - << ENDL << "asset id: " << asset_id - << ENDL << "New owner: " << ado.descriptor.owner - << ENDL << "Name: " << ado.descriptor.full_name - << ENDL << "Ticker: " << ado.descriptor.ticker - << ENDL << "Total Max Supply: " << print_asset_money(ado.descriptor.total_max_supply, ado.descriptor.decimal_point) - << ENDL << "Current Supply: " << print_asset_money(ado.descriptor.current_supply, ado.descriptor.decimal_point) - << ENDL << "Decimal Point: " << (int)ado.descriptor.decimal_point; - - add_rollback_event(ptc.height, asset_register_event{ asset_id }); - WLT_LOG_MAGENTA(ss.str(), LOG_LEVEL_0); - if (m_wcallback) - m_wcallback->on_message(i_wallet2_callback::ms_yellow, ss.str()); - } - else - { - //just an update of the asset - add_rollback_event(ptc.height, asset_update_event{ it->first, it->second }); - epee::misc_utils::cast_assign_a_to_b(it->second, ado.descriptor); - - } } } - } while (false); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::add_rollback_event(uint64_t h, const wallet_event_t& ev) - { - m_rollback_events.emplace_back(h, ev); - } - //---------------------------------------------------------------------------------------------------- + } + } while (false); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::add_rollback_event(uint64_t h, const wallet_event_t& ev) +{ + m_rollback_events.emplace_back(h, ev); +} +//---------------------------------------------------------------------------------------------------- #define M_LAST_ZC_GLOBAL_INDEXS_MAX_SIZE 30 - void wallet2::add_to_last_zc_global_indexs(uint64_t h, uint64_t last_zc_output_index) +void wallet2::add_to_last_zc_global_indexs(uint64_t h, uint64_t last_zc_output_index) +{ + if (m_last_zc_global_indexs.size()) { - if (m_last_zc_global_indexs.size()) - { - if (h > m_last_zc_global_indexs.begin()->first) - { - //new block added on top of last one, simply add new record - m_last_zc_global_indexs.push_front(std::make_pair(h, last_zc_output_index)); - } - else if (h < m_last_zc_global_indexs.begin()->first) - { - //looks like reorganize, pop all records before - while (m_last_zc_global_indexs.size() && m_last_zc_global_indexs.begin()->first >= h) - { - m_last_zc_global_indexs.erase(m_last_zc_global_indexs.begin()); - } - m_last_zc_global_indexs.push_front(std::make_pair(h, last_zc_output_index)); - } - else - { - //@#@ -#ifdef _DEBUG - if (m_last_zc_global_indexs.begin()->second > last_zc_output_index) - { - LOG_ERROR("!!!!!!!!!!!!!!!!!"); - } -#endif - //equals, same h but new last_zc_output_index, just update it, should be always bigger then prev - WLT_THROW_IF_FALSE_WITH_CODE(m_last_zc_global_indexs.begin()->second <= last_zc_output_index, - "condition m_last_zc_global_indexs.begin()->second " << m_last_zc_global_indexs.begin()->second << " <= last_zc_output_index " << last_zc_output_index << " failed", API_RETURN_CODE_INTERNAL_ERROR); - m_last_zc_global_indexs.begin()->second = last_zc_output_index; - } - } - else + if (h > m_last_zc_global_indexs.begin()->first) { //new block added on top of last one, simply add new record m_last_zc_global_indexs.push_front(std::make_pair(h, last_zc_output_index)); } - - if (m_last_zc_global_indexs.size() > M_LAST_ZC_GLOBAL_INDEXS_MAX_SIZE) - m_last_zc_global_indexs.pop_back(); + else if (h < m_last_zc_global_indexs.begin()->first) + { + //looks like reorganize, pop all records before + while (m_last_zc_global_indexs.size() && m_last_zc_global_indexs.begin()->first >= h) + { + m_last_zc_global_indexs.erase(m_last_zc_global_indexs.begin()); + } + m_last_zc_global_indexs.push_front(std::make_pair(h, last_zc_output_index)); + } + else + { + //@#@ +#ifdef _DEBUG + if (m_last_zc_global_indexs.begin()->second > last_zc_output_index) + { + LOG_ERROR("!!!!!!!!!!!!!!!!!"); + } +#endif + //equals, same h but new last_zc_output_index, just update it, should be always bigger then prev + WLT_THROW_IF_FALSE_WITH_CODE(m_last_zc_global_indexs.begin()->second <= last_zc_output_index, + "condition m_last_zc_global_indexs.begin()->second " << m_last_zc_global_indexs.begin()->second << " <= last_zc_output_index " << last_zc_output_index << " failed", API_RETURN_CODE_INTERNAL_ERROR); + m_last_zc_global_indexs.begin()->second = last_zc_output_index; + } } - //---------------------------------------------------------------------------------------------------- - uint64_t wallet2::get_actual_zc_global_index() + else { - WLT_THROW_IF_FALSE_WITH_CODE(m_last_zc_global_indexs.size(), "m_last_zc_global_indexs is empty", API_RETURN_CODE_INTERNAL_ERROR); - for (auto it = m_last_zc_global_indexs.begin(); it != m_last_zc_global_indexs.end(); it++) - { - if (it->first <= m_last_known_daemon_height - WALLET_DEFAULT_TX_SPENDABLE_AGE) - { - return it->second; - } - } - WLT_THROW_IF_FALSE_WITH_CODE(false, "doesn't have anything that match expected height = " << m_last_known_daemon_height - WALLET_DEFAULT_TX_SPENDABLE_AGE, API_RETURN_CODE_INTERNAL_ERROR); - throw std::runtime_error(""); //mostly to suppress compiler warning + //new block added on top of last one, simply add new record + m_last_zc_global_indexs.push_front(std::make_pair(h, last_zc_output_index)); } - //---------------------------------------------------------------------------------------------------- - void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t height, const currency::block& b, const std::vector* pglobal_indexes) + + if (m_last_zc_global_indexs.size() > M_LAST_ZC_GLOBAL_INDEXS_MAX_SIZE) + m_last_zc_global_indexs.pop_back(); +} +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::get_actual_zc_global_index() +{ + WLT_THROW_IF_FALSE_WITH_CODE(m_last_zc_global_indexs.size(), "m_last_zc_global_indexs is empty", API_RETURN_CODE_INTERNAL_ERROR); + for (auto it = m_last_zc_global_indexs.begin(); it != m_last_zc_global_indexs.end(); it++) { - //check for transaction spends - process_transaction_context ptc(tx); - - process_unconfirmed(tx, ptc.recipients, ptc.remote_aliases); - - // check all outputs for spending (compare key images) - ptc.coin_base_tx = is_coinbase(tx, ptc.is_pos_coinbase); - //PoW block don't have change, so all outs supposed to be marked as "mined" - ptc.is_derived_from_coinbase = !ptc.is_pos_coinbase; - ptc.height = height; - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(pglobal_indexes, "pglobal_indexes not set"); - if (this->is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM)) + if (it->first <= m_last_known_daemon_height - WALLET_DEFAULT_TX_SPENDABLE_AGE) { - if (pglobal_indexes->size()) + return it->second; + } + } + WLT_THROW_IF_FALSE_WITH_CODE(false, "doesn't have anything that match expected height = " << m_last_known_daemon_height - WALLET_DEFAULT_TX_SPENDABLE_AGE, API_RETURN_CODE_INTERNAL_ERROR); + throw std::runtime_error(""); //mostly to suppress compiler warning +} +//---------------------------------------------------------------------------------------------------- +void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t height, const currency::block& b, const std::vector* pglobal_indexes) +{ + //check for transaction spends + process_transaction_context ptc(tx); + + process_unconfirmed(tx, ptc.recipients, ptc.remote_aliases); + + // check all outputs for spending (compare key images) + ptc.coin_base_tx = is_coinbase(tx, ptc.is_pos_coinbase); + //PoW block don't have change, so all outs supposed to be marked as "mined" + ptc.is_derived_from_coinbase = !ptc.is_pos_coinbase; + ptc.height = height; + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(pglobal_indexes, "pglobal_indexes not set"); + if (this->is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM)) + { + if (pglobal_indexes->size()) + { + //@#@ + WLT_LOG_L2("add_to_last_zc_global_indexs: h: " << height << ", b: " << currency::get_block_hash(b) << " , tx: " << currency::get_transaction_hash(tx) << ", last_zc_output_index: " << pglobal_indexes->back()); + add_to_last_zc_global_indexs(ptc.height, pglobal_indexes->back()); + } + } + + for (auto& in : tx.vin) + { + ptc.sub_i = 0; + VARIANT_SWITCH_BEGIN(in); + VARIANT_CASE_CONST(currency::txin_to_key, intk) + { + process_input_t(intk, ptc, tx); + } + VARIANT_CASE_CONST(currency::txin_zc_input, in_zc) + { + process_input_t(in_zc, ptc, tx); + //ptc.sub_i++; + } + VARIANT_CASE_CONST(currency::txin_multisig, inms) + { + crypto::hash multisig_id = inms.multisig_out_id; + auto it = m_multisig_transfers.find(multisig_id); + if (it != m_multisig_transfers.end()) { - //@#@ - WLT_LOG_L2("add_to_last_zc_global_indexs: h: " << height << ", b: " << currency::get_block_hash(b) << " , tx: " << currency::get_transaction_hash(tx) << ", last_zc_output_index: " << pglobal_indexes->back()); - add_to_last_zc_global_indexs(ptc.height, pglobal_indexes->back()); + it->second.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; + it->second.m_spent_height = height; + WLT_LOG_L0("Spent multisig out: " << multisig_id << ", amount: " << print_money(currency::get_amount_from_variant(in)) << ", with tx: " << ptc.tx_hash() << ", at height " << height); + ptc.employed_entries.spent.push_back(wallet_public::employed_tx_entry{ ptc.i }); + } + } + VARIANT_CASE_CONST(currency::txin_htlc, in_htlc) + { + if (in_htlc.key_offsets.size() != 1) + { + LOG_ERROR("in_htlc.key_offsets.size() != 1, skip inout"); + continue; + } + + if (in_htlc.key_offsets[0].type() != typeid(uint64_t)) + { + LOG_ERROR("HTLC with ref_by_id is not supported by wallet yet"); + continue; + } + + auto it = m_active_htlcs.find(std::make_pair(in_htlc.amount, boost::get(in_htlc.key_offsets[0]))); + if (it != m_active_htlcs.end()) + { + transfer_details& td = m_transfers.at(it->second); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.m_ptx_wallet_info->m_tx.vout.size() > td.m_internal_output_index, "Internal error: wrong td.m_internal_output_index: " << td.m_internal_output_index); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].type() == typeid(tx_out_bare), "Internal error: wrong output type: " << td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].type().name()); + const boost::typeindex::type_info& ti = boost::get(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]).target.type(); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(ti == typeid(txout_htlc), "Internal error: wrong type of output's target: " << ti.name()); + //input spend active htlc + m_transfers.at(it->second).m_spent_height = height; + transfer_details_extra_option_htlc_info& tdeohi = get_or_add_field_to_variant_vector(td.varian_options); + tdeohi.origin = in_htlc.hltc_origin; + tdeohi.redeem_tx_id = ptc.tx_hash(); + } + } + VARIANT_SWITCH_END(); + ptc.i++; + } + + /* + collect unlock_time from every output that transfered coins to this account and use maximum of + all values m_payments entry, use this strict policy is required to protect exchanges from being feeded with + useless outputs + */ + ptc.max_out_unlock_time = 0; + + std::vector outs; + //uint64_t sum_of_native_outs = 0; // TODO: @#@# correctly calculate tx_money_got_in_outs for post-HF4 + ptc.tx_pub_key = null_pkey; + bool r = parse_and_validate_tx_extra(tx, ptc.tx_pub_key); + THROW_IF_TRUE_WALLET_EX(!r, error::tx_extra_parse_error, tx); + + //check for transaction income + crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); + std::list htlc_info_list; + r = lookup_acc_outs(m_account.get_keys(), tx, ptc.tx_pub_key, outs, derivation, htlc_info_list); + THROW_IF_TRUE_WALLET_EX(!r, error::acc_outs_lookup_error, tx, ptc.tx_pub_key, m_account.get_keys()); + + if (!outs.empty()) + { + //good news - got money! take care about it + //usually we have only one transfer for user in transaction + + //create once instance of tx for all entries + std::shared_ptr pwallet_info(new transaction_wallet_info()); + pwallet_info->m_tx = tx; + pwallet_info->m_block_height = height; + pwallet_info->m_block_timestamp = b.timestamp; + + if (is_auditable()) + { + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(pglobal_indexes && pglobal_indexes->size() == tx.vout.size(), "wrong pglobal_indexes = " << pglobal_indexes << ""); + } + std::vector outputs_index_local; + + if (!pglobal_indexes || (pglobal_indexes->size() == 0 && tx.vout.size() != 0)) + { + //if tx contain htlc_out, then we would need global_indexes anyway, to be able later detect redeem of htlc + if (m_use_deffered_global_outputs && htlc_info_list.size() == 0) + { + pglobal_indexes = nullptr; + } + else + { + fetch_tx_global_indixes(tx, outputs_index_local); + pglobal_indexes = &outputs_index_local; } } - for (auto& in : tx.vin) + for (size_t i_in_outs = 0; i_in_outs != outs.size(); i_in_outs++) { - ptc.sub_i = 0; - VARIANT_SWITCH_BEGIN(in); - VARIANT_CASE_CONST(currency::txin_to_key, intk) + const wallet_out_info& out = outs[i_in_outs]; + size_t o = out.index; + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(o < tx.vout.size(), "wrong out in transaction: internal index: " << o << ", tx.vout.size(): " << tx.vout.size()); { - process_input_t(intk, ptc, tx); - } - VARIANT_CASE_CONST(currency::txin_zc_input, in_zc) - { - process_input_t(in_zc, ptc, tx); - //ptc.sub_i++; - } - VARIANT_CASE_CONST(currency::txin_multisig, inms) - { - crypto::hash multisig_id = inms.multisig_out_id; - auto it = m_multisig_transfers.find(multisig_id); - if (it != m_multisig_transfers.end()) + const currency::tx_out_v& out_v = tx.vout[o]; + bool out_type_zc = out_is_zc(out_v); + bool out_type_to_key = out_is_to_key(out_v); + bool out_type_htlc = out_is_to_htlc(out_v); + bool out_type_multisig = out_is_multisig(out_v); + + if (out_type_zc || out_type_to_key || out_type_htlc) { - it->second.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; - it->second.m_spent_height = height; - WLT_LOG_L0("Spent multisig out: " << multisig_id << ", amount: " << print_money(currency::get_amount_from_variant(in)) << ", with tx: " << ptc.tx_hash() << ", at height " << height); - ptc.employed_entries.spent.push_back(wallet_public::employed_tx_entry{ ptc.i }); - } - } - VARIANT_CASE_CONST(currency::txin_htlc, in_htlc) - { - if (in_htlc.key_offsets.size() != 1) - { - LOG_ERROR("in_htlc.key_offsets.size() != 1, skip inout"); - continue; - } + crypto::public_key out_key = out_get_pub_key(out_v, htlc_info_list); // htlc_info_list contains information about which one, redeem or refund key is ours for an htlc output - if (in_htlc.key_offsets[0].type() != typeid(uint64_t)) - { - LOG_ERROR("HTLC with ref_by_id is not supported by wallet yet"); - continue; - } - - auto it = m_active_htlcs.find(std::make_pair(in_htlc.amount, boost::get(in_htlc.key_offsets[0]))); - if (it != m_active_htlcs.end()) - { - transfer_details& td = m_transfers.at(it->second); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.m_ptx_wallet_info->m_tx.vout.size() > td.m_internal_output_index, "Internal error: wrong td.m_internal_output_index: " << td.m_internal_output_index); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].type() == typeid(tx_out_bare), "Internal error: wrong output type: " << td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].type().name()); - const boost::typeindex::type_info& ti = boost::get(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]).target.type(); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(ti == typeid(txout_htlc), "Internal error: wrong type of output's target: " << ti.name()); - //input spend active htlc - m_transfers.at(it->second).m_spent_height = height; - transfer_details_extra_option_htlc_info& tdeohi = get_or_add_field_to_variant_vector(td.varian_options); - tdeohi.origin = in_htlc.hltc_origin; - tdeohi.redeem_tx_id = ptc.tx_hash(); - } - } - VARIANT_SWITCH_END(); - ptc.i++; - } - - /* - collect unlock_time from every output that transfered coins to this account and use maximum of - all values m_payments entry, use this strict policy is required to protect exchanges from being feeded with - useless outputs - */ - ptc.max_out_unlock_time = 0; - - std::vector outs; - //uint64_t sum_of_native_outs = 0; // TODO: @#@# correctly calculate tx_money_got_in_outs for post-HF4 - ptc.tx_pub_key = null_pkey; - bool r = parse_and_validate_tx_extra(tx, ptc.tx_pub_key); - THROW_IF_TRUE_WALLET_EX(!r, error::tx_extra_parse_error, tx); - - //check for transaction income - crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); - std::list htlc_info_list; - r = lookup_acc_outs(m_account.get_keys(), tx, ptc.tx_pub_key, outs, derivation, htlc_info_list); - THROW_IF_TRUE_WALLET_EX(!r, error::acc_outs_lookup_error, tx, ptc.tx_pub_key, m_account.get_keys()); - - if (!outs.empty()) - { - //good news - got money! take care about it - //usually we have only one transfer for user in transaction - - //create once instance of tx for all entries - std::shared_ptr pwallet_info(new transaction_wallet_info()); - pwallet_info->m_tx = tx; - pwallet_info->m_block_height = height; - pwallet_info->m_block_timestamp = b.timestamp; - - if (is_auditable()) - { - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(pglobal_indexes && pglobal_indexes->size() == tx.vout.size(), "wrong pglobal_indexes = " << pglobal_indexes << ""); - } - std::vector outputs_index_local; - - if (!pglobal_indexes || (pglobal_indexes->size() == 0 && tx.vout.size() != 0)) - { - //if tx contain htlc_out, then we would need global_indexes anyway, to be able later detect redeem of htlc - if (m_use_deffered_global_outputs && htlc_info_list.size() == 0) - { - pglobal_indexes = nullptr; - } - else - { - fetch_tx_global_indixes(tx, outputs_index_local); - pglobal_indexes = &outputs_index_local; - } - } - - for (size_t i_in_outs = 0; i_in_outs != outs.size(); i_in_outs++) - { - const wallet_out_info& out = outs[i_in_outs]; - size_t o = out.index; - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(o < tx.vout.size(), "wrong out in transaction: internal index: " << o << ", tx.vout.size(): " << tx.vout.size()); - { - const currency::tx_out_v& out_v = tx.vout[o]; - bool out_type_zc = out_is_zc(out_v); - bool out_type_to_key = out_is_to_key(out_v); - bool out_type_htlc = out_is_to_htlc(out_v); - bool out_type_multisig = out_is_multisig(out_v); - - if (out_type_zc || out_type_to_key || out_type_htlc) + // obtain key image for this output + crypto::key_image ki = currency::null_ki; + if (m_watch_only) { - crypto::public_key out_key = out_get_pub_key(out_v, htlc_info_list); // htlc_info_list contains information about which one, redeem or refund key is ours for an htlc output - - // obtain key image for this output - crypto::key_image ki = currency::null_ki; - if (m_watch_only) + if (!is_auditable()) { - if (!is_auditable()) + // 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(out_key); + if (it != m_pending_key_images.end()) { - // 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(out_key); - if (it != m_pending_key_images.end()) - { - ki = it->second; - WLT_LOG_L1("pending key image " << ki << " was found by out pub key " << out_key); - } - else - { - ki = currency::null_ki; - WLT_LOG_L1("can't find pending key image by out pub key: " << out_key << ", key image temporarily set to null"); - } + ki = it->second; + WLT_LOG_L1("pending key image " << ki << " was found by out pub key " << out_key); + } + else + { + ki = currency::null_ki; + WLT_LOG_L1("can't find pending key image by out pub key: " << out_key << ", key image temporarily set to null"); } } - else + } + else + { + // normal wallet, calculate and store key images for own outs + currency::keypair in_ephemeral = AUTO_VAL_INIT(in_ephemeral); + currency::generate_key_image_helper(m_account.get_keys(), ptc.tx_pub_key, o, in_ephemeral, ki); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(in_ephemeral.pub == out_key, "key_image generated ephemeral public key that does not match with output_key"); + } + + if (ki != currency::null_ki) + { + // make sure calculated key image for this own output has not been seen before + auto it = m_key_images.find(ki); + if (it != m_key_images.end()) { - // normal wallet, calculate and store key images for own outs - currency::keypair in_ephemeral = AUTO_VAL_INIT(in_ephemeral); - currency::generate_key_image_helper(m_account.get_keys(), ptc.tx_pub_key, o, in_ephemeral, ki); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(in_ephemeral.pub == out_key, "key_image generated ephemeral public key that does not match with output_key"); - } + // We encountered an output with a key image already seen. This implies only one can be spent in the future (assuming the first isn't spent yet). + // To address this, we disregard such outputs and log a warning. + // + // It was later revealed that auditable wallets could still be vulnerable: an attacker might quickly broadcast a transaction + // using the same output's ephemeral keys + the same tx pub key. If the malicious transaction (potentially for a lesser amount) + // arrives first, the recipient would be unable to spend the funds from the second, real transaction. + // This attack vector was highlighted by Luke Parker (twitter: @kayabaNerve), who suggested selecting the output with the largest amount. + // Sadly, this fix only applies to classic RingCT transactions and is incompatible with our use of Confidential Assets. + // Consequently, we adopted a solution suggested by @crypto_zoidberg: verifying in zero knowledge that the sender possesses the transaction's + // secret key. This verification is integrated with the balance proof (double Schnorr proof). + // + // However, we continue to omit outputs with duplicate key images since they could originate from the same source (albeit impractically). + // -- sowle - if (ki != currency::null_ki) - { - // make sure calculated key image for this own output has not been seen before - auto it = m_key_images.find(ki); - if (it != m_key_images.end()) - { - // We encountered an output with a key image already seen. This implies only one can be spent in the future (assuming the first isn't spent yet). - // To address this, we disregard such outputs and log a warning. - // - // It was later revealed that auditable wallets could still be vulnerable: an attacker might quickly broadcast a transaction - // using the same output's ephemeral keys + the same tx pub key. If the malicious transaction (potentially for a lesser amount) - // arrives first, the recipient would be unable to spend the funds from the second, real transaction. - // This attack vector was highlighted by Luke Parker (twitter: @kayabaNerve), who suggested selecting the output with the largest amount. - // Sadly, this fix only applies to classic RingCT transactions and is incompatible with our use of Confidential Assets. - // Consequently, we adopted a solution suggested by @crypto_zoidberg: verifying in zero knowledge that the sender possesses the transaction's - // secret key. This verification is integrated with the balance proof (double Schnorr proof). - // - // However, we continue to omit outputs with duplicate key images since they could originate from the same source (albeit impractically). - // -- sowle + //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.at(it->second); - //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.at(it->second); - - std::stringstream ss; - ss << "tx " << ptc.tx_hash() << " @ block " << height << " has output #" << o << " with amount " << out.amount; - if (!out.is_native_coin()) - ss << "(asset_id: " << out.asset_id << ") "; - ss << "and 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."; - WLT_LOG_YELLOW(ss.str(), LOG_LEVEL_0); - if (m_wcallback) - m_wcallback->on_message(i_wallet2_callback::ms_yellow, ss.str()); - //if (out.is_native_coin()) - //{ - //WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(sum_of_native_outs >= out.amount, "sum_of_native_outs: " << sum_of_native_outs << ", out.amount:" << out.amount); - //sum_of_native_outs -= out.amount; - //} - continue; // skip the output - } - } - - uint8_t mix_attr = CURRENCY_TO_KEY_OUT_RELAXED; - [[maybe_unused]] bool mix_attr_r = get_mix_attr_from_tx_out_v(out_v, mix_attr); - if (is_auditable() && (out_type_to_key || out_type_zc) && mix_attr != CURRENCY_TO_KEY_OUT_FORCED_NO_MIX) - { std::stringstream ss; - ss << "output #" << o << " from tx " << ptc.tx_hash(); + ss << "tx " << ptc.tx_hash() << " @ block " << height << " has output #" << o << " with amount " << out.amount; if (!out.is_native_coin()) - ss << " asset_id: " << out.asset_id; - ss << " with amount " << print_money_brief(out.amount) - << " is targeted to this auditable wallet and has INCORRECT mix_attr = " << (uint64_t)mix_attr << ". Output is IGNORED."; + ss << "(asset_id: " << out.asset_id << ") "; + ss << "and 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."; WLT_LOG_YELLOW(ss.str(), LOG_LEVEL_0); if (m_wcallback) - m_wcallback->on_message(i_wallet2_callback::ms_red, ss.str()); + m_wcallback->on_message(i_wallet2_callback::ms_yellow, ss.str()); //if (out.is_native_coin()) //{ //WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(sum_of_native_outs >= out.amount, "sum_of_native_outs: " << sum_of_native_outs << ", out.amount:" << out.amount); @@ -800,4132 +778,4044 @@ namespace tools //} continue; // skip the output } + } - ptc.employed_entries.receive.push_back(wallet_public::employed_tx_entry{ o , out.amount , out.asset_id }); - uint64_t new_index = m_transfers.empty() ? 0 : (--m_transfers.end())->first+1; - auto rsp = m_transfers.insert(std::make_pair(new_index, boost::value_initialized())); - transfer_details& td = rsp.first->second; - td.m_ptx_wallet_info = pwallet_info; - td.m_internal_output_index = o; - td.m_key_image = ki; - td.m_amount = out.amount; - if (m_use_deffered_global_outputs) - { - if (pglobal_indexes && pglobal_indexes->size() > o) - td.m_global_output_index = (*pglobal_indexes)[o]; - else - td.m_global_output_index = WALLET_GLOBAL_OUTPUT_INDEX_UNDEFINED; - } - else - { - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(pglobal_indexes, "pglobal_indexes IS NULL in non mobile wallet"); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(pglobal_indexes->size() > o, "pglobal_indexes size()(" << pglobal_indexes->size() << ") <= o " << o); + uint8_t mix_attr = CURRENCY_TO_KEY_OUT_RELAXED; + [[maybe_unused]] bool mix_attr_r = get_mix_attr_from_tx_out_v(out_v, mix_attr); + if (is_auditable() && (out_type_to_key || out_type_zc) && mix_attr != CURRENCY_TO_KEY_OUT_FORCED_NO_MIX) + { + std::stringstream ss; + ss << "output #" << o << " from tx " << ptc.tx_hash(); + if (!out.is_native_coin()) + ss << " asset_id: " << out.asset_id; + ss << " with amount " << print_money_brief(out.amount) + << " is targeted to this auditable wallet and has INCORRECT mix_attr = " << (uint64_t)mix_attr << ". Output is IGNORED."; + WLT_LOG_YELLOW(ss.str(), LOG_LEVEL_0); + if (m_wcallback) + m_wcallback->on_message(i_wallet2_callback::ms_red, ss.str()); + //if (out.is_native_coin()) + //{ + //WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(sum_of_native_outs >= out.amount, "sum_of_native_outs: " << sum_of_native_outs << ", out.amount:" << out.amount); + //sum_of_native_outs -= out.amount; + //} + continue; // skip the output + } + + ptc.employed_entries.receive.push_back(wallet_public::employed_tx_entry{ o , out.amount , out.asset_id }); + uint64_t new_index = m_transfers.empty() ? 0 : (--m_transfers.end())->first+1; + auto rsp = m_transfers.insert(std::make_pair(new_index, boost::value_initialized())); + transfer_details& td = rsp.first->second; + td.m_ptx_wallet_info = pwallet_info; + td.m_internal_output_index = o; + td.m_key_image = ki; + td.m_amount = out.amount; + if (m_use_deffered_global_outputs) + { + if (pglobal_indexes && pglobal_indexes->size() > o) td.m_global_output_index = (*pglobal_indexes)[o]; - } - if (ptc.coin_base_tx) + else + td.m_global_output_index = WALLET_GLOBAL_OUTPUT_INDEX_UNDEFINED; + } + else + { + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(pglobal_indexes, "pglobal_indexes IS NULL in non mobile wallet"); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(pglobal_indexes->size() > o, "pglobal_indexes size()(" << pglobal_indexes->size() << ") <= o " << o); + td.m_global_output_index = (*pglobal_indexes)[o]; + } + if (ptc.coin_base_tx) + { + //last out in coinbase tx supposed to be change from coinstake + //for genesis block we'll count every input as WALLET_TRANSFER_DETAIL_FLAG_MINED_TRANSFER + if (td.m_ptx_wallet_info->m_block_height == 0 || !(o == tx.vout.size() - 1 && !ptc.is_derived_from_coinbase)) // TODO: @#@# reconsider this condition { - //last out in coinbase tx supposed to be change from coinstake - //for genesis block we'll count every input as WALLET_TRANSFER_DETAIL_FLAG_MINED_TRANSFER - if (td.m_ptx_wallet_info->m_block_height == 0 || !(o == tx.vout.size() - 1 && !ptc.is_derived_from_coinbase)) // TODO: @#@# reconsider this condition - { - td.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_MINED_TRANSFER; - } + td.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_MINED_TRANSFER; + } + } + + if (out_type_zc) + { + td.m_zc_info_ptr.reset(new transfer_details_base::ZC_out_info(out.amount_blinding_mask, out.asset_id_blinding_mask, out.asset_id)); + } + + size_t transfer_index = new_index; + if (out_type_htlc) + { + const currency::txout_htlc& hltc = out_get_htlc(out_v); + //mark this as spent + td.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT|WALLET_TRANSFER_DETAIL_CONCISE_MODE_PRESERVE; + //create entry for htlc input + htlc_expiration_trigger het = AUTO_VAL_INIT(het); + het.is_wallet_owns_redeem = (out_key == hltc.pkey_redeem) ? true : false; + het.transfer_index = transfer_index; + uint64_t expired_if_more_then = td.m_ptx_wallet_info->m_block_height + hltc.expiration; + m_htlcs.insert(std::make_pair(expired_if_more_then, het)); + + if (het.is_wallet_owns_redeem) + { + td.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_HTLC_REDEEM; } - if (out_type_zc) + //active htlc + auto amount_gindex_pair = std::make_pair(td.m_amount, td.m_global_output_index); + m_active_htlcs[amount_gindex_pair] = transfer_index; + m_active_htlcs_txid[ptc.tx_hash()] = transfer_index; + //add payer to extra options + currency::tx_payer payer = AUTO_VAL_INIT(payer); + if (het.is_wallet_owns_redeem) { - td.m_zc_info_ptr.reset(new transfer_details_base::ZC_out_info(out.amount_blinding_mask, out.asset_id_blinding_mask, out.asset_id)); - } - - size_t transfer_index = new_index; - if (out_type_htlc) - { - const currency::txout_htlc& hltc = out_get_htlc(out_v); - //mark this as spent - td.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT|WALLET_TRANSFER_DETAIL_CONCISE_MODE_PRESERVE; - //create entry for htlc input - htlc_expiration_trigger het = AUTO_VAL_INIT(het); - het.is_wallet_owns_redeem = (out_key == hltc.pkey_redeem) ? true : false; - het.transfer_index = transfer_index; - uint64_t expired_if_more_then = td.m_ptx_wallet_info->m_block_height + hltc.expiration; - m_htlcs.insert(std::make_pair(expired_if_more_then, het)); - - if (het.is_wallet_owns_redeem) + if (currency::get_type_in_variant_container(tx.extra, payer)) { - td.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_HTLC_REDEEM; - } - - //active htlc - auto amount_gindex_pair = std::make_pair(td.m_amount, td.m_global_output_index); - m_active_htlcs[amount_gindex_pair] = transfer_index; - m_active_htlcs_txid[ptc.tx_hash()] = transfer_index; - //add payer to extra options - currency::tx_payer payer = AUTO_VAL_INIT(payer); - if (het.is_wallet_owns_redeem) - { - if (currency::get_type_in_variant_container(tx.extra, payer)) - { - crypto::chacha_crypt(payer.acc_addr, derivation); - td.varian_options.push_back(payer); - } - } - else - { - //since this is refund-mode htlc out, then sender is this wallet itself - payer.acc_addr = m_account.get_public_address(); + crypto::chacha_crypt(payer.acc_addr, derivation); td.varian_options.push_back(payer); } - } else { - ptc.total_balance_change[td.get_asset_id()] += td.amount(); - add_transfer_to_transfers_cache(td.m_amount, transfer_index, td.get_asset_id()); + //since this is refund-mode htlc out, then sender is this wallet itself + payer.acc_addr = m_account.get_public_address(); + td.varian_options.push_back(payer); } - if (td.m_key_image != currency::null_ki) - m_key_images[td.m_key_image] = transfer_index; - - if (is_watch_only() && is_auditable()) - { - WLT_CHECK_AND_ASSERT_MES_NO_RET(td.m_global_output_index != WALLET_GLOBAL_OUTPUT_INDEX_UNDEFINED, "td.m_global_output_index != WALLET_GLOBAL_OUTPUT_INDEX_UNDEFINED validation failed"); - auto amount_gindex_pair = std::make_pair(td.amount_for_global_output_index(), td.m_global_output_index); - WLT_CHECK_AND_ASSERT_MES_NO_RET(m_amount_gindex_to_transfer_id.count(amount_gindex_pair) == 0, "update m_amount_gindex_to_transfer_id: amount_for_global_output_index: " << td.amount_for_global_output_index() << ", gindex: " << td.m_global_output_index << " already exists"); - m_amount_gindex_to_transfer_id[amount_gindex_pair] = transfer_index; - } - - if (ptc.max_out_unlock_time < get_tx_unlock_time(tx, o)) - ptc.max_out_unlock_time = get_tx_unlock_time(tx, o); - - if (out_type_to_key || out_type_zc) - { - if (td.is_native_coin()) - { - WLT_LOG_L0("Received native coins, transfer #" << transfer_index << ", amount: " << print_money_brief(td.amount()) << (out_type_zc ? " (hidden)" : "") << ", with tx: " << ptc.tx_hash() << ", at height " << height); - } - else - { - // TODO @#@# output asset's ticker/name - WLT_LOG_L0("Received asset " << print16(td.get_asset_id()) << ", transfer #" << transfer_index << ", amount: " << print_money_brief(td.amount()) << (out_type_zc ? " (hidden)" : "") << ", with tx: " << ptc.tx_hash() << ", at height " << height); - } - } - else if (out_type_htlc) - { - WLT_LOG_L0("Detected HTLC[" << (td.m_flags & WALLET_TRANSFER_DETAIL_FLAG_HTLC_REDEEM ? "REDEEM" : "REFUND") << "], transfer #" << transfer_index << ", amount: " << print_money(td.amount()) << ", with tx: " << ptc.tx_hash() << ", at height " << height); - } - } - else if (out_type_multisig) - { - crypto::hash multisig_id = currency::get_multisig_out_id(tx, o); - WLT_CHECK_AND_ASSERT_MES_NO_RET(m_multisig_transfers.count(multisig_id) == 0, "multisig_id = " << multisig_id << " already in multisig container"); - transfer_details_base& tdb = m_multisig_transfers[multisig_id]; - tdb.m_ptx_wallet_info = pwallet_info; - tdb.m_internal_output_index = o; - tdb.m_amount = outs[i_in_outs].amount; - WLT_LOG_L0("Received multisig, multisig out id: " << multisig_id << ", amount: " << tdb.amount() << ", with tx: " << ptc.tx_hash()); } else { - WLT_LOG_YELLOW("Unexpected output type: " << out_v.type().name() << ", out index: " << o << " in tx " << ptc.tx_hash(), LOG_LEVEL_0); + ptc.total_balance_change[td.get_asset_id()] += td.amount(); + add_transfer_to_transfers_cache(td.m_amount, transfer_index, td.get_asset_id()); + } + + if (td.m_key_image != currency::null_ki) + m_key_images[td.m_key_image] = transfer_index; + + if (is_watch_only() && is_auditable()) + { + WLT_CHECK_AND_ASSERT_MES_NO_RET(td.m_global_output_index != WALLET_GLOBAL_OUTPUT_INDEX_UNDEFINED, "td.m_global_output_index != WALLET_GLOBAL_OUTPUT_INDEX_UNDEFINED validation failed"); + auto amount_gindex_pair = std::make_pair(td.amount_for_global_output_index(), td.m_global_output_index); + WLT_CHECK_AND_ASSERT_MES_NO_RET(m_amount_gindex_to_transfer_id.count(amount_gindex_pair) == 0, "update m_amount_gindex_to_transfer_id: amount_for_global_output_index: " << td.amount_for_global_output_index() << ", gindex: " << td.m_global_output_index << " already exists"); + m_amount_gindex_to_transfer_id[amount_gindex_pair] = transfer_index; + } + + if (ptc.max_out_unlock_time < get_tx_unlock_time(tx, o)) + ptc.max_out_unlock_time = get_tx_unlock_time(tx, o); + + if (out_type_to_key || out_type_zc) + { + if (td.is_native_coin()) + { + WLT_LOG_L0("Received native coins, transfer #" << transfer_index << ", amount: " << print_money_brief(td.amount()) << (out_type_zc ? " (hidden)" : "") << ", with tx: " << ptc.tx_hash() << ", at height " << height); + } + else + { + // TODO @#@# output asset's ticker/name + WLT_LOG_L0("Received asset " << print16(td.get_asset_id()) << ", transfer #" << transfer_index << ", amount: " << print_money_brief(td.amount()) << (out_type_zc ? " (hidden)" : "") << ", with tx: " << ptc.tx_hash() << ", at height " << height); + } + } + else if (out_type_htlc) + { + WLT_LOG_L0("Detected HTLC[" << (td.m_flags & WALLET_TRANSFER_DETAIL_FLAG_HTLC_REDEEM ? "REDEEM" : "REFUND") << "], transfer #" << transfer_index << ", amount: " << print_money(td.amount()) << ", with tx: " << ptc.tx_hash() << ", at height " << height); } } - - } - } - - - //do final calculations - bool has_in_transfers = false; - bool has_out_transfers = false; - for (const auto& bce : ptc.total_balance_change) - { - if (bce.second > 0) - { - has_in_transfers = true; - } - else if (bce.second < 0) - { - has_out_transfers = true; - } - } - - //check if there are asset_registration that belong to this wallet - const asset_descriptor_operation* pado = get_type_in_variant_container(tx.extra); - if (pado && (ptc.employed_entries.receive.size() || ptc.employed_entries.spent.size() || pado->descriptor.owner == m_account.get_public_address().spend_public_key)) - { - //check if there are asset_registration that belong to this wallet - process_ado_in_new_transaction(*pado, ptc); - } - - if (has_in_transfers || has_out_transfers || is_derivation_used_to_encrypt(tx, derivation) || ptc.employed_entries.spent.size()) - { - ptc.timestamp = get_block_datetime(b); - handle_money(b, ptc); - } - - /* - if (ptc.sum_of_own_native_inputs) - {//this actually is transfer transaction, notify about spend - if (ptc.sum_of_own_native_inputs > sum_of_native_outs) - {//usual transfer - handle_money_spent2(b, tx, ptc.sum_of_own_native_inputs - (sum_of_native_outs + get_tx_fee(tx)), ptc.mtd, recipients, remote_aliases); - } - else - {//strange transfer, seems that in one transaction have transfers from different wallets. - if (!is_coinbase(tx)) + else if (out_type_multisig) { - WLT_LOG_RED("Unusual transaction " << ptc.tx_hash() << ", sum_of_native_inputs: " << ptc.sum_of_own_native_inputs << ", sum_of_native_outs: " << sum_of_native_outs, LOG_LEVEL_0); + crypto::hash multisig_id = currency::get_multisig_out_id(tx, o); + WLT_CHECK_AND_ASSERT_MES_NO_RET(m_multisig_transfers.count(multisig_id) == 0, "multisig_id = " << multisig_id << " already in multisig container"); + transfer_details_base& tdb = m_multisig_transfers[multisig_id]; + tdb.m_ptx_wallet_info = pwallet_info; + tdb.m_internal_output_index = o; + tdb.m_amount = outs[i_in_outs].amount; + WLT_LOG_L0("Received multisig, multisig out id: " << multisig_id << ", amount: " << tdb.amount() << ", with tx: " << ptc.tx_hash()); + } + else + { + WLT_LOG_YELLOW("Unexpected output type: " << out_v.type().name() << ", out index: " << o << " in tx " << ptc.tx_hash(), LOG_LEVEL_0); } - handle_money_received2(b, tx, (sum_of_native_outs - (ptc.sum_of_own_native_inputs - get_tx_fee(tx))), ptc.mtd); } + + } + } + + + //do final calculations + bool has_in_transfers = false; + bool has_out_transfers = false; + for (const auto& bce : ptc.total_balance_change) + { + if (bce.second > 0) + { + has_in_transfers = true; + } + else if (bce.second < 0) + { + has_out_transfers = true; + } + } + + //check if there are asset_registration that belong to this wallet + const asset_descriptor_operation* pado = get_type_in_variant_container(tx.extra); + if (pado && (ptc.employed_entries.receive.size() || ptc.employed_entries.spent.size() || pado->descriptor.owner == m_account.get_public_address().spend_public_key)) + { + //check if there are asset_registration that belong to this wallet + process_ado_in_new_transaction(*pado, ptc); + } + + if (has_in_transfers || has_out_transfers || is_derivation_used_to_encrypt(tx, derivation) || ptc.employed_entries.spent.size()) + { + ptc.timestamp = get_block_datetime(b); + handle_money(b, ptc); + } + + /* + if (ptc.sum_of_own_native_inputs) + {//this actually is transfer transaction, notify about spend + if (ptc.sum_of_own_native_inputs > sum_of_native_outs) + {//usual transfer + handle_money_spent2(b, tx, ptc.sum_of_own_native_inputs - (sum_of_native_outs + get_tx_fee(tx)), ptc.mtd, recipients, remote_aliases); + } + else + {//strange transfer, seems that in one transaction have transfers from different wallets. + if (!is_coinbase(tx)) + { + WLT_LOG_RED("Unusual transaction " << ptc.tx_hash() << ", sum_of_native_inputs: " << ptc.sum_of_own_native_inputs << ", sum_of_native_outs: " << sum_of_native_outs, LOG_LEVEL_0); + } + handle_money_received2(b, tx, (sum_of_native_outs - (ptc.sum_of_own_native_inputs - get_tx_fee(tx))), ptc.mtd); + } + } + else + { + if (sum_of_native_outs != 0) + { + handle_money_received2(b, tx, sum_of_native_outs, ptc.mtd); + } + else if (currency::is_derivation_used_to_encrypt(tx, derivation)) + { + //transaction doesn't transfer actually money, but bring some information + handle_money_received2(b, tx, 0, ptc.mtd); + } + else if (ptc.mtd.spent_indices.size()) + { + // multisig spend detected + handle_money_spent2(b, tx, 0, ptc.mtd, recipients, remote_aliases); + } + }*/ +} +//---------------------------------------------------------------------------------------------------- +void wallet2::prepare_wti_decrypted_attachments(wallet_public::wallet_transfer_info& wti, const std::vector& decrypted_att) +{ + PROFILE_FUNC("wallet2::prepare_wti_decrypted_attachments"); + + if (!wti.payment_id.empty()) + { + LOG_ERROR("wti.payment_id is expected to be empty. Go ahead."); + } + get_payment_id_from_decrypted_container(decrypted_att, wti.payment_id); + + for (const auto& item : decrypted_att) + { + if (item.type() == typeid(currency::tx_service_attachment)) + { + wti.service_entries.push_back(boost::get(item)); + } + } + + if (wti.is_income_mode_encryption()) + { + account_public_address sender_address = AUTO_VAL_INIT(sender_address); + wti.show_sender = handle_2_alternative_types_in_variant_container(decrypted_att, [&](const tx_payer& p) { sender_address = p.acc_addr; return false; /* <- continue? */ }); + if (wti.show_sender) + if (!wti.remote_addresses.size()) + wti.remote_addresses.push_back(currency::get_account_address_as_str(sender_address)); + } + else + { + if (wti.remote_addresses.empty()) + { + handle_2_alternative_types_in_variant_container(decrypted_att, [&](const tx_receiver& p) { + std::string addr_str; + if (wti.payment_id.empty()) + addr_str = currency::get_account_address_as_str(p.acc_addr); + else + addr_str = currency::get_account_address_and_payment_id_as_str(p.acc_addr, wti.payment_id); // show integrated address if there's a payment id provided + wti.remote_addresses.push_back(addr_str); + LOG_PRINT_YELLOW("prepare_wti_decrypted_attachments, income=false, rem. addr = " << addr_str, LOG_LEVEL_0); + return true; // continue iterating through the container + }); + } + } + + currency::tx_comment cm; + if (currency::get_type_in_variant_container(decrypted_att, cm)) + wti.comment = cm.comment; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::resend_unconfirmed() +{ + COMMAND_RPC_FORCE_RELAY_RAW_TXS::request req = AUTO_VAL_INIT(req); + COMMAND_RPC_FORCE_RELAY_RAW_TXS::response res = AUTO_VAL_INIT(res); + + for (auto& ut : m_unconfirmed_txs) + { + req.txs_as_hex.push_back(epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(ut.second.tx))); + WLT_LOG_GREEN("Relaying tx: " << ut.second.tx_hash, LOG_LEVEL_0); + } + + if (!req.txs_as_hex.size()) + return; + + bool r = m_core_proxy->call_COMMAND_RPC_FORCE_RELAY_RAW_TXS(req, res); + WLT_CHECK_AND_ASSERT_MES(r, void(), "wrong result at call_COMMAND_RPC_FORCE_RELAY_RAW_TXS"); + WLT_CHECK_AND_ASSERT_MES(res.status == API_RETURN_CODE_OK, void(), "wrong result at call_COMMAND_RPC_FORCE_RELAY_RAW_TXS: status != OK, status=" << res.status); + + WLT_LOG_GREEN("Relayed " << req.txs_as_hex.size() << " txs", LOG_LEVEL_0); +} +//----------------------------------------------------------------------------------------------------- +void wallet2::accept_proposal(const crypto::hash& contract_id, uint64_t b_acceptance_fee, currency::transaction* p_acceptance_tx /* = nullptr */) +{ + auto contr_it = m_contracts.find(contract_id); + THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it != m_contracts.end(), "Unknow contract id: " << contract_id); + + THROW_IF_FALSE_WALLET_INT_ERR_EX(!contr_it->second.is_a, "contr_it->second.is_a supposed to be false, but it is " << contr_it->second.is_a); + THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it->second.state == tools::wallet_public::escrow_contract_details_basic::proposal_sent, "contr_it->second.state supposed to be proposal_sent(" << tools::wallet_public::escrow_contract_details_basic::proposal_sent << ") but it is: " << tools::wallet_public::get_escrow_contract_state_name(contr_it->second.state)); + + construct_tx_param construct_param = AUTO_VAL_INIT(construct_param); + construct_param.fee = b_acceptance_fee; + mode_separate_context msc = AUTO_VAL_INIT(msc); + msc.escrow = true; + msc.tx_for_mode_separate = contr_it->second.proposal.tx_template; + currency::transaction& tx = msc.tx_for_mode_separate; + 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 = get_current_split_strategy(); + + //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(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 = tx; + pwallet_info->m_block_height = 0; + pwallet_info->m_block_timestamp = 0; + tdb.m_ptx_wallet_info = pwallet_info; + tdb.m_internal_output_index = n; + tdb.m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_SPENT); + //--------------------------------- + //@#@ todo: proper handling with zarcanum_based stuff + //figure out fee that was left for release contract + THROW_IF_FALSE_WALLET_INT_ERR_EX(tx.vout[n].type() == typeid(tx_out_bare), "Unexpected output type in accept proposal"); + THROW_IF_FALSE_WALLET_INT_ERR_EX(boost::get(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 = boost::get(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); + + //prepare templates to let buyer release or burn escrow + bc_services::escrow_relese_templates_body artb = AUTO_VAL_INIT(artb); + build_escrow_release_templates(contract_id, + left_for_fee_in_multisig, + artb.tx_normal_template, + artb.tx_burn_template, + contr_it->second.private_detailes); + + + //---second part of bad design --- + auto it = m_multisig_transfers.find(contract_id); + THROW_IF_FALSE_WALLET_EX(it != m_multisig_transfers.end(), error::wallet_internal_error, "Internal error"); + m_multisig_transfers.erase(it); + //--------------------------------- + + tx_service_attachment tsa = AUTO_VAL_INIT(tsa); + bc_services::pack_attachment_as_gzipped_bin(artb, tsa); + tsa.service_id = BC_ESCROW_SERVICE_ID; + tsa.instruction = BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_TEMPLATES; + tsa.flags |= TX_SERVICE_ATTACHMENT_ENCRYPT_BODY; + construct_param.extra.push_back(tsa); + + //build transaction + currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); + ftp.tx_version = this->get_current_tx_version(); + prepare_transaction(construct_param, ftp, msc); + 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)) + ">"); + + try + { + finalize_transaction(ftp, tx, one_time_key, true); + } + catch (...) + { + clear_transfers_from_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, std::string("exception in finalize_transaction, tx: ") + epee::string_tools::pod_to_hex(get_transaction_hash(tx))); + throw; + } + + 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 = tx; +} +//--------------------------------------------------------------------------------- +uint64_t wallet2::get_current_tx_version() +{ + uint64_t tx_expected_block_height = get_top_block_height() + 1; + return currency::get_tx_version(tx_expected_block_height, this->m_core_runtime_config.hard_forks); +} +//--------------------------------------------------------------------------------- +void wallet2::finish_contract(const crypto::hash& contract_id, const std::string& release_type, currency::transaction* p_release_tx /* = nullptr */) +{ + auto contr_it = m_contracts.find(contract_id); + THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it != m_contracts.end(), "Unknow contract id: " << contract_id); + + THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it->second.is_a, "contr_it->second.is_a is supposed to be true, but it is " << contr_it->second.is_a); + THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it->second.state == tools::wallet_public::escrow_contract_details_basic::contract_accepted + || contr_it->second.state == tools::wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent, + "incorrect contract state at finish_contract(): (" << contr_it->second.state << "), expected states: contract_accepted (" << tools::wallet_public::escrow_contract_details_basic::contract_accepted << "), " << + "contract_cancel_proposal_sent (" << tools::wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent << ")"); + + auto multisig_it = m_multisig_transfers.find(contract_id); + THROW_IF_FALSE_WALLET_INT_ERR_EX(multisig_it != m_multisig_transfers.end(), "Unknow multisig id: " << contract_id); + + transaction tx = AUTO_VAL_INIT(tx); + if (release_type == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_NORMAL) + { + tx = contr_it->second.release_body.tx_normal_template; + } + else if (release_type == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_BURN) + { + tx = contr_it->second.release_body.tx_burn_template; + } + else + { + THROW_IF_FALSE_WALLET_INT_ERR_EX(false, "Unknow release_type = " << release_type); + } + + bool is_input_fully_signed = false; + bool r = sign_multisig_input_in_tx(tx, 0, m_account.get_keys(), multisig_it->second.m_ptx_wallet_info->m_tx, &is_input_fully_signed); + THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "sign_multisig_input_in_tx failed"); + // There's a two-party multisig input in tx and B-party had to already sign it upon template creation, so adding A-party sign here should make the tx fully signed. Make sure it does: + THROW_IF_FALSE_WALLET_INT_ERR_EX(is_input_fully_signed, "sign_multisig_input_in_tx returned is_input_fully_signed == false"); + + send_transaction_to_network(tx); + + if (p_release_tx != nullptr) + *p_release_tx = tx; +} +//----------------------------------------------------------------------------------------------------- +void wallet2::accept_cancel_contract(const crypto::hash& contract_id, currency::transaction* p_cancellation_acceptance_tx /* = nullptr */) +{ + TIME_MEASURE_START_MS(timing1); + auto contr_it = m_contracts.find(contract_id); + THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it != m_contracts.end(), "Unknow contract id"); + TIME_MEASURE_FINISH_MS(timing1); + + THROW_IF_FALSE_WALLET_INT_ERR_EX(!contr_it->second.is_a, "contr_it->second.is_a is supposed to be false, but it is " << contr_it->second.is_a); + THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it->second.state == tools::wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent, + "incorrect contract state: (" << contr_it->second.state << "), expected state: contract_cancel_proposal_sent (" << tools::wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent << ")"); + + TIME_MEASURE_START_MS(timing2); + auto multisig_it = m_multisig_transfers.find(contract_id); + THROW_IF_FALSE_WALLET_INT_ERR_EX(multisig_it != m_multisig_transfers.end(), "Can't find multisig transfer by id: " << contract_id); + TIME_MEASURE_FINISH_MS(timing2); + + transaction tx = contr_it->second.cancel_body.tx_cancel_template; + + TIME_MEASURE_START_MS(timing3); + bool is_input_fully_signed = false; + bool r = sign_multisig_input_in_tx(tx, 0, m_account.get_keys(), multisig_it->second.m_ptx_wallet_info->m_tx, &is_input_fully_signed); + THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "sign_multisig_input_in_tx failed"); + // There's a two-party multisig input in tx and A-party had to already sign it upon template creation, so adding B-party sign here should make the tx fully signed. Make sure it does: + THROW_IF_FALSE_WALLET_INT_ERR_EX(is_input_fully_signed, "sign_multisig_input_in_tx returned is_input_fully_signed == false"); + + send_transaction_to_network(tx); + TIME_MEASURE_FINISH_MS(timing3); + if (timing1 + timing2 + timing1 > 500) + WLT_LOG_RED("[wallet2::accept_cancel_contract] LOW PERFORMANCE: " << timing1 << "," << timing2 << "," << timing1, LOG_LEVEL_0); + + if (p_cancellation_acceptance_tx != nullptr) + *p_cancellation_acceptance_tx = tx; +} +//----------------------------------------------------------------------------------------------------- +void wallet2::request_cancel_contract(const crypto::hash& contract_id, uint64_t fee, uint64_t expiration_period, currency::transaction* p_cancellation_proposal_tx /* = nullptr */) +{ + auto contr_it = m_contracts.find(contract_id); + THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it != m_contracts.end(), "Unknow contract id: " << contract_id); + + THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it->second.is_a, "contr_it->second.is_a supposed to be true at request_cancel_contract"); + THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it->second.state == tools::wallet_public::escrow_contract_details_basic::contract_accepted + || contr_it->second.state == tools::wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent, + "incorrect contract state at request_cancel_contract(): " << tools::wallet_public::get_escrow_contract_state_name(contr_it->second.state) << ", expected states: contract_accepted (" << tools::wallet_public::escrow_contract_details_basic::contract_accepted << "), " << + "contract_cancel_proposal_sent (" << tools::wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent << ")"); + + auto multisig_it = m_multisig_transfers.find(contract_id); + THROW_IF_FALSE_WALLET_INT_ERR_EX(multisig_it != m_multisig_transfers.end(), "Unknow multisig id: " << contract_id); + + + ////////////////////////////////////////////////////////////////////////// + construct_tx_param construct_param = AUTO_VAL_INIT(construct_param); + construct_param.fee = fee; + + //------- + //prepare templates to let seller cancel escrow + bc_services::escrow_cancel_templates_body ectb = AUTO_VAL_INIT(ectb); + build_escrow_cancel_template(contract_id, + expiration_period, + ectb.tx_cancel_template, + contr_it->second.private_detailes); + + tx_service_attachment tsa = AUTO_VAL_INIT(tsa); + bc_services::pack_attachment_as_gzipped_bin(ectb, tsa); + tsa.service_id = BC_ESCROW_SERVICE_ID; + tsa.instruction = BC_ESCROW_SERVICE_INSTRUCTION_CANCEL_PROPOSAL; + 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 = get_current_split_strategy(); + + currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); + ftp.tx_version = this->get_current_tx_version(); + prepare_transaction(construct_param, ftp); + currency::transaction tx = AUTO_VAL_INIT(tx); + crypto::secret_key sk = AUTO_VAL_INIT(sk); + 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)) + ">"); + + try + { + finalize_transaction(ftp, tx, sk, true); + } + catch (...) + { + clear_transfers_from_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, std::string("exception in finalize_transaction, tx: ") + epee::string_tools::pod_to_hex(get_transaction_hash(tx))); + throw; + } + + print_tx_sent_message(tx, "(transport for cancel proposal)", fee); + + if (p_cancellation_proposal_tx != nullptr) + *p_cancellation_proposal_tx = tx; +} +//----------------------------------------------------------------------------------------------------- +void wallet2::scan_tx_to_key_inputs(std::vector& found_transfers, const currency::transaction& tx) +{ + for (auto& in : tx.vin) + { + if (in.type() == typeid(currency::txin_to_key)) + { + + auto it = m_key_images.find(boost::get(in).k_image); + if (it != m_key_images.end()) + found_transfers.push_back(it->second); + } + } +} +//----------------------------------------------------------------------------------------------------- +void wallet2::change_contract_state(wallet_public::escrow_contract_details_basic& contract, uint32_t new_state, const crypto::hash& contract_id, const wallet_public::wallet_transfer_info& wti) const +{ + WLT_LOG_YELLOW("escrow contract STATE CHANGE (" << (contract.is_a ? "A," : "B,") << contract_id << " via tx " << get_transaction_hash(wti.tx) << ", height: " << wti.height << ") : " + << wallet_public::get_escrow_contract_state_name(contract.state) << " -> " << wallet_public::get_escrow_contract_state_name(new_state), LOG_LEVEL_1); + + contract.state = new_state; + contract.height = wti.height; // update height of last state change +} +//----------------------------------------------------------------------------------------------------- +void wallet2::change_contract_state(wallet_public::escrow_contract_details_basic& contract, uint32_t new_state, const crypto::hash& contract_id, const std::string& reason /*= "internal intention"*/) const +{ + WLT_LOG_YELLOW("escrow contract STATE CHANGE (" << (contract.is_a ? "A," : "B,") << contract_id << " " << reason << ") : " + << wallet_public::get_escrow_contract_state_name(contract.state) << " -> " << wallet_public::get_escrow_contract_state_name(new_state), LOG_LEVEL_1); + + contract.state = new_state; +} +//----------------------------------------------------------------------------------------------------- +void from_outs_to_received_items(const std::vector& outs, std::vector& received, const currency::transaction& tx) +{ + for (const auto& item : outs) + { + if (!out_is_multisig(tx.vout[item.index])) + received.push_back(tools::payment_details_subtransfer{ item.asset_id, item.amount }); + } +} +//----------------------------------------------------------------------------------------------------- +bool wallet2::handle_proposal(wallet_public::wallet_transfer_info& wti, const bc_services::proposal_body& prop) +{ + PROFILE_FUNC("wallet2::handle_proposal"); + crypto::hash ms_id = AUTO_VAL_INIT(ms_id); + bc_services::contract_private_details cpd = AUTO_VAL_INIT(cpd); + std::vector decrypted_items; + if (!validate_escrow_proposal(wti, prop, decrypted_items, ms_id, cpd)) + return false; + + wallet_public::escrow_contract_details_basic& ed = epee::misc_utils::get_or_insert_value_initialized(m_contracts, ms_id); + ed.expiration_time = currency::get_tx_expiration_time(prop.tx_template); + ed.timestamp = wti.timestamp; + ed.is_a = cpd.a_addr.spend_public_key == m_account.get_keys().account_address.spend_public_key; + change_contract_state(ed, wallet_public::escrow_contract_details_basic::proposal_sent, ms_id, wti); + ed.private_detailes = cpd; + currency::get_payment_id_from_decrypted_container(decrypted_items, ed.payment_id); + ed.proposal = prop; + ed.height = wti.height; + wti.contract.resize(1); + static_cast(wti.contract.back()) = ed; + wti.contract.back().contract_id = ms_id; + + //correct fee in case if it "B", cz fee is paid by "A" + if (!ed.is_a) + wti.fee = 0; + else + { + //if it's A' then mark proposal template's inputs as spent and add to expiration list + std::vector found_transfers; + scan_tx_to_key_inputs(found_transfers, prop.tx_template); + //scan outputs to figure out amount of change in escrow + crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); + std::vector outs; + bool r = lookup_acc_outs(m_account.get_keys(), prop.tx_template, outs, derivation); + THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "Failed to lookup_acc_outs for tx: " << get_transaction_hash(prop.tx_template)); + std::vector received; + from_outs_to_received_items(outs, received, prop.tx_template); + add_transfers_to_expiration_list(found_transfers, received, ed.expiration_time, wti.tx_hash); + WLT_LOG_GREEN("Locked " << found_transfers.size() << " transfers due to proposal " << ms_id, LOG_LEVEL_0); + } + + + return true; +} +//----------------------------------------------------------------------------------------------------- +bool wallet2::handle_release_contract(wallet_public::wallet_transfer_info& wti, const std::string& release_instruction) +{ + PROFILE_FUNC("wallet2::handle_release_contract"); + size_t n = get_multisig_in_index(wti.tx.vin); + WLT_CHECK_AND_ASSERT_MES(n != wti.tx.vin.size(), false, "Multisig out not found in tx template in proposal"); + crypto::hash ms_id = boost::get(wti.tx.vin[n]).multisig_out_id; + WLT_CHECK_AND_ASSERT_MES(ms_id != null_hash, false, "Multisig out not found in tx template in proposal"); + auto it = m_contracts.find(ms_id); + WLT_CHECK_AND_ASSERT_MES(it != m_contracts.end(), false, "Multisig out not found in tx template in proposal"); + + if (release_instruction == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_NORMAL) + change_contract_state(it->second, wallet_public::escrow_contract_details_basic::contract_released_normal, ms_id, wti); + else if (release_instruction == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_CANCEL) + change_contract_state(it->second, wallet_public::escrow_contract_details_basic::contract_released_cancelled, ms_id, wti); + else if (release_instruction == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_BURN) + { + change_contract_state(it->second, wallet_public::escrow_contract_details_basic::contract_released_burned, ms_id, wti); + wallet_public::wallet_sub_transfer_info* subptr = nullptr; + for (auto& s : wti.subtransfers) + { + if (s.asset_id == currency::native_coin_asset_id) + subptr = &s; + } + if (subptr == nullptr) + { + wti.subtransfers.push_back(wallet_public::wallet_sub_transfer_info()); + subptr = &wti.subtransfers.back(); + } + + subptr->amount = it->second.private_detailes.amount_to_pay + it->second.private_detailes.amount_a_pledge + it->second.private_detailes.amount_b_pledge; + if (!it->second.is_a) + { + wti.fee = currency::get_tx_fee(wti.tx); } else { - if (sum_of_native_outs != 0) - { - handle_money_received2(b, tx, sum_of_native_outs, ptc.mtd); - } - else if (currency::is_derivation_used_to_encrypt(tx, derivation)) - { - //transaction doesn't transfer actually money, but bring some information - handle_money_received2(b, tx, 0, ptc.mtd); - } - else if (ptc.mtd.spent_indices.size()) - { - // multisig spend detected - handle_money_spent2(b, tx, 0, ptc.mtd, recipients, remote_aliases); - } - }*/ - } - //---------------------------------------------------------------------------------------------------- - void wallet2::prepare_wti_decrypted_attachments(wallet_public::wallet_transfer_info& wti, const std::vector& decrypted_att) - { - PROFILE_FUNC("wallet2::prepare_wti_decrypted_attachments"); - - if (!wti.payment_id.empty()) - { - LOG_ERROR("wti.payment_id is expected to be empty. Go ahead."); - } - get_payment_id_from_decrypted_container(decrypted_att, wti.payment_id); - - for (const auto& item : decrypted_att) - { - if (item.type() == typeid(currency::tx_service_attachment)) - { - wti.service_entries.push_back(boost::get(item)); - } - } - - if (wti.is_income_mode_encryption()) - { - account_public_address sender_address = AUTO_VAL_INIT(sender_address); - wti.show_sender = handle_2_alternative_types_in_variant_container(decrypted_att, [&](const tx_payer& p) { sender_address = p.acc_addr; return false; /* <- continue? */ }); - if (wti.show_sender) - if (!wti.remote_addresses.size()) - wti.remote_addresses.push_back(currency::get_account_address_as_str(sender_address)); - } - else - { - if (wti.remote_addresses.empty()) - { - handle_2_alternative_types_in_variant_container(decrypted_att, [&](const tx_receiver& p) { - std::string addr_str; - if (wti.payment_id.empty()) - addr_str = currency::get_account_address_as_str(p.acc_addr); - else - addr_str = currency::get_account_address_and_payment_id_as_str(p.acc_addr, wti.payment_id); // show integrated address if there's a payment id provided - wti.remote_addresses.push_back(addr_str); - LOG_PRINT_YELLOW("prepare_wti_decrypted_attachments, income=false, rem. addr = " << addr_str, LOG_LEVEL_0); - return true; // continue iterating through the container - }); - } - } - - currency::tx_comment cm; - if (currency::get_type_in_variant_container(decrypted_att, cm)) - wti.comment = cm.comment; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::resend_unconfirmed() - { - COMMAND_RPC_FORCE_RELAY_RAW_TXS::request req = AUTO_VAL_INIT(req); - COMMAND_RPC_FORCE_RELAY_RAW_TXS::response res = AUTO_VAL_INIT(res); - - for (auto& ut : m_unconfirmed_txs) - { - req.txs_as_hex.push_back(epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(ut.second.tx))); - WLT_LOG_GREEN("Relaying tx: " << ut.second.tx_hash, LOG_LEVEL_0); - } - - if (!req.txs_as_hex.size()) - return; - - bool r = m_core_proxy->call_COMMAND_RPC_FORCE_RELAY_RAW_TXS(req, res); - WLT_CHECK_AND_ASSERT_MES(r, void(), "wrong result at call_COMMAND_RPC_FORCE_RELAY_RAW_TXS"); - WLT_CHECK_AND_ASSERT_MES(res.status == API_RETURN_CODE_OK, void(), "wrong result at call_COMMAND_RPC_FORCE_RELAY_RAW_TXS: status != OK, status=" << res.status); - - WLT_LOG_GREEN("Relayed " << req.txs_as_hex.size() << " txs", LOG_LEVEL_0); - } - //----------------------------------------------------------------------------------------------------- - void wallet2::accept_proposal(const crypto::hash& contract_id, uint64_t b_acceptance_fee, currency::transaction* p_acceptance_tx /* = nullptr */) - { - auto contr_it = m_contracts.find(contract_id); - THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it != m_contracts.end(), "Unknow contract id: " << contract_id); - - THROW_IF_FALSE_WALLET_INT_ERR_EX(!contr_it->second.is_a, "contr_it->second.is_a supposed to be false, but it is " << contr_it->second.is_a); - THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it->second.state == tools::wallet_public::escrow_contract_details_basic::proposal_sent, "contr_it->second.state supposed to be proposal_sent(" << tools::wallet_public::escrow_contract_details_basic::proposal_sent << ") but it is: " << tools::wallet_public::get_escrow_contract_state_name(contr_it->second.state)); - - construct_tx_param construct_param = AUTO_VAL_INIT(construct_param); - construct_param.fee = b_acceptance_fee; - mode_separate_context msc = AUTO_VAL_INIT(msc); - msc.escrow = true; - msc.tx_for_mode_separate = contr_it->second.proposal.tx_template; - currency::transaction& tx = msc.tx_for_mode_separate; - 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 = get_current_split_strategy(); - - //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(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 = tx; - pwallet_info->m_block_height = 0; - pwallet_info->m_block_timestamp = 0; - tdb.m_ptx_wallet_info = pwallet_info; - tdb.m_internal_output_index = n; - tdb.m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_SPENT); - //--------------------------------- - //@#@ todo: proper handling with zarcanum_based stuff - //figure out fee that was left for release contract - THROW_IF_FALSE_WALLET_INT_ERR_EX(tx.vout[n].type() == typeid(tx_out_bare), "Unexpected output type in accept proposal"); - THROW_IF_FALSE_WALLET_INT_ERR_EX(boost::get(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 = boost::get(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); - - //prepare templates to let buyer release or burn escrow - bc_services::escrow_relese_templates_body artb = AUTO_VAL_INIT(artb); - build_escrow_release_templates(contract_id, - left_for_fee_in_multisig, - artb.tx_normal_template, - artb.tx_burn_template, - contr_it->second.private_detailes); - - - //---second part of bad design --- - auto it = m_multisig_transfers.find(contract_id); - THROW_IF_FALSE_WALLET_EX(it != m_multisig_transfers.end(), error::wallet_internal_error, "Internal error"); - m_multisig_transfers.erase(it); - //--------------------------------- - - tx_service_attachment tsa = AUTO_VAL_INIT(tsa); - bc_services::pack_attachment_as_gzipped_bin(artb, tsa); - tsa.service_id = BC_ESCROW_SERVICE_ID; - tsa.instruction = BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_TEMPLATES; - tsa.flags |= TX_SERVICE_ATTACHMENT_ENCRYPT_BODY; - construct_param.extra.push_back(tsa); - - //build transaction - currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); - ftp.tx_version = this->get_current_tx_version(); - prepare_transaction(construct_param, ftp, msc); - 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)) + ">"); - - try - { - finalize_transaction(ftp, tx, one_time_key, true); - } - catch (...) - { - clear_transfers_from_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, std::string("exception in finalize_transaction, tx: ") + epee::string_tools::pod_to_hex(get_transaction_hash(tx))); - throw; - } - - 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 = tx; - } - //--------------------------------------------------------------------------------- - uint64_t wallet2::get_current_tx_version() - { - uint64_t tx_expected_block_height = get_top_block_height() + 1; - return currency::get_tx_version(tx_expected_block_height, this->m_core_runtime_config.hard_forks); - } - //--------------------------------------------------------------------------------- - void wallet2::finish_contract(const crypto::hash& contract_id, const std::string& release_type, currency::transaction* p_release_tx /* = nullptr */) - { - auto contr_it = m_contracts.find(contract_id); - THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it != m_contracts.end(), "Unknow contract id: " << contract_id); - - THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it->second.is_a, "contr_it->second.is_a is supposed to be true, but it is " << contr_it->second.is_a); - THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it->second.state == tools::wallet_public::escrow_contract_details_basic::contract_accepted - || contr_it->second.state == tools::wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent, - "incorrect contract state at finish_contract(): (" << contr_it->second.state << "), expected states: contract_accepted (" << tools::wallet_public::escrow_contract_details_basic::contract_accepted << "), " << - "contract_cancel_proposal_sent (" << tools::wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent << ")"); - - auto multisig_it = m_multisig_transfers.find(contract_id); - THROW_IF_FALSE_WALLET_INT_ERR_EX(multisig_it != m_multisig_transfers.end(), "Unknow multisig id: " << contract_id); - - transaction tx = AUTO_VAL_INIT(tx); - if (release_type == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_NORMAL) - { - tx = contr_it->second.release_body.tx_normal_template; - } - else if (release_type == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_BURN) - { - tx = contr_it->second.release_body.tx_burn_template; - } - else - { - THROW_IF_FALSE_WALLET_INT_ERR_EX(false, "Unknow release_type = " << release_type); - } - - bool is_input_fully_signed = false; - bool r = sign_multisig_input_in_tx(tx, 0, m_account.get_keys(), multisig_it->second.m_ptx_wallet_info->m_tx, &is_input_fully_signed); - THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "sign_multisig_input_in_tx failed"); - // There's a two-party multisig input in tx and B-party had to already sign it upon template creation, so adding A-party sign here should make the tx fully signed. Make sure it does: - THROW_IF_FALSE_WALLET_INT_ERR_EX(is_input_fully_signed, "sign_multisig_input_in_tx returned is_input_fully_signed == false"); - - send_transaction_to_network(tx); - - if (p_release_tx != nullptr) - *p_release_tx = tx; - } - //----------------------------------------------------------------------------------------------------- - void wallet2::accept_cancel_contract(const crypto::hash& contract_id, currency::transaction* p_cancellation_acceptance_tx /* = nullptr */) - { - TIME_MEASURE_START_MS(timing1); - auto contr_it = m_contracts.find(contract_id); - THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it != m_contracts.end(), "Unknow contract id"); - TIME_MEASURE_FINISH_MS(timing1); - - THROW_IF_FALSE_WALLET_INT_ERR_EX(!contr_it->second.is_a, "contr_it->second.is_a is supposed to be false, but it is " << contr_it->second.is_a); - THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it->second.state == tools::wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent, - "incorrect contract state: (" << contr_it->second.state << "), expected state: contract_cancel_proposal_sent (" << tools::wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent << ")"); - - TIME_MEASURE_START_MS(timing2); - auto multisig_it = m_multisig_transfers.find(contract_id); - THROW_IF_FALSE_WALLET_INT_ERR_EX(multisig_it != m_multisig_transfers.end(), "Can't find multisig transfer by id: " << contract_id); - TIME_MEASURE_FINISH_MS(timing2); - - transaction tx = contr_it->second.cancel_body.tx_cancel_template; - - TIME_MEASURE_START_MS(timing3); - bool is_input_fully_signed = false; - bool r = sign_multisig_input_in_tx(tx, 0, m_account.get_keys(), multisig_it->second.m_ptx_wallet_info->m_tx, &is_input_fully_signed); - THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "sign_multisig_input_in_tx failed"); - // There's a two-party multisig input in tx and A-party had to already sign it upon template creation, so adding B-party sign here should make the tx fully signed. Make sure it does: - THROW_IF_FALSE_WALLET_INT_ERR_EX(is_input_fully_signed, "sign_multisig_input_in_tx returned is_input_fully_signed == false"); - - send_transaction_to_network(tx); - TIME_MEASURE_FINISH_MS(timing3); - if (timing1 + timing2 + timing1 > 500) - WLT_LOG_RED("[wallet2::accept_cancel_contract] LOW PERFORMANCE: " << timing1 << "," << timing2 << "," << timing1, LOG_LEVEL_0); - - if (p_cancellation_acceptance_tx != nullptr) - *p_cancellation_acceptance_tx = tx; - } - //----------------------------------------------------------------------------------------------------- - void wallet2::request_cancel_contract(const crypto::hash& contract_id, uint64_t fee, uint64_t expiration_period, currency::transaction* p_cancellation_proposal_tx /* = nullptr */) - { - auto contr_it = m_contracts.find(contract_id); - THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it != m_contracts.end(), "Unknow contract id: " << contract_id); - - THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it->second.is_a, "contr_it->second.is_a supposed to be true at request_cancel_contract"); - THROW_IF_FALSE_WALLET_INT_ERR_EX(contr_it->second.state == tools::wallet_public::escrow_contract_details_basic::contract_accepted - || contr_it->second.state == tools::wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent, - "incorrect contract state at request_cancel_contract(): " << tools::wallet_public::get_escrow_contract_state_name(contr_it->second.state) << ", expected states: contract_accepted (" << tools::wallet_public::escrow_contract_details_basic::contract_accepted << "), " << - "contract_cancel_proposal_sent (" << tools::wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent << ")"); - - auto multisig_it = m_multisig_transfers.find(contract_id); - THROW_IF_FALSE_WALLET_INT_ERR_EX(multisig_it != m_multisig_transfers.end(), "Unknow multisig id: " << contract_id); - - - ////////////////////////////////////////////////////////////////////////// - construct_tx_param construct_param = AUTO_VAL_INIT(construct_param); - construct_param.fee = fee; - - //------- - //prepare templates to let seller cancel escrow - bc_services::escrow_cancel_templates_body ectb = AUTO_VAL_INIT(ectb); - build_escrow_cancel_template(contract_id, - expiration_period, - ectb.tx_cancel_template, - contr_it->second.private_detailes); - - tx_service_attachment tsa = AUTO_VAL_INIT(tsa); - bc_services::pack_attachment_as_gzipped_bin(ectb, tsa); - tsa.service_id = BC_ESCROW_SERVICE_ID; - tsa.instruction = BC_ESCROW_SERVICE_INSTRUCTION_CANCEL_PROPOSAL; - 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 = get_current_split_strategy(); - - currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); - ftp.tx_version = this->get_current_tx_version(); - prepare_transaction(construct_param, ftp); - currency::transaction tx = AUTO_VAL_INIT(tx); - crypto::secret_key sk = AUTO_VAL_INIT(sk); - 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)) + ">"); - - try - { - finalize_transaction(ftp, tx, sk, true); - } - catch (...) - { - clear_transfers_from_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, std::string("exception in finalize_transaction, tx: ") + epee::string_tools::pod_to_hex(get_transaction_hash(tx))); - throw; - } - - print_tx_sent_message(tx, "(transport for cancel proposal)", fee); - - if (p_cancellation_proposal_tx != nullptr) - *p_cancellation_proposal_tx = tx; - } - //----------------------------------------------------------------------------------------------------- - void wallet2::scan_tx_to_key_inputs(std::vector& found_transfers, const currency::transaction& tx) - { - for (auto& in : tx.vin) - { - if (in.type() == typeid(currency::txin_to_key)) - { - - auto it = m_key_images.find(boost::get(in).k_image); - if (it != m_key_images.end()) - found_transfers.push_back(it->second); - } - } - } - //----------------------------------------------------------------------------------------------------- - void wallet2::change_contract_state(wallet_public::escrow_contract_details_basic& contract, uint32_t new_state, const crypto::hash& contract_id, const wallet_public::wallet_transfer_info& wti) const - { - WLT_LOG_YELLOW("escrow contract STATE CHANGE (" << (contract.is_a ? "A," : "B,") << contract_id << " via tx " << get_transaction_hash(wti.tx) << ", height: " << wti.height << ") : " - << wallet_public::get_escrow_contract_state_name(contract.state) << " -> " << wallet_public::get_escrow_contract_state_name(new_state), LOG_LEVEL_1); - - contract.state = new_state; - contract.height = wti.height; // update height of last state change - } - //----------------------------------------------------------------------------------------------------- - void wallet2::change_contract_state(wallet_public::escrow_contract_details_basic& contract, uint32_t new_state, const crypto::hash& contract_id, const std::string& reason /*= "internal intention"*/) const - { - WLT_LOG_YELLOW("escrow contract STATE CHANGE (" << (contract.is_a ? "A," : "B,") << contract_id << " " << reason << ") : " - << wallet_public::get_escrow_contract_state_name(contract.state) << " -> " << wallet_public::get_escrow_contract_state_name(new_state), LOG_LEVEL_1); - - contract.state = new_state; - } - //----------------------------------------------------------------------------------------------------- - void from_outs_to_received_items(const std::vector& outs, std::vector& received, const currency::transaction& tx) - { - for (const auto& item : outs) - { - if (!out_is_multisig(tx.vout[item.index])) - received.push_back(tools::payment_details_subtransfer{ item.asset_id, item.amount }); - } - } - //----------------------------------------------------------------------------------------------------- - bool wallet2::handle_proposal(wallet_public::wallet_transfer_info& wti, const bc_services::proposal_body& prop) - { - PROFILE_FUNC("wallet2::handle_proposal"); - crypto::hash ms_id = AUTO_VAL_INIT(ms_id); - bc_services::contract_private_details cpd = AUTO_VAL_INIT(cpd); - std::vector decrypted_items; - if (!validate_escrow_proposal(wti, prop, decrypted_items, ms_id, cpd)) - return false; - - wallet_public::escrow_contract_details_basic& ed = epee::misc_utils::get_or_insert_value_initialized(m_contracts, ms_id); - ed.expiration_time = currency::get_tx_expiration_time(prop.tx_template); - ed.timestamp = wti.timestamp; - ed.is_a = cpd.a_addr.spend_public_key == m_account.get_keys().account_address.spend_public_key; - change_contract_state(ed, wallet_public::escrow_contract_details_basic::proposal_sent, ms_id, wti); - ed.private_detailes = cpd; - currency::get_payment_id_from_decrypted_container(decrypted_items, ed.payment_id); - ed.proposal = prop; - ed.height = wti.height; - wti.contract.resize(1); - static_cast(wti.contract.back()) = ed; - wti.contract.back().contract_id = ms_id; - - //correct fee in case if it "B", cz fee is paid by "A" - if (!ed.is_a) wti.fee = 0; - else - { - //if it's A' then mark proposal template's inputs as spent and add to expiration list - std::vector found_transfers; - scan_tx_to_key_inputs(found_transfers, prop.tx_template); - //scan outputs to figure out amount of change in escrow - crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); - std::vector outs; - bool r = lookup_acc_outs(m_account.get_keys(), prop.tx_template, outs, derivation); - THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "Failed to lookup_acc_outs for tx: " << get_transaction_hash(prop.tx_template)); - std::vector received; - from_outs_to_received_items(outs, received, prop.tx_template); - add_transfers_to_expiration_list(found_transfers, received, ed.expiration_time, wti.tx_hash); - WLT_LOG_GREEN("Locked " << found_transfers.size() << " transfers due to proposal " << ms_id, LOG_LEVEL_0); } - - - return true; } - //----------------------------------------------------------------------------------------------------- - bool wallet2::handle_release_contract(wallet_public::wallet_transfer_info& wti, const std::string& release_instruction) + else { - PROFILE_FUNC("wallet2::handle_release_contract"); - size_t n = get_multisig_in_index(wti.tx.vin); - WLT_CHECK_AND_ASSERT_MES(n != wti.tx.vin.size(), false, "Multisig out not found in tx template in proposal"); - crypto::hash ms_id = boost::get(wti.tx.vin[n]).multisig_out_id; - WLT_CHECK_AND_ASSERT_MES(ms_id != null_hash, false, "Multisig out not found in tx template in proposal"); - auto it = m_contracts.find(ms_id); - WLT_CHECK_AND_ASSERT_MES(it != m_contracts.end(), false, "Multisig out not found in tx template in proposal"); + WLT_LOG_ERROR("wrong release_instruction: " << release_instruction); + return false; + } - if (release_instruction == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_NORMAL) - change_contract_state(it->second, wallet_public::escrow_contract_details_basic::contract_released_normal, ms_id, wti); - else if (release_instruction == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_CANCEL) - change_contract_state(it->second, wallet_public::escrow_contract_details_basic::contract_released_cancelled, ms_id, wti); - else if (release_instruction == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_BURN) - { - change_contract_state(it->second, wallet_public::escrow_contract_details_basic::contract_released_burned, ms_id, wti); - wallet_public::wallet_sub_transfer_info* subptr = nullptr; - for (auto& s : wti.subtransfers) - { - if (s.asset_id == currency::native_coin_asset_id) - subptr = &s; - } - if (subptr == nullptr) - { - wti.subtransfers.push_back(wallet_public::wallet_sub_transfer_info()); - subptr = &wti.subtransfers.back(); - } + wti.contract.resize(1); + static_cast(wti.contract.back()) = it->second; + wti.contract.back().contract_id = ms_id; - subptr->amount = it->second.private_detailes.amount_to_pay + it->second.private_detailes.amount_a_pledge + it->second.private_detailes.amount_b_pledge; - if (!it->second.is_a) - { - wti.fee = currency::get_tx_fee(wti.tx); - } - else - { - wti.fee = 0; - } - } - else - { - WLT_LOG_ERROR("wrong release_instruction: " << release_instruction); - return false; - } + //if it's A(buyer) then fee paid by B(seller) + if (it->second.is_a) + { + wti.fee = 0; + } + return true; +} +//----------------------------------------------------------------------------------------------------- +bool wallet2::handle_contract(wallet_public::wallet_transfer_info& wti, const bc_services::contract_private_details& cntr, const std::vector& decrypted_attach) +{ + PROFILE_FUNC("wallet2::handle_contract"); + bool is_a = cntr.a_addr == m_account.get_public_address(); + crypto::hash ms_id = AUTO_VAL_INIT(ms_id); + bc_services::escrow_relese_templates_body rel = AUTO_VAL_INIT(rel); + if (!validate_escrow_contract(wti, cntr, is_a, decrypted_attach, ms_id, rel)) + return false; + + wallet_public::escrow_contract_details_basic& ed = epee::misc_utils::get_or_insert_value_initialized(m_contracts, ms_id); + ed.is_a = is_a; + ed.expiration_time = currency::get_tx_expiration_time(wti.tx); + if (wti.timestamp) + ed.timestamp = wti.timestamp; + ed.height = wti.height; + ed.payment_id = wti.payment_id; + change_contract_state(ed, wallet_public::escrow_contract_details_basic::contract_accepted, ms_id, wti); + ed.private_detailes = cntr; + ed.release_body = rel; + + wti.contract.resize(1); + static_cast(wti.contract.back()) = ed; + wti.contract.back().contract_id = ms_id; + + //fee workaround: in consolidating transactions impossible no figure out which part of participants paid fee for tx, so we correct it + //in code which know escrow protocol, and we know that fee paid by B(seller) + if (ed.is_a) + { + WLT_CHECK_AND_ASSERT_MES(wti.subtransfers.size(), false, "Unexpected subtransfers size"); //TODO: subject for refactoring + wti.subtransfers.back().amount += wti.fee; + wti.fee = 0; + } + + + return true; +} +//----------------------------------------------------------------------------------------------------- +bool wallet2::handle_cancel_proposal(wallet_public::wallet_transfer_info& wti, const bc_services::escrow_cancel_templates_body& ectb, const std::vector& decrypted_attach) +{ + PROFILE_FUNC("wallet2::handle_cancel_proposal"); + //validate cancel proposal + WLT_CHECK_AND_ASSERT_MES(ectb.tx_cancel_template.vin.size() && ectb.tx_cancel_template.vin[0].type() == typeid(currency::txin_multisig), false, "Wrong cancel ecrow proposal"); + crypto::hash contract_id = boost::get(ectb.tx_cancel_template.vin[0]).multisig_out_id; + auto it = m_contracts.find(contract_id); + WLT_CHECK_AND_ASSERT_MES(it != m_contracts.end(), false, "Multisig out not found in tx template in proposal"); + + bool r = validate_escrow_cancel_proposal(wti, ectb, decrypted_attach, contract_id, it->second.private_detailes, it->second.proposal.tx_template); + WLT_CHECK_AND_ASSERT_MES(r, false, "failed to validate escrow cancel request, contract id: " << contract_id); + + uint32_t contract_state = it->second.state; + switch (contract_state) + { + case wallet_public::escrow_contract_details::contract_accepted: + change_contract_state(it->second, wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent, contract_id, wti); BOOST_FALLTHROUGH; + // pass through + case wallet_public::escrow_contract_details::contract_cancel_proposal_sent: // update contract info even if already in that state + it->second.cancel_body.tx_cancel_template = ectb.tx_cancel_template; + it->second.cancel_expiration_time = currency::get_tx_expiration_time(ectb.tx_cancel_template); + //update wti info to let GUI know wti.contract.resize(1); static_cast(wti.contract.back()) = it->second; - wti.contract.back().contract_id = ms_id; - - //if it's A(buyer) then fee paid by B(seller) - if (it->second.is_a) - { - wti.fee = 0; - } + wti.contract.back().contract_id = contract_id; return true; + default: + WLT_LOG_RED("handle_cancel_proposal for contract (" << (it->second.is_a ? "A," : "B,") << contract_id << " via tx " << get_transaction_hash(wti.tx) << ", height: " << wti.height << ") : " << ENDL << + "incorrect state " << wallet_public::get_escrow_contract_state_name(it->second.state) << ", while 'contract_accepted' or 'contract_cancel_proposal_sent' was expected -- decline cancel proposal", LOG_LEVEL_1); } - //----------------------------------------------------------------------------------------------------- - bool wallet2::handle_contract(wallet_public::wallet_transfer_info& wti, const bc_services::contract_private_details& cntr, const std::vector& decrypted_attach) + return false; +} +//----------------------------------------------------------------------------------------------------- +bool wallet2::process_contract_info(wallet_public::wallet_transfer_info& wti, const std::vector& decrypted_attach) +{ + PROFILE_FUNC("wallet2::process_contract_info"); + for (const auto& v : decrypted_attach) { - PROFILE_FUNC("wallet2::handle_contract"); - bool is_a = cntr.a_addr == m_account.get_public_address(); - crypto::hash ms_id = AUTO_VAL_INIT(ms_id); - bc_services::escrow_relese_templates_body rel = AUTO_VAL_INIT(rel); - if (!validate_escrow_contract(wti, cntr, is_a, decrypted_attach, ms_id, rel)) - return false; - - wallet_public::escrow_contract_details_basic& ed = epee::misc_utils::get_or_insert_value_initialized(m_contracts, ms_id); - ed.is_a = is_a; - ed.expiration_time = currency::get_tx_expiration_time(wti.tx); - if (wti.timestamp) - ed.timestamp = wti.timestamp; - ed.height = wti.height; - ed.payment_id = wti.payment_id; - change_contract_state(ed, wallet_public::escrow_contract_details_basic::contract_accepted, ms_id, wti); - ed.private_detailes = cntr; - ed.release_body = rel; - - wti.contract.resize(1); - static_cast(wti.contract.back()) = ed; - wti.contract.back().contract_id = ms_id; - - //fee workaround: in consolidating transactions impossible no figure out which part of participants paid fee for tx, so we correct it - //in code which know escrow protocol, and we know that fee paid by B(seller) - if (ed.is_a) + if (v.type() == typeid(tx_service_attachment)) { - WLT_CHECK_AND_ASSERT_MES(wti.subtransfers.size(), false, "Unexpected subtransfers size"); //TODO: subject for refactoring - wti.subtransfers.back().amount += wti.fee; - wti.fee = 0; - } - - - return true; - } - //----------------------------------------------------------------------------------------------------- - bool wallet2::handle_cancel_proposal(wallet_public::wallet_transfer_info& wti, const bc_services::escrow_cancel_templates_body& ectb, const std::vector& decrypted_attach) - { - PROFILE_FUNC("wallet2::handle_cancel_proposal"); - //validate cancel proposal - WLT_CHECK_AND_ASSERT_MES(ectb.tx_cancel_template.vin.size() && ectb.tx_cancel_template.vin[0].type() == typeid(currency::txin_multisig), false, "Wrong cancel ecrow proposal"); - crypto::hash contract_id = boost::get(ectb.tx_cancel_template.vin[0]).multisig_out_id; - auto it = m_contracts.find(contract_id); - WLT_CHECK_AND_ASSERT_MES(it != m_contracts.end(), false, "Multisig out not found in tx template in proposal"); - - bool r = validate_escrow_cancel_proposal(wti, ectb, decrypted_attach, contract_id, it->second.private_detailes, it->second.proposal.tx_template); - WLT_CHECK_AND_ASSERT_MES(r, false, "failed to validate escrow cancel request, contract id: " << contract_id); - - uint32_t contract_state = it->second.state; - switch (contract_state) - { - case wallet_public::escrow_contract_details::contract_accepted: - change_contract_state(it->second, wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent, contract_id, wti); BOOST_FALLTHROUGH; - // pass through - case wallet_public::escrow_contract_details::contract_cancel_proposal_sent: // update contract info even if already in that state - it->second.cancel_body.tx_cancel_template = ectb.tx_cancel_template; - it->second.cancel_expiration_time = currency::get_tx_expiration_time(ectb.tx_cancel_template); - //update wti info to let GUI know - wti.contract.resize(1); - static_cast(wti.contract.back()) = it->second; - wti.contract.back().contract_id = contract_id; - return true; - default: - WLT_LOG_RED("handle_cancel_proposal for contract (" << (it->second.is_a ? "A," : "B,") << contract_id << " via tx " << get_transaction_hash(wti.tx) << ", height: " << wti.height << ") : " << ENDL << - "incorrect state " << wallet_public::get_escrow_contract_state_name(it->second.state) << ", while 'contract_accepted' or 'contract_cancel_proposal_sent' was expected -- decline cancel proposal", LOG_LEVEL_1); - } - - return false; - } - //----------------------------------------------------------------------------------------------------- - bool wallet2::process_contract_info(wallet_public::wallet_transfer_info& wti, const std::vector& decrypted_attach) - { - PROFILE_FUNC("wallet2::process_contract_info"); - for (const auto& v : decrypted_attach) - { - if (v.type() == typeid(tx_service_attachment)) + const tx_service_attachment& sa = boost::get(v); + if (sa.service_id == BC_ESCROW_SERVICE_ID) { - const tx_service_attachment& sa = boost::get(v); - if (sa.service_id == BC_ESCROW_SERVICE_ID) + if (sa.instruction == BC_ESCROW_SERVICE_INSTRUCTION_PROPOSAL) { - if (sa.instruction == BC_ESCROW_SERVICE_INSTRUCTION_PROPOSAL) + bc_services::proposal_body prop = AUTO_VAL_INIT(prop); + if (!t_unserializable_object_from_blob(prop, sa.body)) { - bc_services::proposal_body prop = AUTO_VAL_INIT(prop); - if (!t_unserializable_object_from_blob(prop, sa.body)) - { - WLT_LOG_ERROR("Failed to unpack attachment for tx: " << wti.tx_hash); - return false; - } - handle_proposal(wti, prop); + WLT_LOG_ERROR("Failed to unpack attachment for tx: " << wti.tx_hash); + return false; } - else if (sa.instruction == BC_ESCROW_SERVICE_INSTRUCTION_PRIVATE_DETAILS) + handle_proposal(wti, prop); + } + else if (sa.instruction == BC_ESCROW_SERVICE_INSTRUCTION_PRIVATE_DETAILS) + { + //means some related contract appeared in blockchain + bc_services::contract_private_details cntr = AUTO_VAL_INIT(cntr); + if (!epee::serialization::load_t_from_json(cntr, sa.body)) { - //means some related contract appeared in blockchain - bc_services::contract_private_details cntr = AUTO_VAL_INIT(cntr); - if (!epee::serialization::load_t_from_json(cntr, sa.body)) - { - WLT_LOG_ERROR("Failed to unpack attachment for tx: " << wti.tx_hash); - return false; - } - handle_contract(wti, cntr, decrypted_attach); - + WLT_LOG_ERROR("Failed to unpack attachment for tx: " << wti.tx_hash); + return false; } - else if ( - sa.instruction == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_NORMAL || - sa.instruction == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_CANCEL || - sa.instruction == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_BURN - ) + handle_contract(wti, cntr, decrypted_attach); + + } + else if ( + sa.instruction == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_NORMAL || + sa.instruction == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_CANCEL || + sa.instruction == BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_BURN + ) + { + handle_release_contract(wti, sa.instruction); + } + else if (sa.instruction == BC_ESCROW_SERVICE_INSTRUCTION_CANCEL_PROPOSAL) + { + //means some related contract appeared in blockchain + bc_services::escrow_cancel_templates_body cpb = AUTO_VAL_INIT(cpb); + if (!t_unserializable_object_from_blob(cpb, sa.body)) { - handle_release_contract(wti, sa.instruction); + WLT_LOG_ERROR("Failed to unpack attachment for tx: " << wti.tx_hash); + return false; } - else if (sa.instruction == BC_ESCROW_SERVICE_INSTRUCTION_CANCEL_PROPOSAL) + handle_cancel_proposal(wti, cpb, decrypted_attach); + + } + } + } + } + + return true; +} +//----------------------------------------------------------------------------------------------------- +void wallet2::prepare_wti(wallet_public::wallet_transfer_info& wti, const process_transaction_context& tx_process_context) +{ + PROFILE_FUNC("wallet2::prepare_wti"); + wti.tx = tx_process_context.tx; + + load_wti_from_process_transaction_context(wti, tx_process_context); + + wti.height = tx_process_context.height; + wti.employed_entries = tx_process_context.employed_entries; + wti.unlock_time = get_max_unlock_time_from_receive_indices(tx_process_context.tx, tx_process_context.employed_entries); + wti.timestamp = tx_process_context.timestamp; + wti.tx_blob_size = static_cast(currency::get_object_blobsize(wti.tx)); + wti.tx_hash = tx_process_context.tx_hash(); + load_wallet_transfer_info_flags(wti); + bc_services::extract_market_instructions(wti.marketplace_entries, wti.tx.attachment); + + // escrow transactions, which are built with TX_FLAG_SIGNATURE_MODE_SEPARATE flag actually encrypt attachments + // with buyer as a sender, and seller as receiver, despite the fact that for both sides transaction seen as outgoing. + // so here to decrypt tx properly we need to figure out, if this transaction is actually escrow acceptance. + //we check if spent_indices have zero then input do not belong to this account, which means that we are seller for this + //escrow, and decryption should be processed as income flag + + //let's assume that the one who pays for tx fee is sender of tx + bool decrypt_attachment_as_income = !(tx_process_context.total_balance_change.count(currency::native_coin_asset_id) && tx_process_context.total_balance_change.at(currency::native_coin_asset_id) < 0); + std::vector decrypted_att; + bool has_zero_input_as_spent = false; + for (const auto& item : tx_process_context.employed_entries.spent) + { + if (item.index == 0) + { + has_zero_input_as_spent = true; + break; + } + } + + if (wti.tx_type == GUI_TX_TYPE_ESCROW_TRANSFER && !has_zero_input_as_spent) + decrypt_attachment_as_income = true; + + + decrypt_payload_items(decrypt_attachment_as_income, wti.tx, m_account.get_keys(), decrypted_att); + if ((is_watch_only() && !decrypt_attachment_as_income) || (wti.height > 638000 && !have_type_in_variant_container(decrypted_att))) + { + remove_field_of_type_from_extra(decrypted_att); + remove_field_of_type_from_extra(decrypted_att); + } + if (is_watch_only() && !decrypt_attachment_as_income) + { + remove_field_of_type_from_extra(decrypted_att); + } + prepare_wti_decrypted_attachments(wti, decrypted_att); + process_contract_info(wti, decrypted_att); + process_payment_id_for_wti(wti, tx_process_context); + +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::process_payment_id_for_wti(wallet_public::wallet_transfer_info& wti, const process_transaction_context& ptc) +{ + //if(this->is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM)) + { + if (wti.get_native_is_income() && wti.payment_id.size()) + { + payment_details payment; + payment.m_tx_hash = wti.tx_hash; + payment.m_amount = 0; + payment.m_block_height = wti.height; + payment.m_unlock_time = ptc.max_out_unlock_time; + + for (const auto& bce : ptc.total_balance_change) + { + if (bce.second > 0) + { + if (bce.first == currency::native_coin_asset_id) { - //means some related contract appeared in blockchain - bc_services::escrow_cancel_templates_body cpb = AUTO_VAL_INIT(cpb); - if (!t_unserializable_object_from_blob(cpb, sa.body)) - { - WLT_LOG_ERROR("Failed to unpack attachment for tx: " << wti.tx_hash); - return false; - } - handle_cancel_proposal(wti, cpb, decrypted_attach); - - } - } - } - } - - return true; - } - //----------------------------------------------------------------------------------------------------- - void wallet2::prepare_wti(wallet_public::wallet_transfer_info& wti, const process_transaction_context& tx_process_context) - { - PROFILE_FUNC("wallet2::prepare_wti"); - wti.tx = tx_process_context.tx; - - load_wti_from_process_transaction_context(wti, tx_process_context); - - wti.height = tx_process_context.height; - wti.employed_entries = tx_process_context.employed_entries; - wti.unlock_time = get_max_unlock_time_from_receive_indices(tx_process_context.tx, tx_process_context.employed_entries); - wti.timestamp = tx_process_context.timestamp; - wti.tx_blob_size = static_cast(currency::get_object_blobsize(wti.tx)); - wti.tx_hash = tx_process_context.tx_hash(); - load_wallet_transfer_info_flags(wti); - bc_services::extract_market_instructions(wti.marketplace_entries, wti.tx.attachment); - - // escrow transactions, which are built with TX_FLAG_SIGNATURE_MODE_SEPARATE flag actually encrypt attachments - // with buyer as a sender, and seller as receiver, despite the fact that for both sides transaction seen as outgoing. - // so here to decrypt tx properly we need to figure out, if this transaction is actually escrow acceptance. - //we check if spent_indices have zero then input do not belong to this account, which means that we are seller for this - //escrow, and decryption should be processed as income flag - - //let's assume that the one who pays for tx fee is sender of tx - bool decrypt_attachment_as_income = !(tx_process_context.total_balance_change.count(currency::native_coin_asset_id) && tx_process_context.total_balance_change.at(currency::native_coin_asset_id) < 0); - std::vector decrypted_att; - bool has_zero_input_as_spent = false; - for (const auto& item : tx_process_context.employed_entries.spent) - { - if (item.index == 0) - { - has_zero_input_as_spent = true; - break; - } - } - - if (wti.tx_type == GUI_TX_TYPE_ESCROW_TRANSFER && !has_zero_input_as_spent) - decrypt_attachment_as_income = true; - - - decrypt_payload_items(decrypt_attachment_as_income, wti.tx, m_account.get_keys(), decrypted_att); - if ((is_watch_only() && !decrypt_attachment_as_income) || (wti.height > 638000 && !have_type_in_variant_container(decrypted_att))) - { - remove_field_of_type_from_extra(decrypted_att); - remove_field_of_type_from_extra(decrypted_att); - } - if (is_watch_only() && !decrypt_attachment_as_income) - { - remove_field_of_type_from_extra(decrypted_att); - } - prepare_wti_decrypted_attachments(wti, decrypted_att); - process_contract_info(wti, decrypted_att); - process_payment_id_for_wti(wti, tx_process_context); - - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::process_payment_id_for_wti(wallet_public::wallet_transfer_info& wti, const process_transaction_context& ptc) - { - //if(this->is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM)) - { - if (wti.get_native_is_income() && wti.payment_id.size()) - { - payment_details payment; - payment.m_tx_hash = wti.tx_hash; - payment.m_amount = 0; - payment.m_block_height = wti.height; - payment.m_unlock_time = ptc.max_out_unlock_time; - - for (const auto& bce : ptc.total_balance_change) - { - if (bce.second > 0) - { - if (bce.first == currency::native_coin_asset_id) - { - payment.m_amount = static_cast(bce.second); - } - else - { - payment.subtransfers.push_back(payment_details_subtransfer{ bce.first, static_cast(bce.second) }); - } - } - } - m_payments.emplace(wti.payment_id, payment); - WLT_LOG_L2("Payment found, id (hex): " << epee::string_tools::buff_to_hex_nodelimer(wti.payment_id) << ", tx: " << payment.m_tx_hash << ", amount: " << print_money_brief(payment.m_amount) << "subtransfers = " << payment.subtransfers.size()); - } - } - return true; - } - void wallet2::rise_on_transfer2(const wallet_public::wallet_transfer_info& wti) - { - PROFILE_FUNC("wallet2::rise_on_transfer2"); - if (!m_do_rise_transfer) - return; - std::list balances; - uint64_t mined_balance = 0; - this->balance(balances, mined_balance); - m_wcallback->on_transfer2(wti, balances, mined_balance); - // second call for legacy callback handlers - //m_wcallback->on_transfer2(wti, balances, mined_balance); - } - //---------------------------------------------------------------------------------------------------- - /* - void wallet2::handle_money_spent2(const currency::block& b, - const currency::transaction& in_tx, - uint64_t amount, - const money_transfer2_details& td, - const std::vector& recipients, - const std::vector& remote_aliases) - { - m_transfer_history.push_back(AUTO_VAL_INIT(wallet_public::wallet_transfer_info())); - wallet_public::wallet_transfer_info& wti = m_transfer_history.back(); - wti.is_income = false; - - wti.remote_addresses = recipients; - wti.remote_aliases = remote_aliases; - prepare_wti(wti, get_block_height(b), get_block_datetime(b), in_tx, amount, td); - WLT_LOG_L1("[MONEY SPENT]: " << epee::serialization::store_t_to_json(wti)); - rise_on_transfer2(wti); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::handle_money_received2(const currency::block& b, const currency::transaction& tx, uint64_t amount, const money_transfer2_details& td) - { - //decrypt attachments - m_transfer_history.push_back(AUTO_VAL_INIT(wallet_public::wallet_transfer_info())); - wallet_public::wallet_transfer_info& wti = m_transfer_history.back(); - wti.is_income = true; - // TODO @#@# this function is only able to handle native coins atm, consider changing -- sowle - wti.asset_id = native_coin_asset_id; - prepare_wti(wti, get_block_height(b), get_block_datetime(b), tx, amount, td); - WLT_LOG_L1("[MONEY RECEIVED]: " << epee::serialization::store_t_to_json(wti)); - rise_on_transfer2(wti); - } - */ - //---------------------------------------------------------------------------------------------------- - void wallet2::load_wti_from_process_transaction_context(wallet_public::wallet_transfer_info& wti, const process_transaction_context& tx_process_context) - { - wti.remote_addresses = tx_process_context.recipients; - wti.remote_aliases = tx_process_context.remote_aliases; - for (const auto& bce : tx_process_context.total_balance_change) - { - wallet_public::wallet_sub_transfer_info wsti = AUTO_VAL_INIT(wsti); - wsti.asset_id = bce.first; - if (bce.second == 0) - { - continue; - } - else if (bce.second > 0) - { - //in transfer - wsti.is_income = true; - wsti.amount = static_cast(bce.second); - } - else - { - //out transfer - wsti.is_income = false; - wsti.amount = static_cast(bce.second * (-1)); - } - wti.subtransfers.push_back(wsti); - } - } - //---------------------------------------------------------------------------------------------------- - void wallet2::handle_money(const currency::block& b, const process_transaction_context& tx_process_context) - { - m_transfer_history.push_back(AUTO_VAL_INIT(wallet_public::wallet_transfer_info())); - wallet_public::wallet_transfer_info& wti = m_transfer_history.back(); - prepare_wti(wti, tx_process_context); - WLT_LOG_L1("[MONEY SPENT]: " << epee::serialization::store_t_to_json(wti)); - rise_on_transfer2(wti); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::process_unconfirmed(const currency::transaction& tx, std::vector& recipients, std::vector& remote_aliases) - { - auto unconf_it = m_unconfirmed_txs.find(get_transaction_hash(tx)); - if (unconf_it != m_unconfirmed_txs.end()) - { - wallet_public::wallet_transfer_info& wti = unconf_it->second; - recipients = wti.remote_addresses; - remote_aliases = wti.remote_aliases; - - m_unconfirmed_txs.erase(unconf_it); - } - } - //---------------------------------------------------------------------------------------------------- - - void wallet2::unprocess_htlc_triggers_on_block_removed(uint64_t height) - { - if (!m_htlcs.size()) - return; - - if (height > m_htlcs.rbegin()->first) - { - //there is no active htlc that at this height - CHECK_AND_ASSERT_MES(m_active_htlcs.size() == 0, void(), "Self check failed: m_active_htlcs.size() = " << m_active_htlcs.size()); - return; - } - //we have to check if there is a htlc that has to become deactivated - auto pair_of_it = m_htlcs.equal_range(height); - for (auto it = pair_of_it.first; it != pair_of_it.second; it++) - { - auto& tr = m_transfers.at(it->second.transfer_index); - //found contract that supposed to be re-activated and set to active - if (it->second.is_wallet_owns_redeem) - { - // this means that wallet received atomic as proposal but never activated it, and now we back to phase where out can be activated - //but we keep spend flag anyway - tr.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; //re assure that it has spent flag - tr.m_spent_height = 0; - } - else - { - // this means that wallet created atomic by itself, and second part didn't redeem it, - // so refund money became available, and now we back again to unavailable state - tr.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; //reset spent flag - m_found_free_amounts.clear(); //reset free amounts cache - tr.m_spent_height = 0; - } - //re-add to active contracts - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(tr.m_ptx_wallet_info->m_tx.vout[tr.m_internal_output_index].type() == typeid(tx_out_bare), std::string("Unexprected type of out in unprocess_htlc_triggers_on_block_removed : ") + tr.m_ptx_wallet_info->m_tx.vout[tr.m_internal_output_index].type().name()); - auto pair_key = std::make_pair(tr.m_amount, tr.m_global_output_index); - auto it_active_htlc = m_active_htlcs.find(pair_key); - if (it_active_htlc != m_active_htlcs.end()) - { - LOG_ERROR("Error at putting back htlc: already exist?"); - it_active_htlc->second = it->second.transfer_index; - - } - else - { - m_active_htlcs[pair_key] = it->second.transfer_index; - } - - const crypto::hash tx_id = tr.tx_hash(); - auto tx_id_it = m_active_htlcs_txid.find(tx_id); - if (tx_id_it != m_active_htlcs_txid.end()) - { - LOG_ERROR("Error at putting back htlc_txid: already exist?"); - tx_id_it->second = it->second.transfer_index; - - } - else - { - m_active_htlcs_txid[tx_id] = it->second.transfer_index; - } - } - } - void wallet2::process_htlc_triggers_on_block_added(uint64_t height) - { - if (!m_htlcs.size()) - return; - - if (height > m_htlcs.rbegin()->first) - { - //there is no active htlc that at this height - CHECK_AND_ASSERT_MES(m_active_htlcs.size() == 0, void(), "Self check failed: m_active_htlcs.size() = " << m_active_htlcs.size()); - return; - } - //we have to check if there is a htlc that has to become deactivated - auto pair_of_it = m_htlcs.equal_range(height); - for (auto it = pair_of_it.first; it != pair_of_it.second; it++) - { - auto& tr = m_transfers.at(it->second.transfer_index); - //found contract that supposed to be deactivated and set to innactive - if (it->second.is_wallet_owns_redeem) - { - // this means that wallet received atomic as proposal but never activated it, money returned to initiator - tr.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; //re assure that it has spent flag - tr.m_spent_height = height; - } - else - { - // this means that wallet created atomic by itself, and second part didn't redeem it, so refund money should become available - tr.m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_SPENT); //reset spent flag - m_found_free_amounts.clear(); //reset free amounts cache - tr.m_spent_height = 0; - } - - //reset cache - m_found_free_amounts.clear(); - - //remove it from active contracts - CHECK_AND_ASSERT_MES(tr.m_ptx_wallet_info->m_tx.vout[tr.m_internal_output_index].type() == typeid(tx_out_bare), void(), "Unexpected type out in process_htlc_triggers_on_block_added: " << tr.m_ptx_wallet_info->m_tx.vout[tr.m_internal_output_index].type().name()); - uint64_t amount = tr.m_amount; - - auto it_active_htlc = m_active_htlcs.find(std::make_pair(amount, tr.m_global_output_index)); - if (it_active_htlc == m_active_htlcs.end()) - { - LOG_ERROR("Erasing active htlc(m_active_htlcs), but it seems to be already erased"); - } - else - { - m_active_htlcs.erase(it_active_htlc); - auto it_tx = m_active_htlcs_txid.find(tr.tx_hash()); - if (it_tx == m_active_htlcs_txid.end()) - { - LOG_ERROR("Erasing active htlc(;), but it seems to be already erased"); - } - else - { - m_active_htlcs_txid.erase(it_tx); - } - } - } - } - //---------------------------------------------------------------------------------------------------- - void wallet2::process_new_blockchain_entry(const currency::block& b, const currency::block_direct_data_entry& bche, const crypto::hash& bl_id, uint64_t height) - { - //handle transactions from new block - THROW_IF_TRUE_WALLET_EX(height != get_blockchain_current_size() && - !(height == m_minimum_height || get_blockchain_current_size() <= 1), error::wallet_internal_error, - "current_index=" + std::to_string(height) + ", get_blockchain_current_height()=" + std::to_string(get_blockchain_current_size())); - - - - - //optimization: seeking only for blocks that are not older then the wallet creation time plus 1 day. 1 day is for possible user incorrect time setup - const std::vector* pglobal_index = nullptr; - if (get_block_height(b) > get_wallet_minimum_height()) // b.timestamp + 60 * 60 * 24 > m_account.get_createtime()) - { - pglobal_index = nullptr; - if (bche.coinbase_ptr.get()) - { - pglobal_index = &(bche.coinbase_ptr->m_global_output_indexes); - } - TIME_MEASURE_START(miner_tx_handle_time); - process_new_transaction(b.miner_tx, height, b, pglobal_index); - TIME_MEASURE_FINISH(miner_tx_handle_time); - - TIME_MEASURE_START(txs_handle_time); - size_t count = 0; - for (const auto& tx_entry : bche.txs_ptr) - { - if (b.tx_hashes.size() < count || currency::get_transaction_hash(tx_entry->tx) != b.tx_hashes[count]) - { - LOG_ERROR("Found tx order fail in process_new_blockchain_entry: count=" << count - << ", b.tx_hashes.size() = " << b.tx_hashes.size() << ", tx real id: " << currency::get_transaction_hash(tx_entry->tx) << ", bl_id: " << bl_id); - } - process_new_transaction(tx_entry->tx, height, b, &(tx_entry->m_global_output_indexes)); - count++; - } - TIME_MEASURE_FINISH(txs_handle_time); - WLT_LOG_L3("Processed block: " << bl_id << ", height " << height << ", " << miner_tx_handle_time + txs_handle_time << "(" << miner_tx_handle_time << "/" << txs_handle_time << ")ms"); - } - else - { - WLT_LOG_L3("Skipped block by timestamp, height: " << height << ", block time " << b.timestamp << ", account time " << m_account.get_createtime()); - } - m_chain.push_new_block_id(bl_id, height); //m_blockchain.push_back(bl_id); - m_last_bc_timestamp = b.timestamp; - if (!is_pos_block(b)) - m_last_pow_block_h = height; - - - process_htlc_triggers_on_block_added(height); - m_wcallback->on_new_block(height, b); - } - //---------------------------------------------------------------------------------------------------- - - //---------------------------------------------------------------------------------------------------- - // void wallet2::get_short_chain_history(std::list& ids) - // { - // ids.clear(); - // size_t i = 0; - // size_t current_multiplier = 1; - // size_t sz = get_blockchain_current_height(); - // if(!sz) - // return; - // size_t current_back_offset = 1; - // bool genesis_included = false; - // while(current_back_offset < sz) - // { - // ids.push_back(m_blockchain[sz-current_back_offset]); - // if(sz-current_back_offset == 0) - // genesis_included = true; - // if(i < 10) - // { - // ++current_back_offset; - // }else - // { - // current_back_offset += current_multiplier *= 2; - // } - // ++i; - // } - // if(!genesis_included) - // ids.push_back(m_blockchain[0]); - // } - - //---------------------------------------------------------------------------------------------------- - void wallet2::set_minimum_height(uint64_t h) - { - m_minimum_height = h; - } - //---------------------------------------------------------------------------------------------------- - uint64_t wallet2::get_wallet_minimum_height() - { - - if (m_minimum_height != WALLET_MINIMUM_HEIGHT_UNSET_CONST) - return m_minimum_height; - - currency::COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE::request req = AUTO_VAL_INIT(req); - currency::COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE::response res = AUTO_VAL_INIT(res); - req.timestamp = m_account.get_createtime(); - bool r = m_core_proxy->call_COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE(req, res); - THROW_IF_FALSE_WALLET_EX(r, error::no_connection_to_daemon, "call_COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE"); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(res.status == API_RETURN_CODE_OK, "FAILED TO CALL COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE"); - m_minimum_height = res.h; - return res.h; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::pull_blocks(size_t& blocks_added, std::atomic& stop, bool& full_reset_needed) - { - blocks_added = 0; - currency::COMMAND_RPC_GET_BLOCKS_DIRECT::request req = AUTO_VAL_INIT(req); - currency::COMMAND_RPC_GET_BLOCKS_DIRECT::response res = AUTO_VAL_INIT(res); - - req.minimum_height = get_wallet_minimum_height(); - if (req.minimum_height > m_height_of_start_sync) - m_height_of_start_sync = req.minimum_height; - - m_chain.get_short_chain_history(req.block_ids); - bool r = m_core_proxy->call_COMMAND_RPC_GET_BLOCKS_DIRECT(req, res); - if (!r) - throw error::no_connection_to_daemon(LOCATION_STR, "getblocks.bin"); - if (res.status == API_RETURN_CODE_GENESIS_MISMATCH) - { - WLT_LOG_MAGENTA("Reseting genesis block...", LOG_LEVEL_0); - COMMAND_RPC_GET_BLOCKS_DETAILS::request gbd_req = AUTO_VAL_INIT(gbd_req); - COMMAND_RPC_GET_BLOCKS_DETAILS::response gbd_res = AUTO_VAL_INIT(gbd_res); - gbd_req.height_start = 0; - gbd_req.count = 1; - gbd_req.ignore_transactions = true; - r = m_core_proxy->call_COMMAND_RPC_GET_BLOCKS_DETAILS(gbd_req, gbd_res); - THROW_IF_TRUE_WALLET_EX(!r, error::no_connection_to_daemon, "get_blocks_details"); - THROW_IF_TRUE_WALLET_EX(gbd_res.status != API_RETURN_CODE_OK, error::get_blocks_error, gbd_res.status); - THROW_IF_TRUE_WALLET_EX(gbd_res.blocks.size() == 0, error::get_blocks_error, gbd_res.status); - crypto::hash new_genesis_id = null_hash; - r = string_tools::parse_tpod_from_hex_string(gbd_res.blocks.back().id, new_genesis_id); - THROW_IF_TRUE_WALLET_EX(!r, error::no_connection_to_daemon, "get_blocks_details"); - reset_all(); - m_minimum_height = req.minimum_height; - m_chain.set_genesis(new_genesis_id); - WLT_LOG_MAGENTA("New genesis set for wallet: " << new_genesis_id, LOG_LEVEL_0); - m_chain.get_short_chain_history(req.block_ids); - //req.block_ids.push_back(new_genesis_id); - bool r = m_core_proxy->call_COMMAND_RPC_GET_BLOCKS_DIRECT(req, res); - THROW_IF_TRUE_WALLET_EX(!r, error::no_connection_to_daemon, "getblocks.bin"); - } - if (res.status == API_RETURN_CODE_BUSY) - { - WLT_LOG_L1("Core is busy, pull cancelled"); - m_core_proxy->get_editable_proxy_diagnostic_info()->is_busy = true; - stop = true; - return; - } - m_core_proxy->get_editable_proxy_diagnostic_info()->is_busy = false; - THROW_IF_TRUE_WALLET_EX(res.status != API_RETURN_CODE_OK, error::get_blocks_error, res.status); - THROW_IF_TRUE_WALLET_EX(get_blockchain_current_size() && get_blockchain_current_size() <= res.start_height && res.start_height != m_minimum_height, error::wallet_internal_error, - "wrong daemon response: m_start_height=" + std::to_string(res.start_height) + - " not less than local blockchain size=" + std::to_string(get_blockchain_current_size())); - - try - { - handle_pulled_blocks(blocks_added, stop, res, full_reset_needed); - } - catch (const tools::error::wallet_error_resync_needed& /*v*/) - { - full_reset_needed = true; - m_full_resync_requested_at_h = get_blockchain_current_size() - blocks_added; - } - - if (full_reset_needed) - { - //back up m_unconfirmed_txs - //back up std::unordered_map m_tx_keys; - unconfirmed_txs_container tmp_unconfirmed = m_unconfirmed_txs; - tx_secrete_keys_container tmp_secrete_keys = m_tx_keys; - crypto::hash genesis = m_chain.get_genesis(); - reset_all(); - m_chain.set_genesis(genesis); - m_unconfirmed_txs = tmp_unconfirmed; - m_tx_keys = tmp_secrete_keys; - } - } - - //---------------------------------------------------------------------------------------------------- - void wallet2::handle_pulled_blocks(size_t& blocks_added, std::atomic& stop, - currency::COMMAND_RPC_GET_BLOCKS_DIRECT::response& res, bool& wallet_reset_needed) - { - size_t current_index = res.start_height; - m_last_known_daemon_height = res.current_height; - bool been_matched_block = false; - if (res.start_height == 0 && get_blockchain_current_size() == 1 && !res.blocks.empty()) - { - const currency::block& genesis = res.blocks.front().block_ptr->bl; - THROW_IF_TRUE_WALLET_EX(get_block_height(genesis) != 0, error::wallet_internal_error, "first block expected to be genesis"); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(res.blocks.front().coinbase_ptr, "Unexpected empty coinbase"); - process_genesis_if_needed(genesis, &(res.blocks.front().coinbase_ptr->m_global_output_indexes)); - res.blocks.pop_front(); - ++current_index; - been_matched_block = true; - } - - uint64_t last_matched_index = 0; - for (const auto& bl_entry : res.blocks) - { - if (stop) - break; - - const currency::block& bl = bl_entry.block_ptr->bl; - uint64_t height = get_block_height(bl); - uint64_t processed_blocks_count = get_blockchain_current_size(); - - //TODO: get_block_hash is slow - crypto::hash bl_id = get_block_hash(bl); - - if (processed_blocks_count != 1 && height > processed_blocks_count) - { - if (height != m_minimum_height) - { - //internal error: - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(false, - "height{" << height << "} > processed_blocks_count{" << processed_blocks_count << "}"); - } - else - { - //possible case, wallet rewound to m_minimum_height - m_chain.clear(); - } - } - else if (height == processed_blocks_count && been_matched_block) - { - //regular block handling - //self check - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(been_matched_block, - "internal error: been_matched_block == false on process_new_blockchain_entry, bl_id" << bl_id << "h=" << height - << " (start_height=" + std::to_string(res.start_height) + ")"); - - process_new_blockchain_entry(bl, bl_entry, bl_id, current_index); - ++blocks_added; - } - else - { - //checking if we need reorganize (might be just first matched block) - bool block_found = false; - bool block_matched = false; - bool full_reset_needed = false; - m_chain.check_if_block_matched(height, bl_id, block_found, block_matched, full_reset_needed); - if (block_found && block_matched) - { - //block matched in that number - last_matched_index = height; - been_matched_block = true; - WLT_LOG_L4("Block " << bl_id << " @ " << height << " is already in wallet's blockchain"); - } - else - { - //this should happen ONLY after block been matched, if not then is internal error - if (full_reset_needed) - { - last_matched_index = 0; - been_matched_block = true; + payment.m_amount = static_cast(bce.second); } else { - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(been_matched_block, - "unmatched block while never been mathced block"); + payment.subtransfers.push_back(payment_details_subtransfer{ bce.first, static_cast(bce.second) }); } - //TODO: take into account date of wallet creation - //reorganize - if (m_concise_mode && m_chain.get_blockchain_current_size() - (last_matched_index) > m_wallet_concise_mode_max_reorg_blocks) - { - m_full_resync_requested_at_h = m_chain.get_blockchain_current_size() - (last_matched_index + 1); - wallet_reset_needed = true; - return; - } - detach_blockchain(last_matched_index + 1); - process_new_blockchain_entry(bl, bl_entry, bl_id, height); - ++blocks_added; - } - } - - - ++current_index; - if (res.current_height > m_height_of_start_sync) - { - uint64_t next_percent = (100 * (current_index - m_height_of_start_sync)) / (res.current_height - m_height_of_start_sync); - if (next_percent != m_last_sync_percent) - { - m_wcallback->on_sync_progress(next_percent); - m_last_sync_percent = next_percent; } } + m_payments.emplace(wti.payment_id, payment); + WLT_LOG_L2("Payment found, id (hex): " << epee::string_tools::buff_to_hex_nodelimer(wti.payment_id) << ", tx: " << payment.m_tx_hash << ", amount: " << print_money_brief(payment.m_amount) << "subtransfers = " << payment.subtransfers.size()); } + } + return true; +} +void wallet2::rise_on_transfer2(const wallet_public::wallet_transfer_info& wti) +{ + PROFILE_FUNC("wallet2::rise_on_transfer2"); + if (!m_do_rise_transfer) + return; + std::list balances; + uint64_t mined_balance = 0; + this->balance(balances, mined_balance); + m_wcallback->on_transfer2(wti, balances, mined_balance); + // second call for legacy callback handlers + //m_wcallback->on_transfer2(wti, balances, mined_balance); +} +//---------------------------------------------------------------------------------------------------- +/* +void wallet2::handle_money_spent2(const currency::block& b, + const currency::transaction& in_tx, + uint64_t amount, + const money_transfer2_details& td, + const std::vector& recipients, + const std::vector& remote_aliases) +{ + m_transfer_history.push_back(AUTO_VAL_INIT(wallet_public::wallet_transfer_info())); + wallet_public::wallet_transfer_info& wti = m_transfer_history.back(); + wti.is_income = false; - WLT_LOG_L2("[PULL BLOCKS] " << res.start_height << " --> " << get_blockchain_current_size() - 1); - } - //---------------------------------------------------------------------------------------------------- - uint64_t wallet2::get_sync_progress() + wti.remote_addresses = recipients; + wti.remote_aliases = remote_aliases; + prepare_wti(wti, get_block_height(b), get_block_datetime(b), in_tx, amount, td); + WLT_LOG_L1("[MONEY SPENT]: " << epee::serialization::store_t_to_json(wti)); + rise_on_transfer2(wti); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::handle_money_received2(const currency::block& b, const currency::transaction& tx, uint64_t amount, const money_transfer2_details& td) +{ + //decrypt attachments + m_transfer_history.push_back(AUTO_VAL_INIT(wallet_public::wallet_transfer_info())); + wallet_public::wallet_transfer_info& wti = m_transfer_history.back(); + wti.is_income = true; + // TODO @#@# this function is only able to handle native coins atm, consider changing -- sowle + wti.asset_id = native_coin_asset_id; + prepare_wti(wti, get_block_height(b), get_block_datetime(b), tx, amount, td); + WLT_LOG_L1("[MONEY RECEIVED]: " << epee::serialization::store_t_to_json(wti)); + rise_on_transfer2(wti); +} +*/ +//---------------------------------------------------------------------------------------------------- +void wallet2::load_wti_from_process_transaction_context(wallet_public::wallet_transfer_info& wti, const process_transaction_context& tx_process_context) +{ + wti.remote_addresses = tx_process_context.recipients; + wti.remote_aliases = tx_process_context.remote_aliases; + for (const auto& bce : tx_process_context.total_balance_change) { - return m_last_sync_percent; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::refresh() - { - size_t blocks_fetched = 0; - refresh(blocks_fetched); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::refresh(size_t& blocks_fetched) - { - bool received_money = false; - m_stop = false; - refresh(blocks_fetched, received_money, m_stop); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::refresh(std::atomic& stop) - { - bool f; - size_t n; - refresh(n, f, stop); - } - //---------------------------------------------------------------------------------------------------- - detail::split_strategy_id_t wallet2::get_current_split_strategy() - { - if (is_need_to_split_outputs()) - return tools::detail::ssi_digit; - else - return tools::detail::ssi_void; - } - // - void wallet2::transfer(uint64_t amount, const currency::account_public_address& acc, currency::transaction& result_tx, const crypto::public_key& asset_id /* = currency::native_coin_asset_id */) - { - std::vector extra; - std::vector attachments; - - std::vector dst; - dst.resize(1); - dst.back().addr.push_back(acc); - dst.back().amount = amount; - dst.back().asset_id = asset_id; - this->transfer(dst, 0, 0, TX_DEFAULT_FEE, extra, attachments, get_current_split_strategy(), tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), result_tx); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::transfer(uint64_t amount, size_t fake_outs_count, const currency::account_public_address& acc, uint64_t fee /* = TX_DEFAULT_FEE*/, - const crypto::public_key& asset_id /* = currency::native_coin_asset_id */) - { - std::vector extra; - std::vector attachments; - transaction result_tx = AUTO_VAL_INIT(result_tx); - - std::vector dst; - dst.resize(1); - dst.back().addr.push_back(acc); - dst.back().amount = amount; - dst.back().asset_id = asset_id; - this->transfer(dst, fake_outs_count, 0, fee, extra, attachments, get_current_split_strategy(), tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), result_tx); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::transfer(uint64_t amount, const currency::account_public_address& acc, const crypto::public_key& asset_id /* = currency::native_coin_asset_id */) - { - transaction result_tx = AUTO_VAL_INIT(result_tx); - this->transfer(amount, acc, result_tx, asset_id); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::reset_creation_time(uint64_t timestamp) - { - m_account.set_createtime(timestamp); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::update_current_tx_limit() - { - currency::COMMAND_RPC_GET_INFO::request req = AUTO_VAL_INIT(req); - currency::COMMAND_RPC_GET_INFO::response res = AUTO_VAL_INIT(res); - bool r = m_core_proxy->call_COMMAND_RPC_GET_INFO(req, res); - THROW_IF_TRUE_WALLET_EX(!r, error::no_connection_to_daemon, "getinfo"); - THROW_IF_TRUE_WALLET_EX(res.status == API_RETURN_CODE_BUSY, error::daemon_busy, "getinfo"); - THROW_IF_TRUE_WALLET_EX(res.status != API_RETURN_CODE_OK, error::get_blocks_error, res.status); - THROW_IF_TRUE_WALLET_EX(res.current_blocks_median < CURRENCY_BLOCK_GRANTED_FULL_REWARD_ZONE, error::get_blocks_error, "bad median size"); - m_upper_transaction_size_limit = res.current_blocks_median - CURRENCY_COINBASE_BLOB_RESERVED_SIZE; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::has_related_alias_entry_unconfirmed(const currency::transaction& tx) - { - std::string local_adr = m_account.get_public_address_str(); - tx_extra_info tei = AUTO_VAL_INIT(tei); - parse_and_validate_tx_extra(tx, tei); - if (tei.m_alias.m_alias.size()) + wallet_public::wallet_sub_transfer_info wsti = AUTO_VAL_INIT(wsti); + wsti.asset_id = bce.first; + if (bce.second == 0) { - //have some check address involved - if (tei.m_alias.m_address.spend_public_key == m_account.get_keys().account_address.spend_public_key && - tei.m_alias.m_address.view_public_key == m_account.get_keys().account_address.view_public_key) - return true; + continue; + } + else if (bce.second > 0) + { + //in transfer + wsti.is_income = true; + wsti.amount = static_cast(bce.second); + } + else + { + //out transfer + wsti.is_income = false; + wsti.amount = static_cast(bce.second * (-1)); + } + wti.subtransfers.push_back(wsti); + } +} +//---------------------------------------------------------------------------------------------------- +void wallet2::handle_money(const currency::block& b, const process_transaction_context& tx_process_context) +{ + m_transfer_history.push_back(AUTO_VAL_INIT(wallet_public::wallet_transfer_info())); + wallet_public::wallet_transfer_info& wti = m_transfer_history.back(); + prepare_wti(wti, tx_process_context); + WLT_LOG_L1("[MONEY SPENT]: " << epee::serialization::store_t_to_json(wti)); + rise_on_transfer2(wti); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::process_unconfirmed(const currency::transaction& tx, std::vector& recipients, std::vector& remote_aliases) +{ + auto unconf_it = m_unconfirmed_txs.find(get_transaction_hash(tx)); + if (unconf_it != m_unconfirmed_txs.end()) + { + wallet_public::wallet_transfer_info& wti = unconf_it->second; + recipients = wti.remote_addresses; + remote_aliases = wti.remote_aliases; - //check if it's update and address before was our address - currency::COMMAND_RPC_GET_ALIAS_DETAILS::request req = AUTO_VAL_INIT(req); - currency::COMMAND_RPC_GET_ALIAS_DETAILS::response res = AUTO_VAL_INIT(res); - req.alias = tei.m_alias.m_alias; - m_core_proxy->call_COMMAND_RPC_GET_ALIAS_DETAILS(req, res); - if (res.status != API_RETURN_CODE_OK) - return false; - if (local_adr == res.alias_details.address) - return true; + m_unconfirmed_txs.erase(unconf_it); + } +} +//---------------------------------------------------------------------------------------------------- + +void wallet2::unprocess_htlc_triggers_on_block_removed(uint64_t height) +{ + if (!m_htlcs.size()) + return; + + if (height > m_htlcs.rbegin()->first) + { + //there is no active htlc that at this height + CHECK_AND_ASSERT_MES(m_active_htlcs.size() == 0, void(), "Self check failed: m_active_htlcs.size() = " << m_active_htlcs.size()); + return; + } + //we have to check if there is a htlc that has to become deactivated + auto pair_of_it = m_htlcs.equal_range(height); + for (auto it = pair_of_it.first; it != pair_of_it.second; it++) + { + auto& tr = m_transfers.at(it->second.transfer_index); + //found contract that supposed to be re-activated and set to active + if (it->second.is_wallet_owns_redeem) + { + // this means that wallet received atomic as proposal but never activated it, and now we back to phase where out can be activated + //but we keep spend flag anyway + tr.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; //re assure that it has spent flag + tr.m_spent_height = 0; + } + else + { + // this means that wallet created atomic by itself, and second part didn't redeem it, + // so refund money became available, and now we back again to unavailable state + tr.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; //reset spent flag + m_found_free_amounts.clear(); //reset free amounts cache + tr.m_spent_height = 0; + } + //re-add to active contracts + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(tr.m_ptx_wallet_info->m_tx.vout[tr.m_internal_output_index].type() == typeid(tx_out_bare), std::string("Unexprected type of out in unprocess_htlc_triggers_on_block_removed : ") + tr.m_ptx_wallet_info->m_tx.vout[tr.m_internal_output_index].type().name()); + auto pair_key = std::make_pair(tr.m_amount, tr.m_global_output_index); + auto it_active_htlc = m_active_htlcs.find(pair_key); + if (it_active_htlc != m_active_htlcs.end()) + { + LOG_ERROR("Error at putting back htlc: already exist?"); + it_active_htlc->second = it->second.transfer_index; } - return false; + else + { + m_active_htlcs[pair_key] = it->second.transfer_index; + } + + const crypto::hash tx_id = tr.tx_hash(); + auto tx_id_it = m_active_htlcs_txid.find(tx_id); + if (tx_id_it != m_active_htlcs_txid.end()) + { + LOG_ERROR("Error at putting back htlc_txid: already exist?"); + tx_id_it->second = it->second.transfer_index; + + } + else + { + m_active_htlcs_txid[tx_id] = it->second.transfer_index; + } } - //---------------------------------------------------------------------------------------------------- - bool wallet2::has_bare_unspent_outputs() const +} +void wallet2::process_htlc_triggers_on_block_added(uint64_t height) +{ + if (!m_htlcs.size()) + return; + + if (height > m_htlcs.rbegin()->first) { - if (m_account.get_createtime() > ZANO_HARDFORK_04_TIMESTAMP_ACTUAL) + //there is no active htlc that at this height + CHECK_AND_ASSERT_MES(m_active_htlcs.size() == 0, void(), "Self check failed: m_active_htlcs.size() = " << m_active_htlcs.size()); + return; + } + //we have to check if there is a htlc that has to become deactivated + auto pair_of_it = m_htlcs.equal_range(height); + for (auto it = pair_of_it.first; it != pair_of_it.second; it++) + { + auto& tr = m_transfers.at(it->second.transfer_index); + //found contract that supposed to be deactivated and set to innactive + if (it->second.is_wallet_owns_redeem) + { + // this means that wallet received atomic as proposal but never activated it, money returned to initiator + tr.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; //re assure that it has spent flag + tr.m_spent_height = height; + } + else + { + // this means that wallet created atomic by itself, and second part didn't redeem it, so refund money should become available + tr.m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_SPENT); //reset spent flag + m_found_free_amounts.clear(); //reset free amounts cache + tr.m_spent_height = 0; + } + + //reset cache + m_found_free_amounts.clear(); + + //remove it from active contracts + CHECK_AND_ASSERT_MES(tr.m_ptx_wallet_info->m_tx.vout[tr.m_internal_output_index].type() == typeid(tx_out_bare), void(), "Unexpected type out in process_htlc_triggers_on_block_added: " << tr.m_ptx_wallet_info->m_tx.vout[tr.m_internal_output_index].type().name()); + uint64_t amount = tr.m_amount; + + auto it_active_htlc = m_active_htlcs.find(std::make_pair(amount, tr.m_global_output_index)); + if (it_active_htlc == m_active_htlcs.end()) + { + LOG_ERROR("Erasing active htlc(m_active_htlcs), but it seems to be already erased"); + } + else + { + m_active_htlcs.erase(it_active_htlc); + auto it_tx = m_active_htlcs_txid.find(tr.tx_hash()); + if (it_tx == m_active_htlcs_txid.end()) + { + LOG_ERROR("Erasing active htlc(;), but it seems to be already erased"); + } + else + { + m_active_htlcs_txid.erase(it_tx); + } + } + } +} +//---------------------------------------------------------------------------------------------------- +void wallet2::process_new_blockchain_entry(const currency::block& b, const currency::block_direct_data_entry& bche, const crypto::hash& bl_id, uint64_t height) +{ + //handle transactions from new block + THROW_IF_TRUE_WALLET_EX(height != get_blockchain_current_size() && + !(height == m_minimum_height || get_blockchain_current_size() <= 1), error::wallet_internal_error, + "current_index=" + std::to_string(height) + ", get_blockchain_current_height()=" + std::to_string(get_blockchain_current_size())); + + + + + //optimization: seeking only for blocks that are not older then the wallet creation time plus 1 day. 1 day is for possible user incorrect time setup + const std::vector* pglobal_index = nullptr; + if (get_block_height(b) > get_wallet_minimum_height()) // b.timestamp + 60 * 60 * 24 > m_account.get_createtime()) + { + pglobal_index = nullptr; + if (bche.coinbase_ptr.get()) + { + pglobal_index = &(bche.coinbase_ptr->m_global_output_indexes); + } + TIME_MEASURE_START(miner_tx_handle_time); + process_new_transaction(b.miner_tx, height, b, pglobal_index); + TIME_MEASURE_FINISH(miner_tx_handle_time); + + TIME_MEASURE_START(txs_handle_time); + size_t count = 0; + for (const auto& tx_entry : bche.txs_ptr) + { + if (b.tx_hashes.size() < count || currency::get_transaction_hash(tx_entry->tx) != b.tx_hashes[count]) + { + LOG_ERROR("Found tx order fail in process_new_blockchain_entry: count=" << count + << ", b.tx_hashes.size() = " << b.tx_hashes.size() << ", tx real id: " << currency::get_transaction_hash(tx_entry->tx) << ", bl_id: " << bl_id); + } + process_new_transaction(tx_entry->tx, height, b, &(tx_entry->m_global_output_indexes)); + count++; + } + TIME_MEASURE_FINISH(txs_handle_time); + WLT_LOG_L3("Processed block: " << bl_id << ", height " << height << ", " << miner_tx_handle_time + txs_handle_time << "(" << miner_tx_handle_time << "/" << txs_handle_time << ")ms"); + } + else + { + WLT_LOG_L3("Skipped block by timestamp, height: " << height << ", block time " << b.timestamp << ", account time " << m_account.get_createtime()); + } + m_chain.push_new_block_id(bl_id, height); //m_blockchain.push_back(bl_id); + m_last_bc_timestamp = b.timestamp; + if (!is_pos_block(b)) + m_last_pow_block_h = height; + + + process_htlc_triggers_on_block_added(height); + m_wcallback->on_new_block(height, b); +} +//---------------------------------------------------------------------------------------------------- + +//---------------------------------------------------------------------------------------------------- +// void wallet2::get_short_chain_history(std::list& ids) +// { +// ids.clear(); +// size_t i = 0; +// size_t current_multiplier = 1; +// size_t sz = get_blockchain_current_height(); +// if(!sz) +// return; +// size_t current_back_offset = 1; +// bool genesis_included = false; +// while(current_back_offset < sz) +// { +// ids.push_back(m_blockchain[sz-current_back_offset]); +// if(sz-current_back_offset == 0) +// genesis_included = true; +// if(i < 10) +// { +// ++current_back_offset; +// }else +// { +// current_back_offset += current_multiplier *= 2; +// } +// ++i; +// } +// if(!genesis_included) +// ids.push_back(m_blockchain[0]); +// } + +//---------------------------------------------------------------------------------------------------- +void wallet2::set_minimum_height(uint64_t h) +{ + m_minimum_height = h; +} +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::get_wallet_minimum_height() +{ + + if (m_minimum_height != WALLET_MINIMUM_HEIGHT_UNSET_CONST) + return m_minimum_height; + + currency::COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE::request req = AUTO_VAL_INIT(req); + currency::COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE::response res = AUTO_VAL_INIT(res); + req.timestamp = m_account.get_createtime(); + bool r = m_core_proxy->call_COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE(req, res); + THROW_IF_FALSE_WALLET_EX(r, error::no_connection_to_daemon, "call_COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE"); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(res.status == API_RETURN_CODE_OK, "FAILED TO CALL COMMAND_RPC_GET_EST_HEIGHT_FROM_DATE"); + m_minimum_height = res.h; + return res.h; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::pull_blocks(size_t& blocks_added, std::atomic& stop, bool& full_reset_needed) +{ + blocks_added = 0; + currency::COMMAND_RPC_GET_BLOCKS_DIRECT::request req = AUTO_VAL_INIT(req); + currency::COMMAND_RPC_GET_BLOCKS_DIRECT::response res = AUTO_VAL_INIT(res); + + req.minimum_height = get_wallet_minimum_height(); + if (req.minimum_height > m_height_of_start_sync) + m_height_of_start_sync = req.minimum_height; + + m_chain.get_short_chain_history(req.block_ids); + bool r = m_core_proxy->call_COMMAND_RPC_GET_BLOCKS_DIRECT(req, res); + if (!r) + throw error::no_connection_to_daemon(LOCATION_STR, "getblocks.bin"); + if (res.status == API_RETURN_CODE_GENESIS_MISMATCH) + { + WLT_LOG_MAGENTA("Reseting genesis block...", LOG_LEVEL_0); + COMMAND_RPC_GET_BLOCKS_DETAILS::request gbd_req = AUTO_VAL_INIT(gbd_req); + COMMAND_RPC_GET_BLOCKS_DETAILS::response gbd_res = AUTO_VAL_INIT(gbd_res); + gbd_req.height_start = 0; + gbd_req.count = 1; + gbd_req.ignore_transactions = true; + r = m_core_proxy->call_COMMAND_RPC_GET_BLOCKS_DETAILS(gbd_req, gbd_res); + THROW_IF_TRUE_WALLET_EX(!r, error::no_connection_to_daemon, "get_blocks_details"); + THROW_IF_TRUE_WALLET_EX(gbd_res.status != API_RETURN_CODE_OK, error::get_blocks_error, gbd_res.status); + THROW_IF_TRUE_WALLET_EX(gbd_res.blocks.size() == 0, error::get_blocks_error, gbd_res.status); + crypto::hash new_genesis_id = null_hash; + r = string_tools::parse_tpod_from_hex_string(gbd_res.blocks.back().id, new_genesis_id); + THROW_IF_TRUE_WALLET_EX(!r, error::no_connection_to_daemon, "get_blocks_details"); + reset_all(); + m_minimum_height = req.minimum_height; + m_chain.set_genesis(new_genesis_id); + WLT_LOG_MAGENTA("New genesis set for wallet: " << new_genesis_id, LOG_LEVEL_0); + m_chain.get_short_chain_history(req.block_ids); + //req.block_ids.push_back(new_genesis_id); + bool r = m_core_proxy->call_COMMAND_RPC_GET_BLOCKS_DIRECT(req, res); + THROW_IF_TRUE_WALLET_EX(!r, error::no_connection_to_daemon, "getblocks.bin"); + } + if (res.status == API_RETURN_CODE_BUSY) + { + WLT_LOG_L1("Core is busy, pull cancelled"); + m_core_proxy->get_editable_proxy_diagnostic_info()->is_busy = true; + stop = true; + return; + } + m_core_proxy->get_editable_proxy_diagnostic_info()->is_busy = false; + THROW_IF_TRUE_WALLET_EX(res.status != API_RETURN_CODE_OK, error::get_blocks_error, res.status); + THROW_IF_TRUE_WALLET_EX(get_blockchain_current_size() && get_blockchain_current_size() <= res.start_height && res.start_height != m_minimum_height, error::wallet_internal_error, + "wrong daemon response: m_start_height=" + std::to_string(res.start_height) + + " not less than local blockchain size=" + std::to_string(get_blockchain_current_size())); + + try + { + handle_pulled_blocks(blocks_added, stop, res, full_reset_needed); + } + catch (const tools::error::wallet_error_resync_needed& /*v*/) + { + full_reset_needed = true; + m_full_resync_requested_at_h = get_blockchain_current_size() - blocks_added; + } + + if (full_reset_needed) + { + //back up m_unconfirmed_txs + //back up std::unordered_map m_tx_keys; + unconfirmed_txs_container tmp_unconfirmed = m_unconfirmed_txs; + tx_secrete_keys_container tmp_secrete_keys = m_tx_keys; + crypto::hash genesis = m_chain.get_genesis(); + reset_all(); + m_chain.set_genesis(genesis); + m_unconfirmed_txs = tmp_unconfirmed; + m_tx_keys = tmp_secrete_keys; + } +} + +//---------------------------------------------------------------------------------------------------- +void wallet2::handle_pulled_blocks(size_t& blocks_added, std::atomic& stop, + currency::COMMAND_RPC_GET_BLOCKS_DIRECT::response& res, bool& wallet_reset_needed) +{ + size_t current_index = res.start_height; + m_last_known_daemon_height = res.current_height; + bool been_matched_block = false; + if (res.start_height == 0 && get_blockchain_current_size() == 1 && !res.blocks.empty()) + { + const currency::block& genesis = res.blocks.front().block_ptr->bl; + THROW_IF_TRUE_WALLET_EX(get_block_height(genesis) != 0, error::wallet_internal_error, "first block expected to be genesis"); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(res.blocks.front().coinbase_ptr, "Unexpected empty coinbase"); + process_genesis_if_needed(genesis, &(res.blocks.front().coinbase_ptr->m_global_output_indexes)); + res.blocks.pop_front(); + ++current_index; + been_matched_block = true; + } + + uint64_t last_matched_index = 0; + for (const auto& bl_entry : res.blocks) + { + if (stop) + break; + + const currency::block& bl = bl_entry.block_ptr->bl; + uint64_t height = get_block_height(bl); + uint64_t processed_blocks_count = get_blockchain_current_size(); + + //TODO: get_block_hash is slow + crypto::hash bl_id = get_block_hash(bl); + + if (processed_blocks_count != 1 && height > processed_blocks_count) + { + if (height != m_minimum_height) + { + //internal error: + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(false, + "height{" << height << "} > processed_blocks_count{" << processed_blocks_count << "}"); + } + else + { + //possible case, wallet rewound to m_minimum_height + m_chain.clear(); + } + } + else if (height == processed_blocks_count && been_matched_block) + { + //regular block handling + //self check + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(been_matched_block, + "internal error: been_matched_block == false on process_new_blockchain_entry, bl_id" << bl_id << "h=" << height + << " (start_height=" + std::to_string(res.start_height) + ")"); + + process_new_blockchain_entry(bl, bl_entry, bl_id, current_index); + ++blocks_added; + } + else + { + //checking if we need reorganize (might be just first matched block) + bool block_found = false; + bool block_matched = false; + bool full_reset_needed = false; + m_chain.check_if_block_matched(height, bl_id, block_found, block_matched, full_reset_needed); + if (block_found && block_matched) + { + //block matched in that number + last_matched_index = height; + been_matched_block = true; + WLT_LOG_L4("Block " << bl_id << " @ " << height << " is already in wallet's blockchain"); + } + else + { + //this should happen ONLY after block been matched, if not then is internal error + if (full_reset_needed) + { + last_matched_index = 0; + been_matched_block = true; + } + else + { + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(been_matched_block, + "unmatched block while never been mathced block"); + } + //TODO: take into account date of wallet creation + //reorganize + if (m_concise_mode && m_chain.get_blockchain_current_size() - (last_matched_index) > m_wallet_concise_mode_max_reorg_blocks) + { + m_full_resync_requested_at_h = m_chain.get_blockchain_current_size() - (last_matched_index + 1); + wallet_reset_needed = true; + return; + } + detach_blockchain(last_matched_index + 1); + process_new_blockchain_entry(bl, bl_entry, bl_id, height); + ++blocks_added; + } + } + + + ++current_index; + if (res.current_height > m_height_of_start_sync) + { + uint64_t next_percent = (100 * (current_index - m_height_of_start_sync)) / (res.current_height - m_height_of_start_sync); + if (next_percent != m_last_sync_percent) + { + m_wcallback->on_sync_progress(next_percent); + m_last_sync_percent = next_percent; + } + } + } + + WLT_LOG_L2("[PULL BLOCKS] " << res.start_height << " --> " << get_blockchain_current_size() - 1); +} +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::get_sync_progress() +{ + return m_last_sync_percent; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::refresh() +{ + size_t blocks_fetched = 0; + refresh(blocks_fetched); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::refresh(size_t& blocks_fetched) +{ + bool received_money = false; + m_stop = false; + refresh(blocks_fetched, received_money, m_stop); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::refresh(std::atomic& stop) +{ + bool f; + size_t n; + refresh(n, f, stop); +} +//---------------------------------------------------------------------------------------------------- +detail::split_strategy_id_t wallet2::get_current_split_strategy() +{ + if (is_need_to_split_outputs()) + return tools::detail::ssi_digit; + else + return tools::detail::ssi_void; +} +// +void wallet2::transfer(uint64_t amount, const currency::account_public_address& acc, currency::transaction& result_tx, const crypto::public_key& asset_id /* = currency::native_coin_asset_id */) +{ + std::vector extra; + std::vector attachments; + + std::vector dst; + dst.resize(1); + dst.back().addr.push_back(acc); + dst.back().amount = amount; + dst.back().asset_id = asset_id; + this->transfer(dst, 0, 0, TX_DEFAULT_FEE, extra, attachments, get_current_split_strategy(), tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), result_tx); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::transfer(uint64_t amount, size_t fake_outs_count, const currency::account_public_address& acc, uint64_t fee /* = TX_DEFAULT_FEE*/, + const crypto::public_key& asset_id /* = currency::native_coin_asset_id */) +{ + std::vector extra; + std::vector attachments; + transaction result_tx = AUTO_VAL_INIT(result_tx); + + std::vector dst; + dst.resize(1); + dst.back().addr.push_back(acc); + dst.back().amount = amount; + dst.back().asset_id = asset_id; + this->transfer(dst, fake_outs_count, 0, fee, extra, attachments, get_current_split_strategy(), tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD), result_tx); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::transfer(uint64_t amount, const currency::account_public_address& acc, const crypto::public_key& asset_id /* = currency::native_coin_asset_id */) +{ + transaction result_tx = AUTO_VAL_INIT(result_tx); + this->transfer(amount, acc, result_tx, asset_id); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::reset_creation_time(uint64_t timestamp) +{ + m_account.set_createtime(timestamp); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::update_current_tx_limit() +{ + currency::COMMAND_RPC_GET_INFO::request req = AUTO_VAL_INIT(req); + currency::COMMAND_RPC_GET_INFO::response res = AUTO_VAL_INIT(res); + bool r = m_core_proxy->call_COMMAND_RPC_GET_INFO(req, res); + THROW_IF_TRUE_WALLET_EX(!r, error::no_connection_to_daemon, "getinfo"); + THROW_IF_TRUE_WALLET_EX(res.status == API_RETURN_CODE_BUSY, error::daemon_busy, "getinfo"); + THROW_IF_TRUE_WALLET_EX(res.status != API_RETURN_CODE_OK, error::get_blocks_error, res.status); + THROW_IF_TRUE_WALLET_EX(res.current_blocks_median < CURRENCY_BLOCK_GRANTED_FULL_REWARD_ZONE, error::get_blocks_error, "bad median size"); + m_upper_transaction_size_limit = res.current_blocks_median - CURRENCY_COINBASE_BLOB_RESERVED_SIZE; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::has_related_alias_entry_unconfirmed(const currency::transaction& tx) +{ + std::string local_adr = m_account.get_public_address_str(); + tx_extra_info tei = AUTO_VAL_INIT(tei); + parse_and_validate_tx_extra(tx, tei); + if (tei.m_alias.m_alias.size()) + { + //have some check address involved + if (tei.m_alias.m_address.spend_public_key == m_account.get_keys().account_address.spend_public_key && + tei.m_alias.m_address.view_public_key == m_account.get_keys().account_address.view_public_key) + return true; + + //check if it's update and address before was our address + currency::COMMAND_RPC_GET_ALIAS_DETAILS::request req = AUTO_VAL_INIT(req); + currency::COMMAND_RPC_GET_ALIAS_DETAILS::response res = AUTO_VAL_INIT(res); + req.alias = tei.m_alias.m_alias; + m_core_proxy->call_COMMAND_RPC_GET_ALIAS_DETAILS(req, res); + if (res.status != API_RETURN_CODE_OK) return false; + if (local_adr == res.alias_details.address) + return true; - [[maybe_unused]] uint64_t bal = 0; - if (!m_has_bare_unspent_outputs.has_value()) - bal = balance(); - - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(m_has_bare_unspent_outputs.has_value(), "m_has_bare_unspent_outputs has no value after balance()"); - - return m_has_bare_unspent_outputs.value(); } - //---------------------------------------------------------------------------------------------------- -#define MAX_INPUTS_FOR_SIMPLE_TX_EURISTIC 20 - bool wallet2::get_bare_unspent_outputs_stats(std::vector& tids_grouped_by_txs) const - { - tids_grouped_by_txs.clear(); + return false; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::has_bare_unspent_outputs() const +{ + if (m_account.get_createtime() > ZANO_HARDFORK_04_TIMESTAMP_ACTUAL) + return false; - // 1/3. Populate a list of bare unspent outputs - std::unordered_map> buo_ids; // tx hash -> Bare Unspent Outs list + [[maybe_unused]] uint64_t bal = 0; + if (!m_has_bare_unspent_outputs.has_value()) + bal = balance(); + + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(m_has_bare_unspent_outputs.has_value(), "m_has_bare_unspent_outputs has no value after balance()"); + + return m_has_bare_unspent_outputs.value(); +} +//---------------------------------------------------------------------------------------------------- +#define MAX_INPUTS_FOR_SIMPLE_TX_EURISTIC 20 +bool wallet2::get_bare_unspent_outputs_stats(std::vector& tids_grouped_by_txs) const +{ + tids_grouped_by_txs.clear(); + + // 1/3. Populate a list of bare unspent outputs + std::unordered_map> buo_ids; // tx hash -> Bare Unspent Outs list + for (const auto& tr : m_transfers) + { + uint64_t tid = tr.first; + const auto& td = m_transfers.at(tid); + if (!td.is_zc() && td.is_spendable()) + { + buo_ids[td.tx_hash()].push_back(tid); + } + } + + if (buo_ids.empty()) + return true; + + // 2/3. Split them into groups + tids_grouped_by_txs.emplace_back(); + for (auto& buo_el : buo_ids) + { + if (tids_grouped_by_txs.back().tids.size() + buo_el.second.size() > MAX_INPUTS_FOR_SIMPLE_TX_EURISTIC) + tids_grouped_by_txs.emplace_back(); + + for (auto& tid : buo_el.second) + { + if (tids_grouped_by_txs.back().tids.size() >= MAX_INPUTS_FOR_SIMPLE_TX_EURISTIC) + tids_grouped_by_txs.emplace_back(); + tids_grouped_by_txs.back().tids.push_back((uint64_t)tid); + tids_grouped_by_txs.back().total_amount += m_transfers.at(tid).m_amount; + } + } + + + // 3/3. Iterate through groups and check whether total amount is big enough to cover min fee. + // Add additional zc output if not. + std::multimap usable_zc_outs_tids; // grouped by amount + bool usable_zc_outs_tids_precalculated = false; + auto precalculate_usable_zc_outs_if_needed = [&]() { + if (usable_zc_outs_tids_precalculated) + return; + size_t decoys = is_auditable() ? 0 : m_core_runtime_config.hf4_minimum_mixins; for (const auto& tr : m_transfers) { uint64_t tid = tr.first; - const auto& td = m_transfers.at(tid); - if (!td.is_zc() && td.is_spendable()) - { - buo_ids[td.tx_hash()].push_back(tid); - } + auto& td = m_transfers.at(tid); + if (td.is_zc() && td.is_native_coin() && is_transfer_ready_to_go(td, decoys)) + usable_zc_outs_tids.insert(std::make_pair(td.m_amount, tid)); } + usable_zc_outs_tids_precalculated = true; + }; - if (buo_ids.empty()) - return true; - - // 2/3. Split them into groups - tids_grouped_by_txs.emplace_back(); - for (auto& buo_el : buo_ids) - { - if (tids_grouped_by_txs.back().tids.size() + buo_el.second.size() > MAX_INPUTS_FOR_SIMPLE_TX_EURISTIC) - tids_grouped_by_txs.emplace_back(); - - for (auto& tid : buo_el.second) - { - if (tids_grouped_by_txs.back().tids.size() >= MAX_INPUTS_FOR_SIMPLE_TX_EURISTIC) - tids_grouped_by_txs.emplace_back(); - tids_grouped_by_txs.back().tids.push_back((uint64_t)tid); - tids_grouped_by_txs.back().total_amount += m_transfers.at(tid).m_amount; - } - } - - - // 3/3. Iterate through groups and check whether total amount is big enough to cover min fee. - // Add additional zc output if not. - std::multimap usable_zc_outs_tids; // grouped by amount - bool usable_zc_outs_tids_precalculated = false; - auto precalculate_usable_zc_outs_if_needed = [&]() { - if (usable_zc_outs_tids_precalculated) - return; - size_t decoys = is_auditable() ? 0 : m_core_runtime_config.hf4_minimum_mixins; - for (const auto& tr : m_transfers) - { - uint64_t tid = tr.first; - auto& td = m_transfers.at(tid); - if (td.is_zc() && td.is_native_coin() && is_transfer_ready_to_go(td, decoys)) - usable_zc_outs_tids.insert(std::make_pair(td.m_amount, tid)); - } - usable_zc_outs_tids_precalculated = true; - }; - - std::unordered_set used_zc_outs; - for (auto it = tids_grouped_by_txs.begin(); it != tids_grouped_by_txs.end(); ) - { - auto& group = *it; - if (group.total_amount < TX_MINIMUM_FEE) - { - precalculate_usable_zc_outs_if_needed(); - - uint64_t min_required_amount = TX_MINIMUM_FEE - group.total_amount; - auto jt = usable_zc_outs_tids.lower_bound(min_required_amount); - bool found = false; - while (jt != usable_zc_outs_tids.end()) - { - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(jt->first >= min_required_amount, "jt->first=" << jt->first << ", min_required_amount=" << min_required_amount); - if (used_zc_outs.count(jt->second) == 0) - { - group.tids.push_back((uint64_t)jt->second); - used_zc_outs.insert(jt->second); - group.additional_tid = true; - group.additional_tid_amount = jt->first; - found = true; - break; - } - ++jt; - } - - if (!found) - { - // no usable outs for required amount, remove this group and go to the next - it = tids_grouped_by_txs.erase(it); - continue; - } - } - - ++it; - } - - return true; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::sweep_bare_unspent_outputs(const currency::account_public_address& target_address, const std::vector& tids_grouped_by_txs, - std::function on_tx_sent) + std::unordered_set used_zc_outs; + for (auto it = tids_grouped_by_txs.begin(); it != tids_grouped_by_txs.end(); ) { - if (m_watch_only) - return false; - - size_t decoys_count = is_auditable() ? 0 : CURRENCY_DEFAULT_DECOY_SET_SIZE; - - bool send_to_network = true; - - size_t batch_index = 0; - for (const batch_of_bare_unspent_outs& group : tids_grouped_by_txs) + auto& group = *it; + if (group.total_amount < TX_MINIMUM_FEE) { - currency::finalized_tx ftx{}; - currency::finalize_tx_param ftp{}; - ftp.pevents_dispatcher = &m_debug_events_dispatcher; - ftp.tx_version = this->get_current_tx_version(); + precalculate_usable_zc_outs_if_needed(); - if (!prepare_tx_sources(decoys_count, /*use_all_decoys_if_found_less_than_required*/ true, ftp.sources, group.tids)) + uint64_t min_required_amount = TX_MINIMUM_FEE - group.total_amount; + auto jt = usable_zc_outs_tids.lower_bound(min_required_amount); + bool found = false; + while (jt != usable_zc_outs_tids.end()) { - on_tx_sent(batch_index, transaction{}, 0, 0, false, "sources for tx couldn't be prepared"); - LOG_PRINT_L0("prepare_tx_sources failed, batch_index = " << batch_index); - return false; - } - uint64_t fee = TX_DEFAULT_FEE; - std::vector destinations{ tx_destination_entry(group.total_amount + group.additional_tid_amount - fee, target_address) }; - assets_selection_context needed_money_map{ std::make_pair(native_coin_asset_id, selection_for_amount{group.total_amount + group.additional_tid_amount, group.total_amount + group.additional_tid_amount}) }; - try - { - prepare_tx_destinations(needed_money_map, get_current_split_strategy(), tx_dust_policy{}, destinations, 0 /* tx_flags */, ftp.prepared_destinations); - } - catch (...) - { - on_tx_sent(batch_index, transaction{}, 0, 0, false, "destinations for tx couldn't be prepared"); - LOG_PRINT_L0("prepare_tx_destinations failed, batch_index = " << batch_index); - return false; - } - - mark_transfers_as_spent(ftp.selected_transfers, std::string("sweep bare UTXO, tx: ") + epee::string_tools::pod_to_hex(get_transaction_hash(ftx.tx))); - try - { - finalize_transaction(ftp, ftx, send_to_network); - on_tx_sent(batch_index, ftx.tx, group.total_amount + group.additional_tid_amount, fee, true, std::string()); - } - catch (std::exception& e) - { - clear_transfers_from_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, std::string("exception on sweep bare UTXO, tx: ") + epee::string_tools::pod_to_hex(get_transaction_hash(ftx.tx))); - on_tx_sent(batch_index, transaction{}, 0, 0, false, e.what()); - return false; - } - - ++batch_index; - } - - return true; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::sweep_bare_unspent_outputs(const currency::account_public_address& target_address, const std::vector& tids_grouped_by_txs, - size_t& total_txs_sent, uint64_t& total_amount_sent, uint64_t& total_fee_spent, uint64_t& total_bare_outs_sent) - { - total_txs_sent = 0; - total_amount_sent = 0; - total_fee_spent = 0; - total_bare_outs_sent = 0; - auto on_tx_sent_callback = [&](size_t batch_index, const currency::transaction& tx, uint64_t amount, uint64_t fee, bool sent_ok, const std::string& err) { - if (sent_ok) - { - total_bare_outs_sent += count_type_in_variant_container(tx.vin); - ++total_txs_sent; - total_fee_spent += fee; - total_amount_sent += amount; - } - }; - - return sweep_bare_unspent_outputs(target_address, tids_grouped_by_txs, on_tx_sent_callback); - } - //---------------------------------------------------------------------------------------------------- - uint64_t wallet2::get_directly_spent_transfer_index_by_input_in_tracking_wallet(const currency::txin_to_key& intk) - { - return get_directly_spent_transfer_index_by_input_in_tracking_wallet(intk.amount, intk.key_offsets); - } - //---------------------------------------------------------------------------------------------------- - uint64_t wallet2::get_directly_spent_transfer_index_by_input_in_tracking_wallet(const currency::txin_zc_input& inzc) - { - return get_directly_spent_transfer_index_by_input_in_tracking_wallet(0, inzc.key_offsets); - } - //---------------------------------------------------------------------------------------------------- - uint64_t wallet2::get_directly_spent_transfer_index_by_input_in_tracking_wallet(uint64_t amount, const std::vector& key_offsets) - { - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(is_auditable() && is_watch_only(), "this is not an auditable-watch-only (tracking) wallet"); - - uint64_t tid = UINT64_MAX; - - // try to find a reference among own UTXOs - std::vector abs_key_offsets = relative_output_offsets_to_absolute(key_offsets); // potential speed-up: don't convert to abs offsets as we interested only in direct spends for auditable wallets. Now it's kind a bit paranoid. - for (auto v : abs_key_offsets) - { - if (v.type() != typeid(uint64_t)) - continue; - uint64_t gindex = boost::get(v); - auto it = m_amount_gindex_to_transfer_id.find(std::make_pair(amount, gindex)); - if (it != m_amount_gindex_to_transfer_id.end()) - { - tid = it->second; - //WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(tid < m_transfers.size(), "invalid tid: " << tid << ", ref from input with amount: " << amount << ", gindex: " << gindex); - auto& td = m_transfers.at(it->second); - if (key_offsets.size() != 1) + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(jt->first >= min_required_amount, "jt->first=" << jt->first << ", min_required_amount=" << min_required_amount); + if (used_zc_outs.count(jt->second) == 0) { - // own output was used in non-direct transaction - // the core should not allow this to happen, the only way it may happen - mixing in own output that was sent without mix_attr == 1 - // log strange situation - std::stringstream ss; - ss << "own transfer tid=" << tid << " tx=" << td.tx_hash() << " mix_attr=" << td.mix_attr() << ", is referenced by a transaction with mixins, ref from input with amount: " << amount << ", gindex: " << gindex; - WLT_LOG_YELLOW(ss.str(), LOG_LEVEL_0); - if (m_wcallback) - m_wcallback->on_message(i_wallet2_callback::ms_yellow, ss.str()); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.mix_attr() != CURRENCY_TO_KEY_OUT_FORCED_NO_MIX, ss.str()); // if mix_attr == 1 this should never happen (mixing in an output with mix_attr = 1) as the core must reject such txs - // our own output has mix_attr != 1 for some reason (a sender did not set correct mix_attr e.g.) - // but mixin count > 1 so we can't say it is spent for sure - tid = UINT64_MAX; - continue; - } - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.m_spent_height == 0, "transfer is spent in blockchain, tid: " << tid << ", ref from input with amount: " << amount << ", gindex: " << gindex); - // okay, own output is being spent, return it - break; - } - } - return tid; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::handle_unconfirmed_tx(process_transaction_context& ptc) - { - const transaction& tx = ptc.tx; - ptc.timestamp = m_core_runtime_config.get_core_time(); - // read extra - std::vector outs; - //uint64_t sum_of_received_native_outs = 0; - crypto::public_key tx_pub_key = null_pkey; - bool r = parse_and_validate_tx_extra(tx, tx_pub_key); - THROW_IF_TRUE_WALLET_EX(!r, error::tx_extra_parse_error, tx); - //check if we have money - crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); - r = lookup_acc_outs(m_account.get_keys(), tx, tx_pub_key, outs, derivation); - THROW_IF_TRUE_WALLET_EX(!r, error::acc_outs_lookup_error, tx, tx_pub_key, m_account.get_keys()); - - //collect incomes - for (auto& o : outs) - { - if (out_is_multisig(tx.vout[o.index])) - continue; - - ptc.total_balance_change[o.asset_id] += o.amount; - ptc.employed_entries.receive.push_back(wallet_public::employed_tx_entry{ o.index, o.amount, o.asset_id }); - } - - - bool new_multisig_spend_detected = false; - //check if we have spendings - //uint64_t sum_of_spent_native_coin = 0; - std::list spend_transfers; - // check all outputs for spending (compare key images) - for (size_t i = 0; i != tx.vin.size(); i++) - { - auto& in = tx.vin[i]; - if (in.type() == typeid(currency::txin_to_key)) - { - const currency::txin_to_key& intk = boost::get(in); - uint64_t tid = UINT64_MAX; - if (is_auditable() && is_watch_only()) - { - // tracking wallet, assuming all outputs are spent directly because of mix_attr = 1 - tid = get_directly_spent_transfer_index_by_input_in_tracking_wallet(intk.amount, intk.key_offsets); - } - else - { - // wallet with spend secret key -- we can calculate own key images and then search among them - auto it = m_key_images.find(intk.k_image); - if (it != m_key_images.end()) - { - tid = it->second; - } - } - - if (tid != UINT64_MAX) - { - // own output is being spent by this input - //sum_of_spent_native_coin += intk.amount; - ptc.employed_entries.spent.push_back(wallet_public::employed_tx_entry{ i, m_transfers.at(tid).amount(), m_transfers.at(tid).get_asset_id() }); - spend_transfers.push_back(tid); - ptc.total_balance_change[currency::native_coin_asset_id] -= m_transfers.at(tid).amount(); - CHECK_AND_ASSERT_THROW_MES(m_transfers.at(tid).get_asset_id() == currency::native_coin_asset_id, "Unexpected asset id for native txin_to_key"); - } - } - else if (in.type() == typeid(currency::txin_zc_input)) - { - // bad design -- remove redundancy like using wallet2::process_input_t() - const currency::txin_zc_input& zc = boost::get(in); - uint64_t tid = UINT64_MAX; - if (is_auditable() && is_watch_only()) - { - // tracking wallet, assuming all outputs are spent directly because of mix_attr = 1 - tid = get_directly_spent_transfer_index_by_input_in_tracking_wallet(zc); - } - else - { - // wallet with spend secret key -- we can calculate own key images and then search among them - auto it = m_key_images.find(zc.k_image); - if (it != m_key_images.end()) - { - tid = it->second; - } - } - - if (tid != UINT64_MAX) - { - ptc.employed_entries.spent.push_back(wallet_public::employed_tx_entry{ i, m_transfers.at(tid).amount(), m_transfers.at(tid).get_asset_id() }); - spend_transfers.push_back(tid); - ptc.total_balance_change[m_transfers.at(tid).get_asset_id()] -= m_transfers.at(tid).amount(); - } - } - else if (in.type() == typeid(currency::txin_multisig)) - { - crypto::hash multisig_id = boost::get(in).multisig_out_id; - auto it = m_multisig_transfers.find(multisig_id); - if (it != m_multisig_transfers.end()) - { - //ptc.employed_entries.spent_indices.push_back(i); - ptc.employed_entries.spent.push_back(wallet_public::employed_tx_entry{ i }); - if (ptc.pmultisig_entries) - { - r = ptc.pmultisig_entries->insert(std::make_pair(multisig_id, std::make_pair(tx, ptc.employed_entries))).second; - if (!r) - { - WLT_LOG_RED("Warning: Receiving the same multisig out id from tx pool more then once: " << multisig_id, LOG_LEVEL_0); - } - } - if (m_unconfirmed_multisig_transfers.count(multisig_id) == 0) - { - // new unconfirmed multisig (see also comments on multisig tranafers handling below) - uint32_t flags_before = it->second.m_flags; - it->second.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; // mark as spent - it->second.m_spent_height = 0; // height 0 means unconfirmed - WLT_LOG_L0("From tx pool got new unconfirmed multisig out with id: " << multisig_id << " tx: " << get_transaction_hash(tx) << " Marked as SPENT, flags: " << flags_before << " -> " << it->second.m_flags); - new_multisig_spend_detected = true; - } - - } - } - } - - //do final calculations - bool has_in_transfers = false; - bool has_out_transfers = false; - for (const auto& bce : ptc.total_balance_change) - { - if (bce.second > 0) - { - has_in_transfers = true; - } - else if (bce.second < 0) - { - has_out_transfers = true; - } - } - if (!is_tx_expired(tx, ptc.tx_expiration_ts_median) && (new_multisig_spend_detected || has_in_transfers || has_out_transfers || (currency::is_derivation_used_to_encrypt(tx, derivation)))) - { - m_unconfirmed_in_transfers[ptc.tx_hash()] = tx; - if (m_unconfirmed_txs.count(ptc.tx_hash())) - return; - - //prepare notification about pending transaction - wallet_public::wallet_transfer_info& unconfirmed_wti = misc_utils::get_or_insert_value_initialized(m_unconfirmed_txs, ptc.tx_hash()); - prepare_wti(unconfirmed_wti, ptc); - for (auto tr_index : spend_transfers) - { - //if (tr_index > m_transfers.size()) - //{ - // WLT_LOG_ERROR("INTERNAL ERROR: tr_index " << tr_index << " more then m_transfers.size()=" << m_transfers.size()); - // continue; - //} - uint32_t flags_before = m_transfers.at(tr_index).m_flags; - m_transfers.at(tr_index).m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; - WLT_LOG_L1("wallet transfer #" << tr_index << " is marked as spent, flags: " << flags_before << " -> " << m_transfers.at(tr_index).m_flags << ", reason: UNCONFIRMED tx: " << ptc.tx_hash()); - unconfirmed_wti.selected_indicies.push_back(tr_index); - } - rise_on_transfer2(unconfirmed_wti); - } - } - - - void wallet2::scan_tx_pool(bool& has_related_alias_in_unconfirmed) - { - //get transaction pool content - currency::COMMAND_RPC_GET_TX_POOL::request req = AUTO_VAL_INIT(req); - currency::COMMAND_RPC_GET_TX_POOL::response res = AUTO_VAL_INIT(res); - bool r = m_core_proxy->call_COMMAND_RPC_GET_TX_POOL(req, res); - if (res.status == API_RETURN_CODE_BUSY) - throw error::daemon_busy(LOCATION_STR, "get_tx_pool"); - if (!r) - throw error::no_connection_to_daemon(LOCATION_STR, "get_tx_pool"); - THROW_IF_TRUE_WALLET_EX(res.status != API_RETURN_CODE_OK, error::get_blocks_error, res.status); - - - //- @#@ ----- debug -#ifdef _DEBUG - std::stringstream ss; - ss << "TXS FROM POOL: " << ENDL; - for (const auto& tx_blob : res.txs) - { - currency::transaction tx; - bool r = parse_and_validate_tx_from_blob(tx_blob, tx); - THROW_IF_TRUE_WALLET_EX(!r, error::tx_parse_error, tx_blob); - crypto::hash tx_hash = currency::get_transaction_hash(tx); - - ss << tx_hash << ENDL; - } - ss << "UNCONFIRMED TXS: " << ENDL; - for (const auto& tx_it : m_unconfirmed_in_transfers) - { - ss << tx_it.first << ENDL; - } - std::string config_tx = ss.str(); -#endif - //- @#@ ----- debug - - std::unordered_map unconfirmed_in_transfers_local(std::move(m_unconfirmed_in_transfers)); - multisig_entries_map unconfirmed_multisig_transfers_from_tx_pool; - - has_related_alias_in_unconfirmed = false; - uint64_t tx_expiration_ts_median = res.tx_expiration_ts_median; //get_tx_expiration_median(); - for (const auto& tx_blob : res.txs) - { - currency::transaction tx; - //money_transfer2_details td; - process_transaction_context ptc(tx); - ptc.tx_expiration_ts_median = tx_expiration_ts_median; - ptc.pmultisig_entries = &unconfirmed_multisig_transfers_from_tx_pool; - bool r = parse_and_validate_tx_from_blob(tx_blob, tx); - THROW_IF_TRUE_WALLET_EX(!r, error::tx_parse_error, tx_blob); - has_related_alias_in_unconfirmed |= has_related_alias_entry_unconfirmed(tx); - - //crypto::hash tx_hash = currency::get_transaction_hash(tx); - auto it = unconfirmed_in_transfers_local.find(ptc.tx_hash()); - if (it != unconfirmed_in_transfers_local.end()) - { - m_unconfirmed_in_transfers.insert(*it); - continue; - } - - handle_unconfirmed_tx(ptc); - } - - // Compare unconfirmed multisigs containers - // IF EXISTS IN unconfirmed_multisig_transfers_in_tx_pool AND EXISTS IN m_unconfirmed_multisig_transfers => already processed, do nothing - // IF EXISTS IN unconfirmed_multisig_transfers_in_tx_pool AND NOT EXISTS IN m_unconfirmed_multisig_transfers => new unconfirmed, add to m_, mark as spent and nofity (see code above) - // IF NOT EXISTS IN unconfirmed_multisig_transfers_in_tx_pool AND EXISTS IN m_unconfirmed_multisig_transfers => EITHER became confirmed (added to the blockchain) OR removed from the pool for some other reason (clear spent flag if there's spent height == 0, means wasn't added to the blockchain) - - std::unordered_set unconfirmed_in_multisig_transfers; - for (auto& el : m_unconfirmed_in_transfers) - for (auto& in : el.second.vin) - if (in.type() == typeid(txin_multisig)) - unconfirmed_in_multisig_transfers.insert(boost::get(in).multisig_out_id); - - for (auto& multisig_id : m_unconfirmed_multisig_transfers) - { - if (unconfirmed_multisig_transfers_from_tx_pool.count(multisig_id) != 0) - continue; - - if (unconfirmed_in_multisig_transfers.count(multisig_id) != 0) - continue; - - // Process unconfirmed tx dissapeared from the pool - auto it = m_multisig_transfers.find(multisig_id); - if (it != m_multisig_transfers.end() && it->second.m_flags & WALLET_TRANSFER_DETAIL_FLAG_SPENT) - { - if (it->second.m_spent_height == 0) - { - // Looks like this tx didn't get into the blockchain, just removed from the pool for some reason. - // So, clear spent flag. - uint32_t flags_before = it->second.m_flags; - it->second.m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_SPENT); - WLT_LOG_L0("Unconfirmed multisig out with id: " << multisig_id << " was presiously marked as spent and now seems to be removed from the pool, while still not added to the blockchain. Marked as NOT SPENT" << ENDL - << "ms source tx: " << (it->second.m_ptx_wallet_info != nullptr ? get_transaction_hash(it->second.m_ptx_wallet_info->m_tx) : null_hash) << " flags: " << flags_before << " -> " << it->second.m_flags); - } - } - } - - // Populate updated unconfirmed list of multisign transfers - m_unconfirmed_multisig_transfers.clear(); - for (auto& p : unconfirmed_multisig_transfers_from_tx_pool) - m_unconfirmed_multisig_transfers.insert(p.first); - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::on_idle() - { - scan_not_compliant_unconfirmed_txs(); - return true; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::scan_not_compliant_unconfirmed_txs() - { - uint64_t tx_expiration_ts_median = get_tx_expiration_median(); - uint64_t time_limit = m_core_runtime_config.get_core_time() - CURRENCY_MEMPOOL_TX_LIVETIME; - for (auto it = m_unconfirmed_txs.begin(); it != m_unconfirmed_txs.end(); ) - { - bool remove_this_tx = false; - std::stringstream reason_ss; - if (it->second.timestamp < time_limit) - { - remove_this_tx = true; - reason_ss << "outdated, "; - } - if (is_tx_expired(it->second.tx, tx_expiration_ts_median)) - { - remove_this_tx = true; - reason_ss << "expired, "; - } - if (is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM) && it->second.tx.version < TRANSACTION_VERSION_POST_HF4) - { - remove_this_tx = true; - reason_ss << "not compliant with HF4, "; - } - - if (remove_this_tx) - { - WLT_LOG_BLUE("removing unconfirmed tx " << it->second.tx_hash << ", reason: " << reason_ss.str() << "tx_expiration_ts_median=" << tx_expiration_ts_median, LOG_LEVEL_0); - //lookup all used transfer and update flags - for (auto i : it->second.selected_indicies) - { - //if (i >= m_transfers.size()) - //{ - // WLT_LOG_ERROR("Wrong index '" << i << "' in 'selected_indicies', while m_transfers.size() = " << m_transfers.size()); - // continue; - //} - if (!m_transfers.at(i).m_spent_height) - { - uint32_t flags_before = m_transfers.at(i).m_flags; - m_transfers.at(i).m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_SPENT); // TODO: consider removing other blocking flags (e.g. for escrow tx) -- sowle - WLT_LOG_BLUE("mark transfer #" << i << " as unspent, flags: " << flags_before << " -> " << m_transfers.at(i).m_flags << ", reason: removing unconfirmed tx " << it->second.tx_hash, LOG_LEVEL_0); - } - } - //fire some event - m_wcallback->on_transfer_canceled(it->second); - m_unconfirmed_txs.erase(it++); - } - else - { - it++; - } - } - - //scan marked as spent but don't have height (unconfirmed, and actually not unconfirmed) - std::unordered_set ki_in_unconfirmed; - for (auto it = m_unconfirmed_txs.begin(); it != m_unconfirmed_txs.end(); it++) - { - if (!it->second.has_outgoing_entries()) - continue; - - for (auto& in_v : it->second.tx.vin) - { - crypto::key_image ki{}; - if (get_key_image_from_txin_v(in_v, ki)) - ki_in_unconfirmed.insert(ki); - } - } - - - for (auto& tr : m_transfers) - { - uint64_t i = tr.first; - auto& t = tr.second; - - if (t.m_flags & WALLET_TRANSFER_DETAIL_FLAG_SPENT && !t.m_spent_height && !static_cast(t.m_flags & WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION) - && !t.is_htlc()) - { - //check if there is unconfirmed for this transfer is no longer exist? - if (!ki_in_unconfirmed.count((t.m_key_image))) - { - uint32_t flags_before = t.m_flags; - t.m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_SPENT); - WLT_LOG_BLUE("Transfer [" << i << "] marked as unspent, flags: " << flags_before << " -> " << t.m_flags << ", reason: there is no unconfirmed tx relataed to this key image", LOG_LEVEL_0); - } - } - } - - return true; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::refresh(size_t& blocks_fetched, bool& received_money, std::atomic& stop) - { - load_whitelisted_tokens_if_not_loaded(); - - bool had_full_reset = false; - received_money = false; - blocks_fetched = 0; - size_t added_blocks = 0; - size_t try_count = 0; - size_t reset_count = 0; - crypto::hash last_tx_hash_id = m_transfers.size() ? get_transaction_hash((--m_transfers.end())->second.m_ptx_wallet_info->m_tx) : null_hash; - m_height_of_start_sync = get_blockchain_current_size(); - m_last_sync_percent = 0; - while (!stop.load(std::memory_order_relaxed)) - { - try - { - bool full_reset_needed = false; - pull_blocks(added_blocks, stop, full_reset_needed); - if (full_reset_needed) - { - if (reset_count > 1) - { - WLT_LOG_L0("Intenral error: reset_count infinit loop catch"); - if (m_wcallback) - m_wcallback->on_message(tools::i_wallet2_callback::ms_red, "Internal error: reset_count infinite loop catch"); - return; - } - reset_count++; - m_height_of_start_sync = 0; - had_full_reset = true; - continue; - } - blocks_fetched += added_blocks; - if (!added_blocks) + group.tids.push_back((uint64_t)jt->second); + used_zc_outs.insert(jt->second); + group.additional_tid = true; + group.additional_tid_amount = jt->first; + found = true; break; - } - catch (error::no_connection_to_daemon&) - { - blocks_fetched += added_blocks; - if (++try_count > 3) - return; - WLT_LOG_L2("no connection to the daemon, wait and try pull_blocks again (try_count: " << try_count << ", blocks_fetched: " << blocks_fetched << ")"); - if (m_wcallback) - m_wcallback->on_message(tools::i_wallet2_callback::ms_red, "no connection to daemon"); - std::this_thread::sleep_for(std::chrono::seconds(3)); - } - catch (const std::exception& e) - { - blocks_fetched += added_blocks; - WLT_LOG_ERROR("refresh->pull_blocks failed, try_count: " << try_count << ", blocks_fetched: " << blocks_fetched << ", exception: " << e.what()); - if (m_wcallback) - m_wcallback->on_message(tools::i_wallet2_callback::ms_red, std::string("error on pulling blocks: ") + e.what()); - return; - } - } - - if (last_tx_hash_id != (m_transfers.size() ? get_transaction_hash((--m_transfers.end())->second.m_ptx_wallet_info->m_tx) : null_hash)) - received_money = true; - - if (blocks_fetched) - { - on_idle(); - - uint64_t tx_expiration_ts_median = get_tx_expiration_median(); - handle_expiration_list(tx_expiration_ts_median); - handle_contract_expirations(tx_expiration_ts_median); - m_found_free_amounts.clear(); - truncate_wallet(); - } - if (had_full_reset) - { - blocks_fetched = get_blockchain_current_size() - m_full_resync_requested_at_h; - m_full_resync_requested_at_h = 0; - } - - - WLT_LOG("Refresh done, blocks received: " << blocks_fetched, blocks_fetched > 0 ? LOG_LEVEL_1 : LOG_LEVEL_2); - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::handle_expiration_list(uint64_t tx_expiration_ts_median) - { - for (auto it = m_money_expirations.begin(); it != m_money_expirations.end(); ) - { - if (it->expiration_time < TX_EXPIRATION_MEDIAN_SHIFT || tx_expiration_ts_median > it->expiration_time - TX_EXPIRATION_MEDIAN_SHIFT) - { - for (auto tr_ind : it->selected_transfers) - { - auto& transfer = m_transfers.at(tr_ind); - if (!transfer.m_spent_height) - { - // Clear WALLET_TRANSFER_DETAIL_FLAG_BLOCKED and WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION flags only. - // Note: transfer may still be marked as spent - uint32_t flags_before = transfer.m_flags; - transfer.m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_BLOCKED); - transfer.m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION); - WLT_LOG_GREEN("Unlocked money from expiration_list: transfer #" << tr_ind << ", flags: " << flags_before << " -> " << transfer.m_flags << ", amount: " << print_money(transfer.amount()) << ", tx: " << - (transfer.m_ptx_wallet_info != nullptr ? get_transaction_hash(transfer.m_ptx_wallet_info->m_tx) : null_hash), LOG_LEVEL_0); - } - } - WLT_LOG_GREEN("expiration_list entry removed by median: " << tx_expiration_ts_median << ", expiration time: " << it->expiration_time << ", related tx: " << it->related_tx_id, LOG_LEVEL_0); - it = m_money_expirations.erase(it); + ++jt; } - else + + if (!found) { - it++; + // no usable outs for required amount, remove this group and go to the next + it = tids_grouped_by_txs.erase(it); + continue; } } - return true; + + ++it; } - //---------------------------------------------------------------------------------------------------- - void wallet2::handle_contract_expirations(uint64_t tx_expiration_ts_median) + + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::sweep_bare_unspent_outputs(const currency::account_public_address& target_address, const std::vector& tids_grouped_by_txs, + std::function on_tx_sent) +{ + if (m_watch_only) + return false; + + size_t decoys_count = is_auditable() ? 0 : CURRENCY_DEFAULT_DECOY_SET_SIZE; + + bool send_to_network = true; + + size_t batch_index = 0; + for (const batch_of_bare_unspent_outs& group : tids_grouped_by_txs) { - for (auto& contract : m_contracts) + currency::finalized_tx ftx{}; + currency::finalize_tx_param ftp{}; + ftp.pevents_dispatcher = &m_debug_events_dispatcher; + ftp.tx_version = this->get_current_tx_version(); + + if (!prepare_tx_sources(decoys_count, /*use_all_decoys_if_found_less_than_required*/ true, ftp.sources, group.tids)) { - switch (contract.second.state) - { - case tools::wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent: - if (is_tx_expired(contract.second.cancel_body.tx_cancel_template, tx_expiration_ts_median)) - change_contract_state(contract.second, tools::wallet_public::escrow_contract_details_basic::contract_accepted, contract.first, "cancel proposal expiration"); - break; - case tools::wallet_public::escrow_contract_details_basic::contract_released_cancelled: - if (contract.second.height == 0 && is_tx_expired(contract.second.cancel_body.tx_cancel_template, tx_expiration_ts_median)) - change_contract_state(contract.second, tools::wallet_public::escrow_contract_details_basic::contract_accepted, contract.first, "cancel acceptance expiration"); - break; - } + on_tx_sent(batch_index, transaction{}, 0, 0, false, "sources for tx couldn't be prepared"); + LOG_PRINT_L0("prepare_tx_sources failed, batch_index = " << batch_index); + return false; } - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::refresh(size_t& blocks_fetched, bool& received_money, bool& ok, std::atomic& stop) - { + uint64_t fee = TX_DEFAULT_FEE; + std::vector destinations{ tx_destination_entry(group.total_amount + group.additional_tid_amount - fee, target_address) }; + assets_selection_context needed_money_map{ std::make_pair(native_coin_asset_id, selection_for_amount{group.total_amount + group.additional_tid_amount, group.total_amount + group.additional_tid_amount}) }; try { - refresh(blocks_fetched, received_money, stop); - ok = true; + prepare_tx_destinations(needed_money_map, get_current_split_strategy(), tx_dust_policy{}, destinations, 0 /* tx_flags */, ftp.prepared_destinations); } catch (...) { - ok = false; - } - return ok; - } - - //---------------------------------------------------------------------------------------------------- - uint64_t wallet2::detach_from_block_ids(uint64_t including_height) - { - //calculate number of erased blocks - uint64_t blocks_detached = get_blockchain_current_size() - including_height; - //id at height should be kept, the rest - erased - m_chain.detach(including_height); - return blocks_detached; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::remove_transfer_from_amount_gindex_map(uint64_t tid) - { - for (auto it = m_amount_gindex_to_transfer_id.begin(); it != m_amount_gindex_to_transfer_id.end(); ) - { - if (it->second == tid) - it = m_amount_gindex_to_transfer_id.erase(it); - else - ++it; - } - } - //---------------------------------------------------------------------------------------------------- - void wallet2::detach_blockchain(uint64_t including_height) - { - WLT_LOG_L0("Detaching blockchain on height " << including_height); - size_t transfers_detached = 0; - - // rollback incoming transfers from detaching subchain - { - auto it_start = std::find_if(m_transfers.begin(), m_transfers.end(), [&](const transfer_container::value_type& tr_e){return tr_e.second.m_ptx_wallet_info->m_block_height >= including_height; }); - if (it_start != m_transfers.end()) - { - - for (auto it = it_start; it!= m_transfers.end(); it++) - { - uint64_t i = it->first; - //check for htlc - if (it->second.m_ptx_wallet_info->m_tx.vout[it->second.m_internal_output_index].type() == typeid(tx_out_bare) && - boost::get(it->second.m_ptx_wallet_info->m_tx.vout[it->second.m_internal_output_index]).target.type() == typeid(txout_htlc)) - { - //need to find an entry in m_htlc and remove it - const txout_htlc& hltc = boost::get(boost::get(it->second.m_ptx_wallet_info->m_tx.vout[it->second.m_internal_output_index]).target); - uint64_t expiration_height = it->second.m_ptx_wallet_info->m_block_height + hltc.expiration; - auto pair_of_it = m_htlcs.equal_range(expiration_height); - bool found = false; - for (auto it = pair_of_it.first; it != pair_of_it.second; it++) - { - if (it->second.transfer_index == i) - { - found = true; - m_htlcs.erase(it); - break; - } - } - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(found, "Internal error: not found record in m_htlcs during rollback"); - } - - - if (!(it->second.m_key_image == null_ki && is_watch_only())) - { - auto it_ki = m_key_images.find(it->second.m_key_image); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it_ki != m_key_images.end(), "key image " << it->second.m_key_image << " not found"); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it->second.m_ptx_wallet_info->m_block_height >= including_height, "transfer #" << i << " block height is less than " << including_height); - m_key_images.erase(it_ki); - } - remove_transfer_from_amount_gindex_map(i); - ++transfers_detached; - } - m_transfers.erase(it_start, m_transfers.end()); - } - } - - for (uint64_t i = get_top_block_height(); i != including_height - 1 && i != 0; i--) - { - unprocess_htlc_triggers_on_block_removed(i); - } - size_t blocks_detached = detach_from_block_ids(including_height); - - //rollback spends - // do not clear spent flag in spent transfers as corresponding txs are most likely in the pool - // they will be moved into m_unconfirmed_txs for clearing in future (if tx will expire of removed from pool) - for (auto& tr_ : m_transfers) - { - uint64_t i = tr_.first; - auto& tr = tr_.second; - if (tr.m_spent_height >= including_height) - { - WLT_LOG_BLUE("Transfer [" << i << "] spent height: " << tr.m_spent_height << " -> 0, reason: detaching blockchain", LOG_LEVEL_1); - tr.m_spent_height = 0; - //check if it's hltc contract - } - } - - //rollback tranfers history - auto tr_hist_it = m_transfer_history.rend(); - for (auto it = m_transfer_history.rbegin(); it != m_transfer_history.rend(); it++) - { - if (it->height < including_height) - break; - tr_hist_it = it; // note that tr_hist_it->height >= height - } - - if (tr_hist_it != m_transfer_history.rend()) - { - auto it_from = --tr_hist_it.base(); - // before removing wti from m_transfer_history put it into m_unconfirmed_txs as txs from detached blocks are most likely be moved into the pool - for (auto it = it_from; it != m_transfer_history.end(); ++it) - { - // skip coinbase txs as they are not expected to go into the pool - if (is_coinbase(it->tx)) - { - continue; - } - - if (!m_unconfirmed_txs.insert(std::make_pair(it->tx_hash, *it)).second) - { - WLT_LOG_ERROR("can't move wti from transfer history to unronfirmed txs because such it is already here, tx hash: " << it->tx_hash); - } - } - m_transfer_history.erase(it_from, m_transfer_history.end()); - } - - //rollback payments - for (auto it = m_payments.begin(); it != m_payments.end(); ) - { - if (including_height <= it->second.m_block_height) - it = m_payments.erase(it); - else - ++it; - } - - //detach in m_last_zc_global_indexs - while (m_last_zc_global_indexs.size() && including_height <= m_last_zc_global_indexs.begin()->first) - { - m_last_zc_global_indexs.erase(m_last_zc_global_indexs.begin()); - } - - //asset descriptors - handle_rollback_events(including_height); - - WLT_LOG_L0("Detached blockchain on height " << including_height << ", transfers detached " << transfers_detached << ", blocks detached " << blocks_detached); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::operator()(const asset_register_event& e) - { - auto it = m_own_asset_descriptors.find(e.asset_id); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it != m_own_asset_descriptors.end(), "asset_id " << e.asset_id << "not found during rolling asset_register_event"); - m_own_asset_descriptors.erase(it); - } - void wallet2::operator()(const asset_update_event& e) - { - auto it = m_own_asset_descriptors.find(e.asset_id); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it != m_own_asset_descriptors.end(), "asset_id " << e.asset_id << "not found during rolling asset_update_event"); - it->second = e.own_context; - } - void wallet2::operator()(const asset_unown_event& e) - { - auto it = m_own_asset_descriptors.find(e.asset_id); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it == m_own_asset_descriptors.end(), "asset_id " << e.asset_id << "unexpectedly found during rolling asset_unown_event"); - m_own_asset_descriptors[e.asset_id] = e.own_context; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::handle_rollback_events(uint64_t including_height) - { - while (m_rollback_events.size() && m_rollback_events.back().first >= including_height) - { - boost::apply_visitor(*this, m_rollback_events.back().second); - m_rollback_events.pop_back(); - } - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::deinit() - { - m_wcallback.reset(); - return true; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::clear() - { - reset_all(); - return true; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::reset_all() - { - //static_cast(*this) = wallet2_base_state{}; - static_cast(*this).~wallet2_base_state(); - new (static_cast(this)) wallet2_base_state(); - return true; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::store_keys(std::string& buff, const std::string& password, wallet2::keys_file_data& keys_file_data, 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(acc, account_data); - WLT_CHECK_AND_ASSERT_MES(r, false, "failed to serialize wallet keys"); - - crypto::chacha8_key key; - crypto::generate_chacha8_key(password, key); - std::string cipher; - cipher.resize(account_data.size()); - keys_file_data.iv = crypto::rand(); - crypto::chacha8(account_data.data(), account_data.size(), key, keys_file_data.iv, &cipher[0]); - keys_file_data.account_data = cipher; - - r = ::serialization::dump_binary(keys_file_data, buff); - - return true; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::backup_keys(const std::string& path) - { - std::string buff; - wallet2::keys_file_data keys_file_data = AUTO_VAL_INIT(keys_file_data); - bool r = store_keys(buff, m_password, keys_file_data); - WLT_CHECK_AND_ASSERT_MES(r, false, "Failed to store keys"); - - r = file_io_utils::save_string_to_file(path, buff); - WLT_CHECK_AND_ASSERT_MES(r, false, "Failed to save_string_to_file at store keys"); - return true; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::reset_password(const std::string& pass) - { - m_password = pass; - return true; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::is_password_valid(const std::string& pass) - { - return pass == m_password; - } - //---------------------------------------------------------------------------------------------------- - namespace - { - bool verify_keys(const crypto::secret_key& sec, const crypto::public_key& expected_pub) - { - crypto::public_key pub; - bool r = crypto::secret_key_to_public_key(sec, pub); - return r && expected_pub == pub; - } - } - //---------------------------------------------------------------------------------------------------- - 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_T(out_key_to_ki); - 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, "UNRECOVERABLE ERROR, wallet stops: m_pending_key_images > m_pending_key_images_file_container" << ENDL << "Missing/wrong " << string_encoding::convert_to_ansii(m_pending_ki_file) << " file?"); - } - } - //---------------------------------------------------------------------------------------------------- - 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, uint64_t file_signature, keys_file_data& kf_data) - { - bool r = false; - if (file_signature == WALLET_FILE_SIGNATURE_OLD) - { - wallet2::keys_file_data_old kf_data_old; - r = ::serialization::parse_binary(buff, kf_data_old); - kf_data = wallet2::keys_file_data::from_old(kf_data_old); - } - else if (file_signature == WALLET_FILE_SIGNATURE_V2) - { - r = ::serialization::parse_binary(buff, kf_data); - } - THROW_IF_TRUE_WALLET_EX(!r, error::wallet_internal_error, "internal error: failed to deserialize"); - - crypto::chacha8_key key; - crypto::generate_chacha8_key(password, key); - std::string account_data; - account_data.resize(kf_data.account_data.size()); - crypto::chacha8(kf_data.account_data.data(), kf_data.account_data.size(), key, kf_data.iv, &account_data[0]); - - 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.view_secret_key, keys.account_address.view_public_key); - if (keys.spend_secret_key == currency::null_skey) - m_watch_only = true; - else - r = r && verify_keys(keys.spend_secret_key, keys.account_address.spend_public_key); - if (!r) - { - WLT_LOG_L0("Wrong password for wallet " << string_encoding::convert_to_ansii(m_wallet_file)); - tools::error::throw_wallet_ex(std::string(__FILE__ ":" STRINGIZE(__LINE__))); - } - init_log_prefix(); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::assign_account(const currency::account_base& acc) - { - clear(); - m_account = acc; - init_log_prefix(); - if (m_account.is_watch_only()) - m_watch_only = true; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::generate(const std::wstring& path, const std::string& pass, bool auditable_wallet) - { - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(validate_password(pass), "new wallet generation failed: password contains forbidden characters") - clear(); - prepare_file_names(path); - - m_password = pass; - m_account.generate(auditable_wallet); - 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)); - if (m_watch_only && !auditable_wallet) - { - bool stub; - load_keys2ki(true, stub); - } - store(); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::restore(const std::wstring& path, const std::string& pass, const std::string& seed_or_tracking_seed, bool tracking_wallet, const std::string& seed_password) - { - bool r = false; - clear(); - prepare_file_names(path); - m_password = pass; - - if (tracking_wallet) - { - r = m_account.restore_from_tracking_seed(seed_or_tracking_seed); - init_log_prefix(); - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "Could not load tracking wallet from a given seed: invalid tracking seed"); - m_watch_only = true; - } - else - { - r = m_account.restore_from_seed_phrase(seed_or_tracking_seed, seed_password); - init_log_prefix(); - THROW_IF_FALSE_WALLET_EX(r, error::wallet_wrong_seed_error, epee::string_encoding::convert_to_ansii(m_wallet_file)); - } - - 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)); - store(); - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::check_connection() - { - return m_core_proxy->check_connection(); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::set_votes_config_path(const std::string& path_to_config_file/* = tools::get_default_data_dir() + "\voting_config.json"*/) - { - m_votes_config_path = path_to_config_file; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::load_votes_config() - { - if (boost::filesystem::exists(m_votes_config_path)) - { - epee::serialization::load_t_from_json_file(m_votes_config, m_votes_config_path); - } - } - //---------------------------------------------------------------------------------------------------- - void wallet2::load(const std::wstring& wallet_, const std::string& password) - { - clear(); - prepare_file_names(wallet_); - - m_password = password; - - std::string keys_buff; - std::string body_buff; - - - boost::system::error_code e; - bool exists = boost::filesystem::exists(m_wallet_file, e); - THROW_IF_TRUE_WALLET_EX(e || !exists, error::file_not_found, epee::string_encoding::convert_to_ansii(m_wallet_file)); - boost::filesystem::ifstream data_file; - data_file.open(m_wallet_file, std::ios_base::binary | std::ios_base::in); - THROW_IF_TRUE_WALLET_EX(data_file.fail(), error::file_read_error, epee::string_encoding::convert_to_ansii(m_wallet_file)); - - wallet_file_binary_header wbh = AUTO_VAL_INIT(wbh); - - data_file.read((char*)&wbh, sizeof(wbh)); - THROW_IF_TRUE_WALLET_EX(data_file.fail(), error::file_read_error, epee::string_encoding::convert_to_ansii(m_wallet_file)); - - THROW_IF_TRUE_WALLET_EX(wbh.m_signature != WALLET_FILE_SIGNATURE_OLD && wbh.m_signature != WALLET_FILE_SIGNATURE_V2, error::file_read_error, epee::string_encoding::convert_to_ansii(m_wallet_file)); - THROW_IF_TRUE_WALLET_EX( - wbh.m_cb_keys > WALLET_FILE_MAX_KEYS_SIZE, error::file_read_error, epee::string_encoding::convert_to_ansii(m_wallet_file)); - - - keys_buff.resize(wbh.m_cb_keys); - data_file.read((char*)keys_buff.data(), wbh.m_cb_keys); - wallet2::keys_file_data kf_data = AUTO_VAL_INIT(kf_data); - load_keys(keys_buff, password, wbh.m_signature, kf_data); - - bool need_to_resync = false; - if (wbh.m_ver == WALLET_FILE_BINARY_HEADER_VERSION_INITAL) - { - // old WALLET_FILE_BINARY_HEADER_VERSION version means no encryption - need_to_resync = !tools::portable_unserialize_obj_from_stream(*this, data_file); - WLT_LOG_L1("Detected format: WALLET_FILE_BINARY_HEADER_VERSION_INITAL (need_to_resync=" << need_to_resync << ")"); - } - else if (wbh.m_ver == WALLET_FILE_BINARY_HEADER_VERSION_2) - { - tools::encrypt_chacha_in_filter decrypt_filter(password, kf_data.iv); - boost::iostreams::filtering_istream in; - in.push(decrypt_filter); - in.push(data_file); - need_to_resync = !tools::portable_unserialize_obj_from_stream(*this, in); - WLT_LOG_L1("Detected format: WALLET_FILE_BINARY_HEADER_VERSION_2 (need_to_resync=" << need_to_resync << ")"); - } - else - { - WLT_LOG_L0("Unknown wallet body version(" << wbh.m_ver << "), resync initiated."); - need_to_resync = true; - } - - - - if (m_watch_only && !is_auditable()) - load_keys2ki(true, need_to_resync); - - boost::system::error_code ec = AUTO_VAL_INIT(ec); - m_current_wallet_file_size = boost::filesystem::file_size(wallet_, ec); - - 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() - << ", file_size: " << m_current_wallet_file_size - << ", blockchain_size: " << m_chain.get_blockchain_current_size() - ); - WLT_LOG_L1("[LOADING]Blockchain shortener state: " << ENDL << m_chain.get_internal_state_text()); - - load_votes_config(); - - WLT_LOG_L1("(after loading: pending_key_images: " << m_pending_key_images.size() << ", pki file elements: " << m_pending_key_images_file_container.size() << ", tx_keys: " << m_tx_keys.size() << ")"); - - if (need_to_resync) - { - reset_history(); - WLT_LOG_L0("Unable to load history data from wallet file, wallet will be resynced!"); - } - - THROW_IF_TRUE_WALLET_EX(need_to_resync, error::wallet_load_notice_wallet_restored, epee::string_encoding::convert_to_ansii(m_wallet_file)); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::store() - { - store(m_wallet_file, m_password); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::store(const std::wstring& path) - { - store(path, m_password); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::store(const std::wstring& path_to_save, const std::string& password) - { - LOG_PRINT_L0("(before storing: pending_key_images: " << m_pending_key_images.size() << ", pki file elements: " << m_pending_key_images_file_container.size() << ", tx_keys: " << m_tx_keys.size() << ")"); - - std::string ascii_path_to_save = epee::string_encoding::convert_to_ansii(path_to_save); - - //prepare data - std::string keys_buff; - wallet2::keys_file_data keys_file_data = AUTO_VAL_INIT(keys_file_data); - bool r = store_keys(keys_buff, password, keys_file_data, m_watch_only); - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "failed to store_keys for wallet " << ascii_path_to_save); - - //store data - wallet_file_binary_header wbh = AUTO_VAL_INIT(wbh); - wbh.m_signature = WALLET_FILE_SIGNATURE_V2; - wbh.m_cb_keys = keys_buff.size(); - //@#@ change it to proper - wbh.m_ver = WALLET_FILE_BINARY_HEADER_VERSION_2; - std::string header_buff((const char*)&wbh, sizeof(wbh)); - - uint64_t ts = m_core_runtime_config.get_core_time(); - - // save to tmp file, then rename - boost::filesystem::path tmp_file_path = boost::filesystem::path(path_to_save); - tmp_file_path += L".newtmp_" + std::to_wstring(ts); - - boost::filesystem::ofstream data_file; - data_file.open(tmp_file_path, std::ios_base::binary | std::ios_base::out | std::ios::trunc); - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(!data_file.fail(), "failed to open binary wallet file for saving: " << tmp_file_path.string()); - data_file << header_buff << keys_buff; - - WLT_LOG_L0("Storing to temporary file " << tmp_file_path.string() << " ..."); - //creating encryption stream - tools::encrypt_chacha_out_filter decrypt_filter(m_password, keys_file_data.iv); - boost::iostreams::filtering_ostream out; - out.push(decrypt_filter); - out.push(data_file); - - r = tools::portble_serialize_obj_to_stream(*this, out); - if (!r) - { - data_file.close(); - boost::filesystem::remove(tmp_file_path); // remove tmp file if smth went wrong - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(false, "IO error while storing wallet to " << tmp_file_path.string() << " (portble_serialize_obj_to_stream failed)"); - } - - data_file.flush(); - data_file.close(); - boost::uintmax_t tmp_file_size = boost::filesystem::file_size(tmp_file_path); - WLT_LOG_L0("Stored successfully to temporary file " << tmp_file_path.string() << ", file size=" << tmp_file_size); - - WLT_LOG_L1("[LOADING]Blockchain shortener state: " << ENDL << m_chain.get_internal_state_text()); - - // for the sake of safety perform a double-renaming: wallet file -> old tmp, new tmp -> wallet file, remove old tmp - - boost::filesystem::path tmp_old_file_path = boost::filesystem::path(path_to_save); - tmp_old_file_path += L".oldtmp_" + std::to_wstring(ts); - - if (boost::filesystem::is_regular_file(path_to_save)) - { - boost::filesystem::rename(path_to_save, tmp_old_file_path); - WLT_LOG_L1("Renamed: " << ascii_path_to_save << " -> " << tmp_old_file_path.string()); - } - - boost::filesystem::rename(tmp_file_path, path_to_save); - WLT_LOG_L1("Renamed: " << tmp_file_path.string() << " -> " << ascii_path_to_save); - - if (boost::filesystem::remove(tmp_old_file_path)) - { - WLT_LOG_L1("Removed temporary file: " << tmp_old_file_path.string()); - } - - bool path_to_save_exists = boost::filesystem::is_regular_file(path_to_save); - bool tmp_file_path_exists = boost::filesystem::is_regular_file(tmp_file_path); - bool tmp_old_file_path_exists = boost::filesystem::is_regular_file(tmp_old_file_path); - - boost::system::error_code ec = AUTO_VAL_INIT(ec); - m_current_wallet_file_size = boost::filesystem::file_size(path_to_save, ec); - if (path_to_save_exists && !tmp_file_path_exists && !tmp_old_file_path_exists) - { - - WLT_LOG_L0("Wallet was successfully stored to " << ascii_path_to_save << ", file size=" << m_current_wallet_file_size - << " blockchain_size: " << m_chain.get_blockchain_current_size()); - } - else - { - WLT_LOG_ERROR("Wallet stroing to " << ascii_path_to_save << " might not be successfull: path_to_save_exists=" << path_to_save_exists << ", tmp_file_path_exists=" << tmp_file_path_exists << ", tmp_old_file_path_exists=" << tmp_old_file_path_exists); - throw tools::error::wallet_common_error(LOCATION_STR, "Wallet file storing might not be successfull. Please make sure you have backed up your seed phrase and check log for details."); - } - } - //---------------------------------------------------------------------------------------------------- - uint64_t wallet2::get_wallet_file_size()const - { - return m_current_wallet_file_size; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::set_use_deffered_global_outputs(bool use) - { - LOG_PRINT_L0("[DEFFERED_MODE]: " << use); - m_use_deffered_global_outputs = use; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::set_use_assets_whitelisting(bool use) - { - LOG_PRINT_L0("[ASSET_WHITELISTING_MODE]: " << use); - m_use_assets_whitelisting = use; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::store_watch_only(const std::wstring& path_to_save, const std::string& password) const - { - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(path_to_save != m_wallet_file, "trying to save watch-only wallet to the same wallet file!"); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(!m_watch_only, "saving watch-only wallet into a watch-only wallet is not allowed"); - - // prepare data for watch-only wallet - wallet2 wo; - // wallet2 wo(*this); copy-constructor is not working, so do a this serialization workaround - std::stringstream stream_buffer; - tools::portble_serialize_obj_to_stream(*this, stream_buffer); - tools::portable_unserialize_obj_from_stream(wo, stream_buffer); - - wo.m_watch_only = true; - wo.m_account = m_account; - wo.m_account.make_account_watch_only(); - wo.prepare_file_names(path_to_save); - - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(!boost::filesystem::exists(wo.m_wallet_file), "file " << epee::string_encoding::convert_to_ansii(wo.m_wallet_file) << " already exists"); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(!boost::filesystem::exists(wo.m_pending_ki_file), "file " << epee::string_encoding::convert_to_ansii(wo.m_pending_ki_file) << " already exists"); - - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(wo.m_pending_key_images.empty(), "pending key images is expected to be empty"); - if (!is_auditable()) - { - bool stub = false; - wo.load_keys2ki(true, stub); // to create outkey2ki file - } - - // populate pending key images for spent outputs (this will help to resync watch-only wallet) - for (const auto& tr : m_transfers) - { - - const auto& td = tr.second; - if (!td.is_spent()) - continue; // only spent transfers really need to be stored, because watch-only wallet will not be able to figure out they were spent otherwise - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.m_internal_output_index < td.m_ptx_wallet_info->m_tx.vout.size(), "invalid transfer #" << tr.first); - if (td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].type() != typeid(tx_out_bare)) - continue; - const currency::txout_target_v& out_t = boost::get(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]).target; - if (out_t.type() != typeid(currency::txout_to_key)) - continue; - const crypto::public_key& out_key = boost::get(out_t).key; - wo.m_pending_key_images.insert(std::make_pair(out_key, td.m_key_image)); - wo.m_pending_key_images_file_container.push_back(tools::out_key_to_ki{ out_key, td.m_key_image }); - WLT_LOG_L1("preparing watch-only wallet: added pending ki (" << out_key << ", " << td.m_key_image << ")"); - } - - // TODO additional clearing for watch-only wallet's data - - wo.store(path_to_save, password); - } - //---------------------------------------------------------------------------------------------------- - uint64_t wallet2::unlocked_balance() const - { - uint64_t stub = 0; - uint64_t unlocked_balance = 0; - balance(unlocked_balance, stub, stub, stub); - return unlocked_balance; - } - //---------------------------------------------------------------------------------------------------- - uint64_t wallet2::balance(uint64_t& unloked) const - { - uint64_t fake = 0; - return balance(unloked, fake, fake, fake); - } - //---------------------------------------------------------------------------------------------------- - uint64_t wallet2::balance(uint64_t& unlocked, uint64_t& awaiting_in, uint64_t& awaiting_out, uint64_t& mined, const crypto::public_key& asset_id /* = currency::native_coin_asset_id */) const - { - uint64_t total = 0; - unlocked = 0; - awaiting_in = 0; - awaiting_out = 0; - mined = 0; - std::unordered_map balances; - balance(balances, mined); - auto it = balances.find(asset_id); - if (it != balances.end()) - { - total = it->second.total; - unlocked = it->second.unlocked; - awaiting_in = it->second.awaiting_in; - awaiting_out = it->second.awaiting_out; - } - return total; - } - //---------------------------------------------------------------------------------------------------- - uint64_t wallet2::balance(const crypto::public_key& asset_id, uint64_t& unlocked) const - { - std::unordered_map balances; - uint64_t dummy; - balance(balances, dummy); - auto it = balances.find(asset_id); - if (it == balances.end()) - { - return 0; - } - unlocked = it->second.unlocked; - return it->second.total; - } - //---------------------------------------------------------------------------------------------------- - uint64_t wallet2::balance(const crypto::public_key& asset_id) const - { - uint64_t dummy = 0; - return balance(asset_id, dummy); - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::balance(std::unordered_map& balances, uint64_t& mined) const - { - mined = 0; - m_has_bare_unspent_outputs = false; - - for (auto& tr : m_transfers) - { - auto& td = tr.second; - - if (td.is_spendable() || (td.is_reserved_for_escrow() && !td.is_spent())) - { - wallet_public::asset_balance_entry_base& e = balances[td.get_asset_id()]; - e.total += td.amount(); - if (is_transfer_unlocked(td)) - e.unlocked += td.amount(); - if (td.m_flags & WALLET_TRANSFER_DETAIL_FLAG_MINED_TRANSFER) - { - if (td.m_ptx_wallet_info->m_block_height == 0) - { - //for genesis block we add actual amounts - mined += td.amount(); - } - else { - mined += CURRENCY_BLOCK_REWARD; //this code would work only for cases where block reward is full. For reduced block rewards might need more flexible code (TODO) - } - } - - if (!td.is_zc()) - m_has_bare_unspent_outputs = true; - } - } - - std::unordered_map subtransfers_by_assets_map; - for (auto& utx : m_unconfirmed_txs) - { - for (auto& subtransfer : utx.second.subtransfers) - { - wallet_public::asset_balance_entry_base& e = balances[subtransfer.asset_id]; - subtransfers_by_assets_map[subtransfer.asset_id] = subtransfer.is_income; - if (subtransfer.is_income) - { - e.total += subtransfer.amount; - e.awaiting_in += subtransfer.amount; - } - else - { - e.awaiting_out += subtransfer.amount; - if (subtransfer.asset_id == currency::native_coin_asset_id) - { - // this "if" present here only due to sophisticated checks in escrow_custom_test, which - // inaccuracy might be driven by tangled processing of sent transactions and unconfirmed - // transactions in pre-refactoring era (few weeks before this commit) - if (!(utx.second.contract.size() && utx.second.contract[0].state == wallet_public::escrow_contract_details_basic::contract_released_burned)) - { - e.awaiting_out -= currency::get_tx_fee(utx.second.tx); - } - } - } - } - - //has outgoing entries for each asset - //if (utx.second.has_outgoing_entries()) - //{ - //collect change to unconfirmed - for (const auto& emp_entry : utx.second.employed_entries.receive) - { - auto it_employed_entry = subtransfers_by_assets_map.find(emp_entry.asset_id); - if (it_employed_entry == subtransfers_by_assets_map.end()) - { - LOG_ERROR("Intenral error, check the wallet code at give location"); - continue; - } - if (!(it_employed_entry->second)) // if is_incoming == false, then we need to check for change and add it to total - { - wallet_public::asset_balance_entry_base& e = balances[emp_entry.asset_id]; - e.total += emp_entry.amount; - } - } - //} - - } - - return true; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::truncate_wallet() - { - if (m_concise_mode) - { - std::list items_to_remove; - for (auto& tr : m_transfers) - { - if (tr.second.is_spent() && tr.second.m_spent_height != 0 && !(tr.second.m_flags & WALLET_TRANSFER_DETAIL_CONCISE_MODE_PRESERVE) ) - { - if (tr.second.m_spent_height + m_wallet_concise_mode_max_reorg_blocks < m_chain.get_top_block_height()) - { - items_to_remove.push_back(tr.first); - } - } - } - - return truncate_transfers_and_history(items_to_remove); - } - return true; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::truncate_transfers_and_history(const std::list& items_to_remove) - { - //delete from m_transfers - for (auto item : items_to_remove) - { - auto it = m_transfers.find(item); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it != m_transfers.end(), - "internal error: item to delet " << item << " not found"); - m_transfers.erase(it); - } - - //delete from recent_history - if (m_truncate_history_max_entries != 0 && m_transfer_history.size() > m_truncate_history_max_entries) - { - m_transfer_history.erase(m_transfer_history.begin(), m_transfer_history.end() - m_truncate_history_max_entries); - } - - return true; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::balance(std::list& balances, uint64_t& mined) const - { - load_whitelisted_tokens_if_not_loaded(); - balances.clear(); - std::unordered_map balances_map; - this->balance(balances_map, mined); - std::unordered_map custom_assets_local = m_custom_assets; - - for (auto& own_asset : m_own_asset_descriptors) - { - if (m_whitelisted_assets.find(own_asset.first) == m_whitelisted_assets.end()) - { - custom_assets_local[own_asset.first] = own_asset.second; - } - } - - asset_descriptor_base native_asset_info = AUTO_VAL_INIT(native_asset_info); - native_asset_info.full_name = CURRENCY_NAME_SHORT_BASE; - native_asset_info.ticker = CURRENCY_NAME_ABR; - native_asset_info.decimal_point = CURRENCY_DISPLAY_DECIMAL_POINT; - custom_assets_local[currency::native_coin_asset_id] = native_asset_info; - - for (const auto& item : balances_map) - { - asset_descriptor_base asset_info = AUTO_VAL_INIT(asset_info); - //check if asset is whitelisted or customly added - - //check if it custom asset - auto it_cust = custom_assets_local.find(item.first); - if (it_cust == custom_assets_local.end()) - { - if (!m_use_assets_whitelisting) - continue; - - auto it_local = m_whitelisted_assets.find(item.first); - if (it_local == m_whitelisted_assets.end()) - { - WLT_LOG_YELLOW("WARNING: unknown asset " << item.first << " found and skipped; it's NOT included in balance", LOG_LEVEL_1); - continue; - } - else - { - asset_info = it_local->second; - } - } - else - { - asset_info = it_cust->second; - custom_assets_local.erase(it_cust); - } - - balances.push_back(wallet_public::asset_balance_entry()); - wallet_public::asset_balance_entry& new_item = balances.back(); - static_cast(new_item) = item.second; - new_item.asset_info.asset_id = item.first; - static_cast(new_item.asset_info) = asset_info; - } - //manually added assets should be always present, at least as zero balanced items - for (auto& asset : custom_assets_local) - { - balances.push_back(wallet_public::asset_balance_entry()); - wallet_public::asset_balance_entry& new_item = balances.back(); - new_item.asset_info.asset_id = asset.first; - static_cast(new_item.asset_info) = asset.second; - } - - return true; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::get_asset_info(const crypto::public_key& asset_id, currency::asset_descriptor_base& asset_info, uint32_t& asset_flags) const - { - asset_flags = aif_none; - if (asset_id == currency::native_coin_asset_id) - { - asset_info = currency::get_native_coin_asset_descriptor(); - asset_flags |= aif_whitelisted; - return true; - } - - // own asset? - auto it_own = m_own_asset_descriptors.find(asset_id); - if (it_own != m_own_asset_descriptors.end()) - { - asset_info = it_own->second; - asset_flags |= aif_own; - return true; - } - - // whitelisted? - auto it_white = m_whitelisted_assets.find(asset_id); - if (it_white != m_whitelisted_assets.end()) - { - asset_info = it_white->second; - asset_flags |= aif_whitelisted; - return true; - } - - // custom asset? - auto it_cust = m_custom_assets.find(asset_id); - if (it_cust != m_custom_assets.end()) - { - asset_info = it_cust->second; - return true; - } - - return false; - } - //---------------------------------------------------------------------------------------------------- - size_t wallet2::get_asset_decimal_point(const crypto::public_key& asset_id, size_t result_if_not_found /* = 0 */) const - { - if (asset_id == currency::native_coin_asset_id) - return currency::get_native_coin_asset_descriptor().decimal_point; - - // whitelisted? - auto it_white = m_whitelisted_assets.find(asset_id); - if (it_white != m_whitelisted_assets.end()) - return it_white->second.decimal_point; - - // custom asset? - auto it_cust = m_custom_assets.find(asset_id); - if (it_cust != m_custom_assets.end()) - return it_cust->second.decimal_point; - - auto it_own = m_own_asset_descriptors.find(asset_id); - if (it_own != m_own_asset_descriptors.end()) - return it_own->second.decimal_point; - - return result_if_not_found; // if not overriden, use the 0 decimal point (raw numbers) as the default - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::get_asset_decimal_point(const crypto::public_key& asset_id, size_t* p_decimal_point_result) const - { - size_t decimal_point = get_asset_decimal_point(asset_id, SIZE_MAX); - if (decimal_point == SIZE_MAX) + on_tx_sent(batch_index, transaction{}, 0, 0, false, "destinations for tx couldn't be prepared"); + LOG_PRINT_L0("prepare_tx_destinations failed, batch_index = " << batch_index); return false; - if (p_decimal_point_result != nullptr) - *p_decimal_point_result = decimal_point; - return true; - } - //---------------------------------------------------------------------------------------------------- - uint64_t wallet2::balance() const - { - uint64_t stub = 0; - return balance(stub, stub, stub, stub); - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::add_custom_asset_id(const crypto::public_key& asset_id, asset_descriptor_base& asset_descriptor) - { - currency::COMMAND_RPC_GET_ASSET_INFO::request req = AUTO_VAL_INIT(req); - currency::COMMAND_RPC_GET_ASSET_INFO::response resp = AUTO_VAL_INIT(resp); - req.asset_id = asset_id; - - bool r = m_core_proxy->call_COMMAND_RPC_GET_ASSET_INFO(req, resp); - if (r && resp.status == API_RETURN_CODE_OK) - { - m_custom_assets[asset_id] = resp.asset_descriptor; - asset_descriptor = resp.asset_descriptor; - return true; - } - return false; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::delete_custom_asset_id(const crypto::public_key& asset_id) - { - const auto it = m_custom_assets.find(asset_id); - if (it != m_custom_assets.end()) - { - m_custom_assets.erase(it); } - return true; - } - //---------------------------------------------------------------------------------------------------- - const std::unordered_map& wallet2::get_local_whitelist() const - { - return m_custom_assets; - } - //---------------------------------------------------------------------------------------------------- - const std::unordered_map& wallet2::get_global_whitelist() const - { - return m_whitelisted_assets; - } - //---------------------------------------------------------------------------------------------------- - const std::unordered_map& wallet2::get_own_assets() const - { - return m_own_asset_descriptors; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::load_whitelisted_tokens() const - { - if (!m_use_assets_whitelisting) - return true; - - m_whitelisted_assets.clear(); - std::string body; - wallet_public::assets_whitelist aw = AUTO_VAL_INIT(aw); - if (epee::net_utils::get_http_json_t(WALLET_ASSETS_WHITELIST_URL, aw)) - { - for (auto it = aw.assets.begin(); it != aw.assets.end(); it++) - { - m_whitelisted_assets[it->asset_id] = static_cast(*it); - } - return true; - } - return false; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::load_whitelisted_tokens_if_not_loaded() const - { - if (m_whitelist_updated) - { - return true; - } - if (!load_whitelisted_tokens()) - return false; - m_whitelist_updated = true; - return true; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::get_transfers(transfer_container& incoming_transfers) const - { - incoming_transfers = m_transfers; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::generate_utxo_defragmentation_transaction_if_needed(currency::transaction& tx) - { - if (!m_defragmentation_tx_enabled) - return false; - - construct_tx_param ctp = get_default_construct_tx_param(); - ctp.create_utxo_defragmentation_tx = true; - finalized_tx ftp{}; - - transfer(ctp, ftp, false, nullptr); - - if (ftp.was_not_prepared) - return false; // no such UTXO were found, not an error - - tx = ftp.tx; - return true; - } - //---------------------------------------------------------------------------------------------------- - std::string wallet2::get_transfers_str(bool include_spent /*= true*/, bool include_unspent /*= true*/, bool show_only_unknown /*= false*/, const std::string& filter_asset_ticker /*= std::string{}*/) const - { - static const char* header = " index amount ticker g_index flags block tx out# asset id"; - std::stringstream ss; - ss << header << ENDL; - size_t count = 0; - size_t unknown_assets_outs_count = 0; - for (const auto& tr : m_transfers) - { - uint64_t i = tr.first; - const transfer_details& td = tr.second; - - if ((td.is_spent() && !include_spent) || (!td.is_spent() && !include_unspent)) - continue; - - bool is_locked = !is_transfer_unlocked(td); - bool native_coin = td.is_native_coin(); - asset_descriptor_base adb{}; - uint32_t asset_info_flags{}; - if (get_asset_info(td.get_asset_id(), adb, asset_info_flags) == show_only_unknown) - { - if (!show_only_unknown) - ++unknown_assets_outs_count; - continue; - } - - if (!filter_asset_ticker.empty() && adb.ticker != filter_asset_ticker) - continue; - - ss << std::right << (is_locked ? "*" : " ") << - std::setw(5) << i << " " << - std::setw(21) << print_asset_money(td.m_amount, adb.decimal_point) << " " << - std::setw(6) << std::left << (native_coin ? std::string(" ") : adb.ticker) << " " << std::right << - std::setw(7) << td.m_global_output_index << " " << - std::setw(2) << std::setfill('0') << td.m_flags << std::setfill(' ') << ":" << - std::setw(5) << transfer_flags_to_str(td.m_flags) << " " << - std::setw(7) << td.m_ptx_wallet_info->m_block_height << " " << - get_transaction_hash(td.m_ptx_wallet_info->m_tx) << " " << - std::setw(4) << td.m_internal_output_index << " "; - if (native_coin) - ss << " "; - else - ss << td.get_asset_id(); - - ss << ENDL; - - ++count; - } - - ss << "printed " << count << " outputs of " << m_transfers.size() << " total" << ENDL; - if (unknown_assets_outs_count == 1) - ss << "(" << unknown_assets_outs_count << " output with unrecognized asset id is not shown, use 'list_outputs unknown' to see it)" << ENDL; - else if (unknown_assets_outs_count > 1) - ss << "(" << unknown_assets_outs_count << " outputs with unrecognized asset ids are not shown, use 'list_outputs unknown' to see them)" << ENDL; - return ss.str(); - } - //---------------------------------------------------------------------------------------------------- - std::string wallet2::get_balance_str() const - { - // balance unlocked / [balance total] ticker asset id - // 0.21 / 98.51 DP2 a6974d5874e97e5f4ed5ad0a62f0975edbccb1bb55502fc75c7fe808f12f44d3 - // 190.123456789012 / 199.123456789012 ZANO d6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a - // 98.0 BGTVUW af2b12f3033337f9aea1845a6bc3fc966ed4d13227a3ace7706fca7dbcdaa7e2 - // 1000.034 DP3 d4aba1020f26927571771e04b585b4ffb211f52708d5e4c465bbdfa4a12e6271 - - static const char* header = " balance unlocked / [balance total] ticker asset id"; - std::stringstream ss; - ss << header << ENDL; - - std::list balances; - uint64_t mined = 0; - balance(balances, mined); - - auto native_coin_it = std::find_if(balances.begin(), balances.end(), [&](auto& v) { return v.asset_info.asset_id == currency::native_coin_asset_id; }); - if (native_coin_it != balances.end()) - { - balances.push_front(*native_coin_it); - balances.erase(native_coin_it); - } - - for (const tools::wallet_public::asset_balance_entry& b : balances) - { - ss << " " << std::left << std::setw(21) << print_fixed_decimal_point_with_trailing_spaces(b.unlocked, b.asset_info.decimal_point); - if (b.total == b.unlocked) - ss << std::string(21 + 3, ' '); - else - ss << " / " << std::setw(21) << print_fixed_decimal_point_with_trailing_spaces(b.total, b.asset_info.decimal_point); - ss << " " << std::setw(8) << std::left << b.asset_info.ticker << " " << b.asset_info.asset_id; - if (b.asset_info.asset_id == native_coin_asset_id) - ss << " NATIVE"; - ss << ENDL; - } - - return ss.str(); - } - //---------------------------------------------------------------------------------------------------- - std::string wallet2::get_balance_str_raw() const - { - // balance unlocked / [balance total] DP asset id - // 0.21 / 98.51 2 a6974d5874e97e5f4ed5ad0a62f0975edbccb1bb55502fc75c7fe808f12f44d3 - // 190.123456789012 / 199.123456789012 12 d6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a - // 98.0 12 af2b12f3033337f9aea1845a6bc3fc966ed4d13227a3ace7706fca7dbcdaa7e2 - // 1000.034 3 d4aba1020f26927571771e04b585b4ffb211f52708d5e4c465bbdfa4a12e6271 - //WHITELIST: - // 7d3f348fbebfffc4e61a3686189cf870ea393e1c88b8f636acbfdacf9e4b2db2 CT - // ... - - static const char* header = " balance unlocked / [balance total] ticker asset id DP flags"; - std::stringstream ss; - ss << header << ENDL; - - uint64_t dummy = 0; - typedef std::unordered_map balances_map_t; - balances_map_t balances_map; - this->balance(balances_map, dummy); - - auto print_map = [&](const balances_map_t& map) { - for (const auto& entry : map) - { - uint32_t asset_flags = 0; - asset_descriptor_base asset_info{}; - bool has_info = get_asset_info(entry.first, asset_info, asset_flags); - ss << " " << std::left << std::setw(21) << print_fixed_decimal_point_with_trailing_spaces(entry.second.unlocked, asset_info.decimal_point); - if (entry.second.total == entry.second.unlocked) - ss << std::string(21 + 3, ' '); - else - ss << " / " << std::setw(21) << print_fixed_decimal_point_with_trailing_spaces(entry.second.total, asset_info.decimal_point); - - ss << " " << std::setw(8) << std::left << asset_info.ticker; - ss << " " << entry.first << " "; - - if (has_info) - ss << std::setw(2) << std::right << (int)asset_info.decimal_point; - else - ss << "??"; - - ss << " "; - - if (entry.first == native_coin_asset_id) - { - ss << "NATIVE"; - } - else if (asset_flags != aif_none) - { - if (asset_flags & aif_own) - ss << "own,"; - if (asset_flags & aif_whitelisted) - ss << "whitelisted,"; - ss.seekp(-1, ss.cur); // trim comma - } - ss << ENDL; - } - }; - - auto balances_map_it = balances_map.find(native_coin_asset_id); - if (balances_map_it != balances_map.end()) - { - balances_map_t native_coin_map; - native_coin_map.insert(*balances_map_it); - balances_map.erase(balances_map_it); - print_map(native_coin_map); - } - print_map(balances_map); - - - //print whitelist - ss << "WHITELIST: " << ENDL; - - for (const auto& entry : m_whitelisted_assets) - { - ss << " " << std::left << entry.first << " " << entry.second.ticker << ENDL; - } - - // print custom list - ss << "CUSTOM LIST: " << ENDL; - - for (const auto& entry : m_custom_assets) - { - ss << " " << std::left << entry.first << " " << entry.second.ticker << ENDL; - } - - // print own list - ss << "OWN DESCRIPTORS LIST: " << ENDL; - - for (const auto& entry : m_own_asset_descriptors) - { - ss << " " << std::left << entry.first << " " << entry.second.ticker << ENDL; - } - - return ss.str(); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::get_payments(const std::string& payment_id, std::list& payments, uint64_t min_height) const - { - auto range = m_payments.equal_range(payment_id); - 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().view_secret_key); - - // deserialize args - currency::finalized_tx ft = AUTO_VAL_INIT(ft); - bool r = t_unserializable_object_from_blob(ft.ftp, decrypted_src_blob); - THROW_IF_FALSE_WALLET_EX(r, error::wallet_common_error, "Failed to decrypt tx sources blob"); - - // make sure unsigned tx was created with the same keys - THROW_IF_FALSE_WALLET_EX(ft.ftp.spend_pub_key == m_account.get_keys().account_address.spend_public_key, error::wallet_common_error, "The was created in a different wallet, keys missmatch"); - - finalize_transaction(ft.ftp, ft.tx, ft.one_time_key, false); - - // calculate key images for each change output - crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX( - crypto::generate_key_derivation( - m_account.get_keys().account_address.view_public_key, - ft.one_time_key, - derivation), - "internal error: sign_transfer: failed to generate key derivation(" - << m_account.get_keys().account_address.view_public_key - << ", view secret key: " << ft.one_time_key << ")"); - - for (size_t i = 0; i < ft.tx.vout.size(); ++i) - { - VARIANT_SWITCH_BEGIN(ft.tx.vout[i]); - VARIANT_CASE_CONST(tx_out_bare, out) - { - if (out.target.type() != typeid(txout_to_key)) - continue; - const txout_to_key& otk = boost::get(out.target); - - crypto::public_key ephemeral_pub = AUTO_VAL_INIT(ephemeral_pub); - if (!crypto::derive_public_key(derivation, i, m_account.get_keys().account_address.spend_public_key, ephemeral_pub)) - { - WLT_LOG_ERROR("derive_public_key failed for tx " << get_transaction_hash(ft.tx) << ", out # " << i); - } - - if (otk.key == ephemeral_pub) - { - // this is the output to the given keys - // derive secret key and calculate key image - crypto::secret_key ephemeral_sec = AUTO_VAL_INIT(ephemeral_sec); - crypto::derive_secret_key(derivation, i, m_account.get_keys().spend_secret_key, ephemeral_sec); - crypto::key_image ki = AUTO_VAL_INIT(ki); - crypto::generate_key_image(ephemeral_pub, ephemeral_sec, ki); - - ft.outs_key_images.push_back(make_serializable_pair(static_cast(i), ki)); - } - } - VARIANT_CASE_CONST(tx_out_zarcanum, o); - //@#@ - VARIANT_SWITCH_END(); - } - - // serialize and encrypt the result - signed_tx_blob = t_serializable_object_to_blob(ft); - crypto::chacha_crypt(signed_tx_blob, m_account.get_keys().view_secret_key); - - tx = ft.tx; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::sign_transfer_files(const std::string& tx_sources_file, const std::string& signed_tx_file, currency::transaction& tx) - { - std::string sources_blob; - bool r = epee::file_io_utils::load_file_to_string(tx_sources_file, sources_blob); - THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "failed to open file " << tx_sources_file); - - std::string signed_tx_blob; - sign_transfer(sources_blob, signed_tx_blob, tx); - - r = epee::file_io_utils::save_string_to_file(signed_tx_file, signed_tx_blob); - THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "failed to store signed tx to file " << signed_tx_file); - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::get_utxo_distribution(std::map& distribution) - { - //TODO@#@ - /* - prepare_free_transfers_cache(0); - for (auto ent : m_found_free_amounts) - { - distribution[ent.first] = ent.second.size(); - } - */ - - return false; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::submit_transfer(const std::string& signed_tx_blob, currency::transaction& tx) - { - // decrypt sources - std::string decrypted_src_blob = crypto::chacha_crypt(signed_tx_blob, m_account.get_keys().view_secret_key); - - // deserialize tx data - currency::finalized_tx ft = AUTO_VAL_INIT(ft); - bool r = t_unserializable_object_from_blob(ft, decrypted_src_blob); - THROW_IF_FALSE_WALLET_EX(r, error::wallet_common_error, "Failed to decrypt signed tx data"); - tx = ft.tx; - crypto::hash tx_hash = get_transaction_hash(tx); - - // foolproof - 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: " << tx_hash); - + mark_transfers_as_spent(ftp.selected_transfers, std::string("sweep bare UTXO, tx: ") + epee::string_tools::pod_to_hex(get_transaction_hash(ftx.tx))); try { - send_transaction_to_network(tx); + finalize_transaction(ftp, ftx, send_to_network); + on_tx_sent(batch_index, ftx.tx, group.total_amount + group.additional_tid_amount, fee, true, std::string()); } - catch (...) + catch (std::exception& e) { - // clear transfers flags if smth went wrong - uint32_t flag = WALLET_TRANSFER_DETAIL_FLAG_SPENT | WALLET_TRANSFER_DETAIL_FLAG_COLD_SIG_RESERVATION; - clear_transfers_from_flag(ft.ftp.selected_transfers, flag, "broadcasting tx " + epee::string_tools::pod_to_hex(tx_hash) + " was unsuccessful"); - throw; + clear_transfers_from_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, std::string("exception on sweep bare UTXO, tx: ") + epee::string_tools::pod_to_hex(get_transaction_hash(ftx.tx))); + on_tx_sent(batch_index, transaction{}, 0, 0, false, e.what()); + return false; } - add_sent_tx_detailed_info(tx, ft.ftp.attachments, ft.ftp.prepared_destinations, ft.ftp.selected_transfers); - m_tx_keys.insert(std::make_pair(tx_hash, ft.one_time_key)); + ++batch_index; + } - if (m_watch_only) + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::sweep_bare_unspent_outputs(const currency::account_public_address& target_address, const std::vector& tids_grouped_by_txs, + size_t& total_txs_sent, uint64_t& total_amount_sent, uint64_t& total_fee_spent, uint64_t& total_bare_outs_sent) +{ + total_txs_sent = 0; + total_amount_sent = 0; + total_fee_spent = 0; + total_bare_outs_sent = 0; + auto on_tx_sent_callback = [&](size_t batch_index, const currency::transaction& tx, uint64_t amount, uint64_t fee, bool sent_ok, const std::string& err) { + if (sent_ok) { - std::vector> pk_ki_to_be_added; - std::vector> tri_ki_to_be_added; + total_bare_outs_sent += count_type_in_variant_container(tx.vin); + ++total_txs_sent; + total_fee_spent += fee; + total_amount_sent += amount; + } + }; - for (auto& p : ft.outs_key_images) + return sweep_bare_unspent_outputs(target_address, tids_grouped_by_txs, on_tx_sent_callback); +} +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::get_directly_spent_transfer_index_by_input_in_tracking_wallet(const currency::txin_to_key& intk) +{ + return get_directly_spent_transfer_index_by_input_in_tracking_wallet(intk.amount, intk.key_offsets); +} +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::get_directly_spent_transfer_index_by_input_in_tracking_wallet(const currency::txin_zc_input& inzc) +{ + return get_directly_spent_transfer_index_by_input_in_tracking_wallet(0, inzc.key_offsets); +} +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::get_directly_spent_transfer_index_by_input_in_tracking_wallet(uint64_t amount, const std::vector& key_offsets) +{ + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(is_auditable() && is_watch_only(), "this is not an auditable-watch-only (tracking) wallet"); + + uint64_t tid = UINT64_MAX; + + // try to find a reference among own UTXOs + std::vector abs_key_offsets = relative_output_offsets_to_absolute(key_offsets); // potential speed-up: don't convert to abs offsets as we interested only in direct spends for auditable wallets. Now it's kind a bit paranoid. + for (auto v : abs_key_offsets) + { + if (v.type() != typeid(uint64_t)) + continue; + uint64_t gindex = boost::get(v); + auto it = m_amount_gindex_to_transfer_id.find(std::make_pair(amount, gindex)); + if (it != m_amount_gindex_to_transfer_id.end()) + { + tid = it->second; + //WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(tid < m_transfers.size(), "invalid tid: " << tid << ", ref from input with amount: " << amount << ", gindex: " << gindex); + auto& td = m_transfers.at(it->second); + if (key_offsets.size() != 1) { - THROW_IF_FALSE_WALLET_INT_ERR_EX(p.first < tx.vout.size(), "outs_key_images has invalid out index: " << p.first << ", tx.vout.size() = " << tx.vout.size()); - THROW_IF_FALSE_WALLET_INT_ERR_EX(tx.vout[p.first].type() == typeid(tx_out_bare), "Unexpected type in submit_transfer: " << tx.vout[p.first].type().name()); - auto& out = boost::get(tx.vout[p.first]); - THROW_IF_FALSE_WALLET_INT_ERR_EX(out.target.type() == typeid(txout_to_key), "outs_key_images has invalid out type, index: " << p.first); - const txout_to_key& otk = boost::get(out.target); - pk_ki_to_be_added.push_back(std::make_pair(otk.key, p.second)); + // own output was used in non-direct transaction + // the core should not allow this to happen, the only way it may happen - mixing in own output that was sent without mix_attr == 1 + // log strange situation + std::stringstream ss; + ss << "own transfer tid=" << tid << " tx=" << td.tx_hash() << " mix_attr=" << td.mix_attr() << ", is referenced by a transaction with mixins, ref from input with amount: " << amount << ", gindex: " << gindex; + WLT_LOG_YELLOW(ss.str(), LOG_LEVEL_0); + if (m_wcallback) + m_wcallback->on_message(i_wallet2_callback::ms_yellow, ss.str()); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.mix_attr() != CURRENCY_TO_KEY_OUT_FORCED_NO_MIX, ss.str()); // if mix_attr == 1 this should never happen (mixing in an output with mix_attr = 1) as the core must reject such txs + // our own output has mix_attr != 1 for some reason (a sender did not set correct mix_attr e.g.) + // but mixin count > 1 so we can't say it is spent for sure + tid = UINT64_MAX; + continue; } + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.m_spent_height == 0, "transfer is spent in blockchain, tid: " << tid << ", ref from input with amount: " << amount << ", gindex: " << gindex); + // okay, own output is being spent, return it + break; + } + } + return tid; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::handle_unconfirmed_tx(process_transaction_context& ptc) +{ + const transaction& tx = ptc.tx; + ptc.timestamp = m_core_runtime_config.get_core_time(); + // read extra + std::vector outs; + //uint64_t sum_of_received_native_outs = 0; + crypto::public_key tx_pub_key = null_pkey; + bool r = parse_and_validate_tx_extra(tx, tx_pub_key); + THROW_IF_TRUE_WALLET_EX(!r, error::tx_extra_parse_error, tx); + //check if we have money + crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); + r = lookup_acc_outs(m_account.get_keys(), tx, tx_pub_key, outs, derivation); + THROW_IF_TRUE_WALLET_EX(!r, error::acc_outs_lookup_error, tx, tx_pub_key, m_account.get_keys()); - THROW_IF_FALSE_WALLET_INT_ERR_EX(tx.vin.size() == ft.ftp.sources.size(), "tx.vin and ft.ftp.sources sizes missmatch"); - for (size_t i = 0; i < tx.vin.size(); ++i) + //collect incomes + for (auto& o : outs) + { + if (out_is_multisig(tx.vout[o.index])) + continue; + + ptc.total_balance_change[o.asset_id] += o.amount; + ptc.employed_entries.receive.push_back(wallet_public::employed_tx_entry{ o.index, o.amount, o.asset_id }); + } + + + bool new_multisig_spend_detected = false; + //check if we have spendings + //uint64_t sum_of_spent_native_coin = 0; + std::list spend_transfers; + // check all outputs for spending (compare key images) + for (size_t i = 0; i != tx.vin.size(); i++) + { + auto& in = tx.vin[i]; + if (in.type() == typeid(currency::txin_to_key)) + { + const currency::txin_to_key& intk = boost::get(in); + uint64_t tid = UINT64_MAX; + if (is_auditable() && is_watch_only()) { - const txin_v& in = tx.vin[i]; - THROW_IF_FALSE_WALLET_CMN_ERR_EX(in.type() == typeid(txin_to_key), "tx " << tx_hash << " has a non txin_to_key input"); - const crypto::key_image& ki = boost::get(in).k_image; - - const auto& src = ft.ftp.sources[i]; - THROW_IF_FALSE_WALLET_INT_ERR_EX(src.real_output < src.outputs.size(), "src.real_output is out of bounds: " << src.real_output); - const crypto::public_key& out_key = src.outputs[src.real_output].stealth_address; - - tri_ki_to_be_added.push_back(std::make_pair(src.transfer_index, ki)); - pk_ki_to_be_added.push_back(std::make_pair(out_key, ki)); + // tracking wallet, assuming all outputs are spent directly because of mix_attr = 1 + tid = get_directly_spent_transfer_index_by_input_in_tracking_wallet(intk.amount, intk.key_offsets); } - - for (auto& p : pk_ki_to_be_added) + else { - auto it = m_pending_key_images.find(p.first); - if (it != m_pending_key_images.end()) + // wallet with spend secret key -- we can calculate own key images and then search among them + auto it = m_key_images.find(intk.k_image); + if (it != m_key_images.end()) { - LOG_PRINT_YELLOW("warning: for tx " << tx_hash << " out pub key " << p.first << " already exist in m_pending_key_images, ki: " << it->second << ", proposed new ki: " << p.second, LOG_LEVEL_0); - } - else - { - m_pending_key_images[p.first] = p.second; - m_pending_key_images_file_container.push_back(tools::out_key_to_ki{ p.first, p.second }); - LOG_PRINT_L2("for tx " << tx_hash << " pending key image added (" << p.first << ", " << p.second << ")"); + tid = it->second; } } - for (auto& p : tri_ki_to_be_added) + if (tid != UINT64_MAX) { - //THROW_IF_FALSE_WALLET_INT_ERR_EX(p.first < m_transfers.size(), "incorrect transfer index: " << p.first); - auto& tr = m_transfers.at(p.first); - if (tr.m_key_image != currency::null_ki && tr.m_key_image != p.second) - { - 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; - LOG_PRINT_L2("for tx " << tx_hash << " key image " << p.second << " was associated with transfer # " << p.first); + // own output is being spent by this input + //sum_of_spent_native_coin += intk.amount; + ptc.employed_entries.spent.push_back(wallet_public::employed_tx_entry{ i, m_transfers.at(tid).amount(), m_transfers.at(tid).get_asset_id() }); + spend_transfers.push_back(tid); + ptc.total_balance_change[currency::native_coin_asset_id] -= m_transfers.at(tid).amount(); + CHECK_AND_ASSERT_THROW_MES(m_transfers.at(tid).get_asset_id() == currency::native_coin_asset_id, "Unexpected asset id for native txin_to_key"); } } - - // TODO: print inputs' key images - print_tx_sent_message(tx, "(from submit_transfer)"); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::submit_transfer_files(const std::string& signed_tx_file, currency::transaction& tx) - { - std::string signed_tx_blob; - bool r = epee::file_io_utils::load_file_to_string(signed_tx_file, signed_tx_blob); - THROW_IF_FALSE_WALLET_EX(r, error::wallet_common_error, std::string("failed to open file ") + signed_tx_file); - - submit_transfer(signed_tx_blob, tx); - } - //---------------------------------------------------------------------------------------------------- - uint64_t wallet2::get_recent_transfers_total_count() - { - return m_transfer_history.size(); - } - //---------------------------------------------------------------------------------------------------- - uint64_t wallet2::get_transfer_entries_count() - { - return m_transfers.size(); - } - //---------------------------------------------------------------------------------------------------- - - template - bool enum_container(iterator_t it_begin, iterator_t it_end, callback_t cb) - { - for (iterator_t it = it_begin; it != it_end; it++) + else if (in.type() == typeid(currency::txin_zc_input)) { - if (!cb(*it, it - it_begin)) - return true; + // bad design -- remove redundancy like using wallet2::process_input_t() + const currency::txin_zc_input& zc = boost::get(in); + uint64_t tid = UINT64_MAX; + if (is_auditable() && is_watch_only()) + { + // tracking wallet, assuming all outputs are spent directly because of mix_attr = 1 + tid = get_directly_spent_transfer_index_by_input_in_tracking_wallet(zc); + } + else + { + // wallet with spend secret key -- we can calculate own key images and then search among them + auto it = m_key_images.find(zc.k_image); + if (it != m_key_images.end()) + { + tid = it->second; + } + } + + if (tid != UINT64_MAX) + { + ptc.employed_entries.spent.push_back(wallet_public::employed_tx_entry{ i, m_transfers.at(tid).amount(), m_transfers.at(tid).get_asset_id() }); + spend_transfers.push_back(tid); + ptc.total_balance_change[m_transfers.at(tid).get_asset_id()] -= m_transfers.at(tid).amount(); + } } - return true; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::is_defragmentation_transaction(const wallet_public::wallet_transfer_info& wti) - { - if (wti.employed_entries.receive.size() && wti.employed_entries.spent.size() && wti.subtransfers.size() == 1) + else if (in.type() == typeid(currency::txin_multisig)) { - if (wti.subtransfers[0].asset_id == currency::native_coin_asset_id && !wti.subtransfers[0].is_income && wti.subtransfers[0].amount == get_tx_fee(wti.tx)) - return true; + crypto::hash multisig_id = boost::get(in).multisig_out_id; + auto it = m_multisig_transfers.find(multisig_id); + if (it != m_multisig_transfers.end()) + { + //ptc.employed_entries.spent_indices.push_back(i); + ptc.employed_entries.spent.push_back(wallet_public::employed_tx_entry{ i }); + if (ptc.pmultisig_entries) + { + r = ptc.pmultisig_entries->insert(std::make_pair(multisig_id, std::make_pair(tx, ptc.employed_entries))).second; + if (!r) + { + WLT_LOG_RED("Warning: Receiving the same multisig out id from tx pool more then once: " << multisig_id, LOG_LEVEL_0); + } + } + if (m_unconfirmed_multisig_transfers.count(multisig_id) == 0) + { + // new unconfirmed multisig (see also comments on multisig tranafers handling below) + uint32_t flags_before = it->second.m_flags; + it->second.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; // mark as spent + it->second.m_spent_height = 0; // height 0 means unconfirmed + WLT_LOG_L0("From tx pool got new unconfirmed multisig out with id: " << multisig_id << " tx: " << get_transaction_hash(tx) << " Marked as SPENT, flags: " << flags_before << " -> " << it->second.m_flags); + new_multisig_spend_detected = true; + } + + } } - return false; } - //---------------------------------------------------------------------------------------------------- - void wallet2::get_recent_transfers_history(std::vector& trs, size_t offset, size_t count, uint64_t& total, uint64_t& last_item_index, bool exclude_mining_txs, bool start_from_end) + + //do final calculations + bool has_in_transfers = false; + bool has_out_transfers = false; + for (const auto& bce : ptc.total_balance_change) { - if (!count || offset >= m_transfer_history.size()) + if (bce.second > 0) + { + has_in_transfers = true; + } + else if (bce.second < 0) + { + has_out_transfers = true; + } + } + if (!is_tx_expired(tx, ptc.tx_expiration_ts_median) && (new_multisig_spend_detected || has_in_transfers || has_out_transfers || (currency::is_derivation_used_to_encrypt(tx, derivation)))) + { + m_unconfirmed_in_transfers[ptc.tx_hash()] = tx; + if (m_unconfirmed_txs.count(ptc.tx_hash())) return; - auto cb = [&](wallet_public::wallet_transfer_info& wti, size_t local_offset) { + //prepare notification about pending transaction + wallet_public::wallet_transfer_info& unconfirmed_wti = misc_utils::get_or_insert_value_initialized(m_unconfirmed_txs, ptc.tx_hash()); + prepare_wti(unconfirmed_wti, ptc); + for (auto tr_index : spend_transfers) + { + //if (tr_index > m_transfers.size()) + //{ + // WLT_LOG_ERROR("INTERNAL ERROR: tr_index " << tr_index << " more then m_transfers.size()=" << m_transfers.size()); + // continue; + //} + uint32_t flags_before = m_transfers.at(tr_index).m_flags; + m_transfers.at(tr_index).m_flags |= WALLET_TRANSFER_DETAIL_FLAG_SPENT; + WLT_LOG_L1("wallet transfer #" << tr_index << " is marked as spent, flags: " << flags_before << " -> " << m_transfers.at(tr_index).m_flags << ", reason: UNCONFIRMED tx: " << ptc.tx_hash()); + unconfirmed_wti.selected_indicies.push_back(tr_index); + } + rise_on_transfer2(unconfirmed_wti); + } +} - if (exclude_mining_txs) - { - if (currency::is_coinbase(wti.tx) || is_defragmentation_transaction(wti)) - return true; - } - trs.push_back(wti); - load_wallet_transfer_info_flags(trs.back()); - last_item_index = offset + local_offset; - trs.back().transfer_internal_index = last_item_index; - if (wti.remote_addresses.size() == 1) - { - wti.remote_aliases = get_aliases_for_address(wti.remote_addresses[0]); - } +void wallet2::scan_tx_pool(bool& has_related_alias_in_unconfirmed) +{ + //get transaction pool content + currency::COMMAND_RPC_GET_TX_POOL::request req = AUTO_VAL_INIT(req); + currency::COMMAND_RPC_GET_TX_POOL::response res = AUTO_VAL_INIT(res); + bool r = m_core_proxy->call_COMMAND_RPC_GET_TX_POOL(req, res); + if (res.status == API_RETURN_CODE_BUSY) + throw error::daemon_busy(LOCATION_STR, "get_tx_pool"); + if (!r) + throw error::no_connection_to_daemon(LOCATION_STR, "get_tx_pool"); + THROW_IF_TRUE_WALLET_EX(res.status != API_RETURN_CODE_OK, error::get_blocks_error, res.status); - if (trs.size() >= count) - { - return false; - } - return true; - }; - if (start_from_end) - enum_container(m_transfer_history.rbegin() + offset, m_transfer_history.rend(), cb); - else - enum_container(m_transfer_history.begin() + offset, m_transfer_history.end(), cb); + //- @#@ ----- debug +#ifdef _DEBUG + std::stringstream ss; + ss << "TXS FROM POOL: " << ENDL; + for (const auto& tx_blob : res.txs) + { + currency::transaction tx; + bool r = parse_and_validate_tx_from_blob(tx_blob, tx); + THROW_IF_TRUE_WALLET_EX(!r, error::tx_parse_error, tx_blob); + crypto::hash tx_hash = currency::get_transaction_hash(tx); - total = m_transfer_history.size(); + ss << tx_hash << ENDL; + } + ss << "UNCONFIRMED TXS: " << ENDL; + for (const auto& tx_it : m_unconfirmed_in_transfers) + { + ss << tx_it.first << ENDL; + } + std::string config_tx = ss.str(); +#endif + //- @#@ ----- debug + std::unordered_map unconfirmed_in_transfers_local(std::move(m_unconfirmed_in_transfers)); + multisig_entries_map unconfirmed_multisig_transfers_from_tx_pool; + + has_related_alias_in_unconfirmed = false; + uint64_t tx_expiration_ts_median = res.tx_expiration_ts_median; //get_tx_expiration_median(); + for (const auto& tx_blob : res.txs) + { + currency::transaction tx; + //money_transfer2_details td; + process_transaction_context ptc(tx); + ptc.tx_expiration_ts_median = tx_expiration_ts_median; + ptc.pmultisig_entries = &unconfirmed_multisig_transfers_from_tx_pool; + bool r = parse_and_validate_tx_from_blob(tx_blob, tx); + THROW_IF_TRUE_WALLET_EX(!r, error::tx_parse_error, tx_blob); + has_related_alias_in_unconfirmed |= has_related_alias_entry_unconfirmed(tx); + + //crypto::hash tx_hash = currency::get_transaction_hash(tx); + auto it = unconfirmed_in_transfers_local.find(ptc.tx_hash()); + if (it != unconfirmed_in_transfers_local.end()) + { + m_unconfirmed_in_transfers.insert(*it); + continue; + } + + handle_unconfirmed_tx(ptc); } - void wallet2::wti_to_csv_entry(std::ostream& ss, const wallet_public::wallet_transfer_info& wti, size_t index) + // Compare unconfirmed multisigs containers + // IF EXISTS IN unconfirmed_multisig_transfers_in_tx_pool AND EXISTS IN m_unconfirmed_multisig_transfers => already processed, do nothing + // IF EXISTS IN unconfirmed_multisig_transfers_in_tx_pool AND NOT EXISTS IN m_unconfirmed_multisig_transfers => new unconfirmed, add to m_, mark as spent and nofity (see code above) + // IF NOT EXISTS IN unconfirmed_multisig_transfers_in_tx_pool AND EXISTS IN m_unconfirmed_multisig_transfers => EITHER became confirmed (added to the blockchain) OR removed from the pool for some other reason (clear spent flag if there's spent height == 0, means wasn't added to the blockchain) + + std::unordered_set unconfirmed_in_multisig_transfers; + for (auto& el : m_unconfirmed_in_transfers) + for (auto& in : el.second.vin) + if (in.type() == typeid(txin_multisig)) + unconfirmed_in_multisig_transfers.insert(boost::get(in).multisig_out_id); + + for (auto& multisig_id : m_unconfirmed_multisig_transfers) { - for (auto& subtr : wti.subtransfers) + if (unconfirmed_multisig_transfers_from_tx_pool.count(multisig_id) != 0) + continue; + + if (unconfirmed_in_multisig_transfers.count(multisig_id) != 0) + continue; + + // Process unconfirmed tx dissapeared from the pool + auto it = m_multisig_transfers.find(multisig_id); + if (it != m_multisig_transfers.end() && it->second.m_flags & WALLET_TRANSFER_DETAIL_FLAG_SPENT) { - ss << index << ","; - ss << epee::misc_utils::get_time_str(wti.timestamp) << ","; - ss << print_money(subtr.amount) << ","; - ss << subtr.asset_id << ","; - ss << "\"" << wti.comment << "\","; - ss << "["; - std::copy(wti.remote_addresses.begin(), wti.remote_addresses.end(), std::ostream_iterator(ss, " ")); - ss << "]" << ","; - ss << wti.tx_hash << ","; - ss << wti.height << ","; - ss << wti.unlock_time << ","; - ss << wti.tx_blob_size << ","; - ss << epee::string_tools::buff_to_hex_nodelimer(wti.payment_id) << ","; - ss << "["; - std::copy(wti.remote_aliases.begin(), wti.remote_aliases.end(), std::ostream_iterator(ss, " ")); - ss << "]" << ","; - ss << (subtr.is_income ? "in" : "out") << ","; - ss << (wti.is_service ? "[SERVICE]" : "") << (wti.is_mixing ? "[MIXINS]" : "") << (wti.is_mining ? "[MINING]" : "") << ","; - ss << wti.tx_type << ","; - ss << print_money(wti.fee) << ENDL; + if (it->second.m_spent_height == 0) + { + // Looks like this tx didn't get into the blockchain, just removed from the pool for some reason. + // So, clear spent flag. + uint32_t flags_before = it->second.m_flags; + it->second.m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_SPENT); + WLT_LOG_L0("Unconfirmed multisig out with id: " << multisig_id << " was presiously marked as spent and now seems to be removed from the pool, while still not added to the blockchain. Marked as NOT SPENT" << ENDL + << "ms source tx: " << (it->second.m_ptx_wallet_info != nullptr ? get_transaction_hash(it->second.m_ptx_wallet_info->m_tx) : null_hash) << " flags: " << flags_before << " -> " << it->second.m_flags); + } } - - }; - - void wallet2::wti_to_txt_line(std::ostream& ss, const wallet_public::wallet_transfer_info& wti, size_t index) - { - for (auto& subtr : wti.subtransfers) - { - ss << (subtr.is_income ? "[INC]" : "[OUT]") << "\t" - << epee::misc_utils::get_time_str(wti.timestamp) << "\t" - << print_money(subtr.amount) << "\t" - << subtr.asset_id << "\t" - << print_money(wti.fee) << "\t" - << wti.remote_addresses << "\t" - << wti.comment << ENDL; - } - }; - - void wallet2::wti_to_json_line(std::ostream& ss, const wallet_public::wallet_transfer_info& wti, size_t index) - { - ss << epee::serialization::store_t_to_json(wti, 4) << ","; - }; - - //---------------------------------------------------------------------------------------------------- - void wallet2::set_connectivity_options(unsigned int timeout) - { - m_core_proxy->set_connectivity(timeout, WALLET_RCP_COUNT_ATTEMNTS); } - //---------------------------------------------------------------------------------------------------- - void wallet2::export_transaction_history(std::ostream& ss, const std::string& format, bool include_pos_transactions) - { - //typedef int(*t_somefunc)(int, int); - typedef void(*playout_cb_type)(std::ostream&, const wallet_public::wallet_transfer_info&, size_t); - playout_cb_type cb_csv = &wallet2::wti_to_csv_entry; - playout_cb_type cb_json = &wallet2::wti_to_json_line; - playout_cb_type cb_plain_text = &wallet2::wti_to_txt_line; - playout_cb_type cb = cb_csv; - if (format == "json") + // Populate updated unconfirmed list of multisign transfers + m_unconfirmed_multisig_transfers.clear(); + for (auto& p : unconfirmed_multisig_transfers_from_tx_pool) + m_unconfirmed_multisig_transfers.insert(p.first); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::on_idle() +{ + scan_not_compliant_unconfirmed_txs(); + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::scan_not_compliant_unconfirmed_txs() +{ + uint64_t tx_expiration_ts_median = get_tx_expiration_median(); + uint64_t time_limit = m_core_runtime_config.get_core_time() - CURRENCY_MEMPOOL_TX_LIVETIME; + for (auto it = m_unconfirmed_txs.begin(); it != m_unconfirmed_txs.end(); ) + { + bool remove_this_tx = false; + std::stringstream reason_ss; + if (it->second.timestamp < time_limit) { - ss << "{ \"history\": ["; - cb = cb_json; + remove_this_tx = true; + reason_ss << "outdated, "; } - else if (format == "text") + if (is_tx_expired(it->second.tx, tx_expiration_ts_median)) { - cb = cb_plain_text; + remove_this_tx = true; + reason_ss << "expired, "; + } + if (is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM) && it->second.tx.version < TRANSACTION_VERSION_POST_HF4) + { + remove_this_tx = true; + reason_ss << "not compliant with HF4, "; + } + + if (remove_this_tx) + { + WLT_LOG_BLUE("removing unconfirmed tx " << it->second.tx_hash << ", reason: " << reason_ss.str() << "tx_expiration_ts_median=" << tx_expiration_ts_median, LOG_LEVEL_0); + //lookup all used transfer and update flags + for (auto i : it->second.selected_indicies) + { + //if (i >= m_transfers.size()) + //{ + // WLT_LOG_ERROR("Wrong index '" << i << "' in 'selected_indicies', while m_transfers.size() = " << m_transfers.size()); + // continue; + //} + if (!m_transfers.at(i).m_spent_height) + { + uint32_t flags_before = m_transfers.at(i).m_flags; + m_transfers.at(i).m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_SPENT); // TODO: consider removing other blocking flags (e.g. for escrow tx) -- sowle + WLT_LOG_BLUE("mark transfer #" << i << " as unspent, flags: " << flags_before << " -> " << m_transfers.at(i).m_flags << ", reason: removing unconfirmed tx " << it->second.tx_hash, LOG_LEVEL_0); + } + } + //fire some event + m_wcallback->on_transfer_canceled(it->second); + m_unconfirmed_txs.erase(it++); } else { - //csv by default - ss << "N, Date, Amount, AssetID, Comment, Address, ID, Height, Unlock timestamp, Tx size, Alias, PaymentID, In/Out, Flags, Type, Fee" << ENDL; + it++; } + } + + //scan marked as spent but don't have height (unconfirmed, and actually not unconfirmed) + std::unordered_set ki_in_unconfirmed; + for (auto it = m_unconfirmed_txs.begin(); it != m_unconfirmed_txs.end(); it++) + { + if (!it->second.has_outgoing_entries()) + continue; + + for (auto& in_v : it->second.tx.vin) + { + crypto::key_image ki{}; + if (get_key_image_from_txin_v(in_v, ki)) + ki_in_unconfirmed.insert(ki); + } + } - enum_container(m_transfer_history.begin(), m_transfer_history.end(), [&](wallet_public::wallet_transfer_info& wti, size_t index) { - if (!include_pos_transactions) + for (auto& tr : m_transfers) + { + uint64_t i = tr.first; + auto& t = tr.second; + + if (t.m_flags & WALLET_TRANSFER_DETAIL_FLAG_SPENT && !t.m_spent_height && !static_cast(t.m_flags & WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION) + && !t.is_htlc()) + { + //check if there is unconfirmed for this transfer is no longer exist? + if (!ki_in_unconfirmed.count((t.m_key_image))) { - if (currency::is_coinbase(wti.tx)) - return true; - } - wti.fee = currency::get_tx_fee(wti.tx); - cb(ss, wti, index); - return true; - }); - - if (format == "json") - { - ss << "{}]}"; - } - - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::get_transfer_address(const std::string& adr_str, currency::account_public_address& addr, std::string& payment_id) - { - return m_core_proxy->get_transfer_address(adr_str, addr, payment_id); - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::is_transfer_okay_for_pos(const transfer_details& tr, bool is_zarcanum_hf, uint64_t& stake_unlock_time) const - { - if (is_zarcanum_hf) - { - if (!tr.is_zc()) - return false; - - if (!tr.is_native_coin()) - return false; - } - - if (!tr.is_spendable()) - return false; - - //blockchain conditions - if (!is_transfer_unlocked(tr, true, stake_unlock_time)) - return false; - - //prevent staking of after-last-pow-coins - if (get_blockchain_current_size() - tr.m_ptx_wallet_info->m_block_height <= m_core_runtime_config.min_coinstake_age) - return false; - - if (tr.m_ptx_wallet_info->m_block_height > m_last_pow_block_h) - return false; - - return true; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::get_mining_history(wallet_public::mining_history& hist, uint64_t timestamp_from) - { - for (auto& tr : m_transfer_history) - { - if (currency::is_coinbase(tr.tx) && tr.tx.vin.size() == 2 && tr.timestamp > timestamp_from) - { - tools::wallet_public::mining_history_entry mhe = AUTO_VAL_INIT(mhe); - mhe.a = tr.get_native_income_amount(); - mhe.t = tr.timestamp; - mhe.h = tr.height; - hist.mined_entries.push_back(mhe); + uint32_t flags_before = t.m_flags; + t.m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_SPENT); + WLT_LOG_BLUE("Transfer [" << i << "] marked as unspent, flags: " << flags_before << " -> " << t.m_flags << ", reason: there is no unconfirmed tx relataed to this key image", LOG_LEVEL_0); } } } - //---------------------------------------------------------------------------------------------------- - size_t wallet2::get_pos_entries_count() - { - bool is_zarcanum_hf = is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM); - size_t counter = 0; - for (const auto& tr : m_transfers) + return true; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::refresh(size_t& blocks_fetched, bool& received_money, std::atomic& stop) +{ + load_whitelisted_tokens_if_not_loaded(); + + bool had_full_reset = false; + received_money = false; + blocks_fetched = 0; + size_t added_blocks = 0; + size_t try_count = 0; + size_t reset_count = 0; + crypto::hash last_tx_hash_id = m_transfers.size() ? get_transaction_hash((--m_transfers.end())->second.m_ptx_wallet_info->m_tx) : null_hash; + m_height_of_start_sync = get_blockchain_current_size(); + m_last_sync_percent = 0; + while (!stop.load(std::memory_order_relaxed)) + { + try { - auto& td = tr.second; - - uint64_t stake_unlock_time = 0; - if (!is_transfer_okay_for_pos(td, is_zarcanum_hf, stake_unlock_time)) - continue; - - ++counter; - } - - return counter; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::get_pos_entries(std::vector& entries) - { - bool is_zarcanum_hf = is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM); - for (const auto& td : m_transfers) - { - uint64_t i = td.first; - auto& tr = td.second; - - uint64_t stake_unlock_time = 0; - if (!is_transfer_okay_for_pos(tr, is_zarcanum_hf, stake_unlock_time)) - continue; - - pos_entry pe = AUTO_VAL_INIT(pe); - pe.amount = tr.amount(); - pe.g_index = tr.m_global_output_index; - pe.keyimage = tr.m_key_image; - pe.wallet_index = i; - pe.stake_unlock_time = stake_unlock_time; - pe.block_timestamp = tr.m_ptx_wallet_info->m_block_timestamp; - entries.push_back(pe); - } - - return true; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::is_in_hardfork_zone(uint64_t hardfork_index) const - { - return m_core_runtime_config.is_hardfork_active_for_height(hardfork_index, get_blockchain_current_size()); - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::proxy_to_daemon(const std::string& uri, const std::string& body, int& response_code, std::string& response_body) - { - return m_core_proxy->call_COMMAND_RPC_INVOKE(uri, body, response_code, response_body); - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::prepare_and_sign_pos_block(const mining_context& cxt, uint64_t full_block_reward, const currency::pos_entry& pe, currency::tx_generation_context& miner_tx_tgc, currency::block& b) const - { - bool r = false; - //WLT_CHECK_AND_ASSERT_MES(pe.wallet_index < m_transfers.size(), false, "invalid pe.wallet_index: " << pe.wallet_index); - const transfer_details& td = m_transfers.at(pe.wallet_index); - const transaction& source_tx = td.m_ptx_wallet_info->m_tx; - const crypto::public_key source_tx_pub_key = get_tx_pub_key_from_extra(source_tx); - WLT_CHECK_AND_ASSERT_MES(pe.tx_out_index < source_tx.vout.size(), false, "invalid pe.tx_out_index: " << pe.tx_out_index); - const currency::tx_out_v& stake_out_v = source_tx.vout[pe.tx_out_index]; - - // calculate stake_out_derivation and secret_x (derived ephemeral secret key) - crypto::key_derivation stake_out_derivation = AUTO_VAL_INIT(stake_out_derivation); - r = crypto::generate_key_derivation(source_tx_pub_key, m_account.get_keys().view_secret_key, stake_out_derivation); // d = 8 * v * R - WLT_CHECK_AND_ASSERT_MES(r, false, "generate_key_derivation failed, tid: " << pe.wallet_index << ", pe.tx_id: " << pe.tx_id); - crypto::secret_key secret_x = AUTO_VAL_INIT(secret_x); - crypto::derive_secret_key(stake_out_derivation, pe.tx_out_index, m_account.get_keys().spend_secret_key, secret_x); // x = Hs(8 * v * R, i) + s - - if (!cxt.zarcanum) - { - // old PoS with non-hidden amounts - WLT_CHECK_AND_ASSERT_MES(b.miner_tx.vin[0].type() == typeid(currency::txin_gen), false, "Wrong input 0 type in transaction: " << b.miner_tx.vin[0].type().name()); - WLT_CHECK_AND_ASSERT_MES(b.miner_tx.vin[1].type() == typeid(currency::txin_to_key), false, "Wrong input 1 type in transaction: " << b.miner_tx.vin[1].type().name()); - WLT_CHECK_AND_ASSERT_MES(b.miner_tx.signatures.size() == 1 && b.miner_tx.signatures[0].type() == typeid(NLSAG_sig), false, "wrong sig prepared in a PoS block"); - WLT_CHECK_AND_ASSERT_MES(stake_out_v.type() == typeid(tx_out_bare), false, "unexpected stake output type: " << stake_out_v.type().name() << ", expected: tx_out_bare"); - const tx_out_bare& stake_out = boost::get(stake_out_v); - WLT_CHECK_AND_ASSERT_MES(stake_out.target.type() == typeid(txout_to_key), false, "unexpected stake output target type: " << stake_out.target.type().name() << ", expected: txout_to_key"); - - NLSAG_sig& sig = boost::get(b.miner_tx.signatures[0]); - txin_to_key& stake_input = boost::get(b.miner_tx.vin[1]); - const txout_to_key& stake_out_target = boost::get(stake_out.target); - - // partially fill stake input - stake_input.k_image = pe.keyimage; - stake_input.amount = pe.amount; - - // get decoys outputs and construct miner tx - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response decoys_resp = AUTO_VAL_INIT(decoys_resp); - std::vector ring; - uint64_t secret_index = 0; // index of the real stake output - if (m_required_decoys_count > 0 && !is_auditable()) + bool full_reset_needed = false; + pull_blocks(added_blocks, stop, full_reset_needed); + if (full_reset_needed) { - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request decoys_req = AUTO_VAL_INIT(decoys_req); - decoys_req.height_upper_limit = std::min(m_last_pow_block_h, m_last_known_daemon_height > m_core_runtime_config.min_coinstake_age ? m_last_known_daemon_height - m_core_runtime_config.min_coinstake_age : m_last_pow_block_h); - decoys_req.use_forced_mix_outs = false; - decoys_req.decoys_count = m_required_decoys_count + 1; // one more to be able to skip a decoy in case it hits the real output - decoys_req.amounts.push_back(pe.amount); // request one batch of decoys - - r = m_core_proxy->call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS(decoys_req, decoys_resp); - // TODO @#@# do we need these exceptions? - THROW_IF_FALSE_WALLET_EX(r, error::no_connection_to_daemon, "getrandom_outs1.bin"); - THROW_IF_FALSE_WALLET_EX(decoys_resp.status != API_RETURN_CODE_BUSY, error::daemon_busy, "getrandom_outs1.bin"); - THROW_IF_FALSE_WALLET_EX(decoys_resp.status == API_RETURN_CODE_OK, error::get_random_outs_error, decoys_resp.status); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(decoys_resp.outs.size() == 1, "got wrong number of decoys batches: " << decoys_resp.outs.size()); - - // we expect that less decoys can be returned than requested, we will use them all anyway - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(decoys_resp.outs[0].outs.size() <= m_required_decoys_count + 1, "for PoS stake tx got greater decoys to mix than requested: " << decoys_resp.outs[0].outs.size() << " < " << m_required_decoys_count + 1); - - auto& ring_candidates = decoys_resp.outs[0].outs; - ring_candidates.emplace_front(td.m_global_output_index, stake_out_target.key); - - std::unordered_set used_gindices; - size_t good_outs_count = 0; - for (auto it = ring_candidates.begin(); it != ring_candidates.end(); ) + if (reset_count > 1) { - if (used_gindices.count(it->global_amount_index) != 0) - { - it = ring_candidates.erase(it); - continue; - } - used_gindices.insert(it->global_amount_index); - if (++good_outs_count == m_required_decoys_count + 1) - { - ring_candidates.erase(++it, ring_candidates.end()); - break; - } - ++it; + WLT_LOG_L0("Intenral error: reset_count infinit loop catch"); + if (m_wcallback) + m_wcallback->on_message(tools::i_wallet2_callback::ms_red, "Internal error: reset_count infinite loop catch"); + return; + } + reset_count++; + m_height_of_start_sync = 0; + had_full_reset = true; + continue; + } + blocks_fetched += added_blocks; + if (!added_blocks) + break; + } + catch (error::no_connection_to_daemon&) + { + blocks_fetched += added_blocks; + if (++try_count > 3) + return; + WLT_LOG_L2("no connection to the daemon, wait and try pull_blocks again (try_count: " << try_count << ", blocks_fetched: " << blocks_fetched << ")"); + if (m_wcallback) + m_wcallback->on_message(tools::i_wallet2_callback::ms_red, "no connection to daemon"); + std::this_thread::sleep_for(std::chrono::seconds(3)); + } + catch (const std::exception& e) + { + blocks_fetched += added_blocks; + WLT_LOG_ERROR("refresh->pull_blocks failed, try_count: " << try_count << ", blocks_fetched: " << blocks_fetched << ", exception: " << e.what()); + if (m_wcallback) + m_wcallback->on_message(tools::i_wallet2_callback::ms_red, std::string("error on pulling blocks: ") + e.what()); + return; + } + } + + if (last_tx_hash_id != (m_transfers.size() ? get_transaction_hash((--m_transfers.end())->second.m_ptx_wallet_info->m_tx) : null_hash)) + received_money = true; + + if (blocks_fetched) + { + on_idle(); + + uint64_t tx_expiration_ts_median = get_tx_expiration_median(); + handle_expiration_list(tx_expiration_ts_median); + handle_contract_expirations(tx_expiration_ts_median); + m_found_free_amounts.clear(); + truncate_wallet(); + } + if (had_full_reset) + { + blocks_fetched = get_blockchain_current_size() - m_full_resync_requested_at_h; + m_full_resync_requested_at_h = 0; + } + + + WLT_LOG("Refresh done, blocks received: " << blocks_fetched, blocks_fetched > 0 ? LOG_LEVEL_1 : LOG_LEVEL_2); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::handle_expiration_list(uint64_t tx_expiration_ts_median) +{ + for (auto it = m_money_expirations.begin(); it != m_money_expirations.end(); ) + { + if (it->expiration_time < TX_EXPIRATION_MEDIAN_SHIFT || tx_expiration_ts_median > it->expiration_time - TX_EXPIRATION_MEDIAN_SHIFT) + { + for (auto tr_ind : it->selected_transfers) + { + auto& transfer = m_transfers.at(tr_ind); + if (!transfer.m_spent_height) + { + // Clear WALLET_TRANSFER_DETAIL_FLAG_BLOCKED and WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION flags only. + // Note: transfer may still be marked as spent + uint32_t flags_before = transfer.m_flags; + transfer.m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_BLOCKED); + transfer.m_flags &= ~(WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION); + WLT_LOG_GREEN("Unlocked money from expiration_list: transfer #" << tr_ind << ", flags: " << flags_before << " -> " << transfer.m_flags << ", amount: " << print_money(transfer.amount()) << ", tx: " << + (transfer.m_ptx_wallet_info != nullptr ? get_transaction_hash(transfer.m_ptx_wallet_info->m_tx) : null_hash), LOG_LEVEL_0); } - // won't assert that ring_candidates.size() == m_required_decoys_count + 1 here as we will use all the decoys anyway - if (ring_candidates.size() < m_required_decoys_count + 1) - LOG_PRINT_YELLOW("PoS: using " << ring_candidates.size() - 1 << " decoys for mining tx, while " << m_required_decoys_count << " are required", LOG_LEVEL_1); + } + WLT_LOG_GREEN("expiration_list entry removed by median: " << tx_expiration_ts_median << ", expiration time: " << it->expiration_time << ", related tx: " << it->related_tx_id, LOG_LEVEL_0); + it = m_money_expirations.erase(it); + } + else + { + it++; + } + } + return true; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::handle_contract_expirations(uint64_t tx_expiration_ts_median) +{ + for (auto& contract : m_contracts) + { + switch (contract.second.state) + { + case tools::wallet_public::escrow_contract_details_basic::contract_cancel_proposal_sent: + if (is_tx_expired(contract.second.cancel_body.tx_cancel_template, tx_expiration_ts_median)) + change_contract_state(contract.second, tools::wallet_public::escrow_contract_details_basic::contract_accepted, contract.first, "cancel proposal expiration"); + break; + case tools::wallet_public::escrow_contract_details_basic::contract_released_cancelled: + if (contract.second.height == 0 && is_tx_expired(contract.second.cancel_body.tx_cancel_template, tx_expiration_ts_median)) + change_contract_state(contract.second, tools::wallet_public::escrow_contract_details_basic::contract_accepted, contract.first, "cancel acceptance expiration"); + break; + } + } +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::refresh(size_t& blocks_fetched, bool& received_money, bool& ok, std::atomic& stop) +{ + try + { + refresh(blocks_fetched, received_money, stop); + ok = true; + } + catch (...) + { + ok = false; + } + return ok; +} - ring_candidates.sort([](auto& l, auto& r) { return l.global_amount_index < r.global_amount_index; }); // sort them now (note absolute_sorted_output_offsets_to_relative_in_place() below) +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::detach_from_block_ids(uint64_t including_height) +{ + //calculate number of erased blocks + uint64_t blocks_detached = get_blockchain_current_size() - including_height; + //id at height should be kept, the rest - erased + m_chain.detach(including_height); + return blocks_detached; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::remove_transfer_from_amount_gindex_map(uint64_t tid) +{ + for (auto it = m_amount_gindex_to_transfer_id.begin(); it != m_amount_gindex_to_transfer_id.end(); ) + { + if (it->second == tid) + it = m_amount_gindex_to_transfer_id.erase(it); + else + ++it; + } +} +//---------------------------------------------------------------------------------------------------- +void wallet2::detach_blockchain(uint64_t including_height) +{ + WLT_LOG_L0("Detaching blockchain on height " << including_height); + size_t transfers_detached = 0; - uint64_t i = 0; - for (auto& el : ring_candidates) + // rollback incoming transfers from detaching subchain + { + auto it_start = std::find_if(m_transfers.begin(), m_transfers.end(), [&](const transfer_container::value_type& tr_e){return tr_e.second.m_ptx_wallet_info->m_block_height >= including_height; }); + if (it_start != m_transfers.end()) + { + + for (auto it = it_start; it!= m_transfers.end(); it++) + { + uint64_t i = it->first; + //check for htlc + if (it->second.m_ptx_wallet_info->m_tx.vout[it->second.m_internal_output_index].type() == typeid(tx_out_bare) && + boost::get(it->second.m_ptx_wallet_info->m_tx.vout[it->second.m_internal_output_index]).target.type() == typeid(txout_htlc)) { - uint64_t gindex = el.global_amount_index; - if (gindex == td.m_global_output_index) - secret_index = i; - ++i; - ring.emplace_back(&el.stealth_address); - stake_input.key_offsets.push_back(el.global_amount_index); + //need to find an entry in m_htlc and remove it + const txout_htlc& hltc = boost::get(boost::get(it->second.m_ptx_wallet_info->m_tx.vout[it->second.m_internal_output_index]).target); + uint64_t expiration_height = it->second.m_ptx_wallet_info->m_block_height + hltc.expiration; + auto pair_of_it = m_htlcs.equal_range(expiration_height); + bool found = false; + for (auto it = pair_of_it.first; it != pair_of_it.second; it++) + { + if (it->second.transfer_index == i) + { + found = true; + m_htlcs.erase(it); + break; + } + } + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(found, "Internal error: not found record in m_htlcs during rollback"); } - r = absolute_sorted_output_offsets_to_relative_in_place(stake_input.key_offsets); - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "absolute_sorted_output_offsets_to_relative_in_place failed"); + + + if (!(it->second.m_key_image == null_ki && is_watch_only())) + { + auto it_ki = m_key_images.find(it->second.m_key_image); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it_ki != m_key_images.end(), "key image " << it->second.m_key_image << " not found"); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it->second.m_ptx_wallet_info->m_block_height >= including_height, "transfer #" << i << " block height is less than " << including_height); + m_key_images.erase(it_ki); + } + remove_transfer_from_amount_gindex_map(i); + ++transfers_detached; + } + m_transfers.erase(it_start, m_transfers.end()); + } + } + + for (uint64_t i = get_top_block_height(); i != including_height - 1 && i != 0; i--) + { + unprocess_htlc_triggers_on_block_removed(i); + } + size_t blocks_detached = detach_from_block_ids(including_height); + + //rollback spends + // do not clear spent flag in spent transfers as corresponding txs are most likely in the pool + // they will be moved into m_unconfirmed_txs for clearing in future (if tx will expire of removed from pool) + for (auto& tr_ : m_transfers) + { + uint64_t i = tr_.first; + auto& tr = tr_.second; + if (tr.m_spent_height >= including_height) + { + WLT_LOG_BLUE("Transfer [" << i << "] spent height: " << tr.m_spent_height << " -> 0, reason: detaching blockchain", LOG_LEVEL_1); + tr.m_spent_height = 0; + //check if it's hltc contract + } + } + + //rollback tranfers history + auto tr_hist_it = m_transfer_history.rend(); + for (auto it = m_transfer_history.rbegin(); it != m_transfer_history.rend(); it++) + { + if (it->height < including_height) + break; + tr_hist_it = it; // note that tr_hist_it->height >= height + } + + if (tr_hist_it != m_transfer_history.rend()) + { + auto it_from = --tr_hist_it.base(); + // before removing wti from m_transfer_history put it into m_unconfirmed_txs as txs from detached blocks are most likely be moved into the pool + for (auto it = it_from; it != m_transfer_history.end(); ++it) + { + // skip coinbase txs as they are not expected to go into the pool + if (is_coinbase(it->tx)) + { + continue; + } + + if (!m_unconfirmed_txs.insert(std::make_pair(it->tx_hash, *it)).second) + { + WLT_LOG_ERROR("can't move wti from transfer history to unronfirmed txs because such it is already here, tx hash: " << it->tx_hash); + } + } + m_transfer_history.erase(it_from, m_transfer_history.end()); + } + + //rollback payments + for (auto it = m_payments.begin(); it != m_payments.end(); ) + { + if (including_height <= it->second.m_block_height) + it = m_payments.erase(it); + else + ++it; + } + + //detach in m_last_zc_global_indexs + while (m_last_zc_global_indexs.size() && including_height <= m_last_zc_global_indexs.begin()->first) + { + m_last_zc_global_indexs.erase(m_last_zc_global_indexs.begin()); + } + + //asset descriptors + handle_rollback_events(including_height); + + WLT_LOG_L0("Detached blockchain on height " << including_height << ", transfers detached " << transfers_detached << ", blocks detached " << blocks_detached); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::operator()(const asset_register_event& e) +{ + auto it = m_own_asset_descriptors.find(e.asset_id); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it != m_own_asset_descriptors.end(), "asset_id " << e.asset_id << "not found during rolling asset_register_event"); + m_own_asset_descriptors.erase(it); +} +void wallet2::operator()(const asset_update_event& e) +{ + auto it = m_own_asset_descriptors.find(e.asset_id); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it != m_own_asset_descriptors.end(), "asset_id " << e.asset_id << "not found during rolling asset_update_event"); + it->second = e.own_context; +} +void wallet2::operator()(const asset_unown_event& e) +{ + auto it = m_own_asset_descriptors.find(e.asset_id); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it == m_own_asset_descriptors.end(), "asset_id " << e.asset_id << "unexpectedly found during rolling asset_unown_event"); + m_own_asset_descriptors[e.asset_id] = e.own_context; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::handle_rollback_events(uint64_t including_height) +{ + while (m_rollback_events.size() && m_rollback_events.back().first >= including_height) + { + boost::apply_visitor(*this, m_rollback_events.back().second); + m_rollback_events.pop_back(); + } +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::deinit() +{ + m_wcallback.reset(); + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::clear() +{ + reset_all(); + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::reset_all() +{ + //static_cast(*this) = wallet2_base_state{}; + static_cast(*this).~wallet2_base_state(); + new (static_cast(this)) wallet2_base_state(); + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::store_keys(std::string& buff, const std::string& password, wallet2::keys_file_data& keys_file_data, 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(acc, account_data); + WLT_CHECK_AND_ASSERT_MES(r, false, "failed to serialize wallet keys"); + + crypto::chacha8_key key; + crypto::generate_chacha8_key(password, key); + std::string cipher; + cipher.resize(account_data.size()); + keys_file_data.iv = crypto::rand(); + crypto::chacha8(account_data.data(), account_data.size(), key, keys_file_data.iv, &cipher[0]); + keys_file_data.account_data = cipher; + + r = ::serialization::dump_binary(keys_file_data, buff); + + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::backup_keys(const std::string& path) +{ + std::string buff; + wallet2::keys_file_data keys_file_data = AUTO_VAL_INIT(keys_file_data); + bool r = store_keys(buff, m_password, keys_file_data); + WLT_CHECK_AND_ASSERT_MES(r, false, "Failed to store keys"); + + r = file_io_utils::save_string_to_file(path, buff); + WLT_CHECK_AND_ASSERT_MES(r, false, "Failed to save_string_to_file at store keys"); + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::reset_password(const std::string& pass) +{ + m_password = pass; + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::is_password_valid(const std::string& pass) +{ + return pass == m_password; +} +//---------------------------------------------------------------------------------------------------- +namespace +{ + bool verify_keys(const crypto::secret_key& sec, const crypto::public_key& expected_pub) + { + crypto::public_key pub; + bool r = crypto::secret_key_to_public_key(sec, pub); + return r && expected_pub == pub; + } +} +//---------------------------------------------------------------------------------------------------- +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_T(out_key_to_ki); + 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, "UNRECOVERABLE ERROR, wallet stops: m_pending_key_images > m_pending_key_images_file_container" << ENDL << "Missing/wrong " << string_encoding::convert_to_ansii(m_pending_ki_file) << " file?"); + } +} +//---------------------------------------------------------------------------------------------------- +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, uint64_t file_signature, keys_file_data& kf_data) +{ + bool r = false; + if (file_signature == WALLET_FILE_SIGNATURE_OLD) + { + wallet2::keys_file_data_old kf_data_old; + r = ::serialization::parse_binary(buff, kf_data_old); + kf_data = wallet2::keys_file_data::from_old(kf_data_old); + } + else if (file_signature == WALLET_FILE_SIGNATURE_V2) + { + r = ::serialization::parse_binary(buff, kf_data); + } + THROW_IF_TRUE_WALLET_EX(!r, error::wallet_internal_error, "internal error: failed to deserialize"); + + crypto::chacha8_key key; + crypto::generate_chacha8_key(password, key); + std::string account_data; + account_data.resize(kf_data.account_data.size()); + crypto::chacha8(kf_data.account_data.data(), kf_data.account_data.size(), key, kf_data.iv, &account_data[0]); + + 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.view_secret_key, keys.account_address.view_public_key); + if (keys.spend_secret_key == currency::null_skey) + m_watch_only = true; + else + r = r && verify_keys(keys.spend_secret_key, keys.account_address.spend_public_key); + if (!r) + { + WLT_LOG_L0("Wrong password for wallet " << string_encoding::convert_to_ansii(m_wallet_file)); + tools::error::throw_wallet_ex(std::string(__FILE__ ":" STRINGIZE(__LINE__))); + } + init_log_prefix(); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::assign_account(const currency::account_base& acc) +{ + clear(); + m_account = acc; + init_log_prefix(); + if (m_account.is_watch_only()) + m_watch_only = true; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::generate(const std::wstring& path, const std::string& pass, bool auditable_wallet) +{ + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(validate_password(pass), "new wallet generation failed: password contains forbidden characters") + clear(); + prepare_file_names(path); + + m_password = pass; + m_account.generate(auditable_wallet); + 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)); + if (m_watch_only && !auditable_wallet) + { + bool stub; + load_keys2ki(true, stub); + } + store(); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::restore(const std::wstring& path, const std::string& pass, const std::string& seed_or_tracking_seed, bool tracking_wallet, const std::string& seed_password) +{ + bool r = false; + clear(); + prepare_file_names(path); + m_password = pass; + + if (tracking_wallet) + { + r = m_account.restore_from_tracking_seed(seed_or_tracking_seed); + init_log_prefix(); + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "Could not load tracking wallet from a given seed: invalid tracking seed"); + m_watch_only = true; + } + else + { + r = m_account.restore_from_seed_phrase(seed_or_tracking_seed, seed_password); + init_log_prefix(); + THROW_IF_FALSE_WALLET_EX(r, error::wallet_wrong_seed_error, epee::string_encoding::convert_to_ansii(m_wallet_file)); + } + + 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)); + store(); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::check_connection() +{ + return m_core_proxy->check_connection(); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::set_votes_config_path(const std::string& path_to_config_file/* = tools::get_default_data_dir() + "\voting_config.json"*/) +{ + m_votes_config_path = path_to_config_file; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::load_votes_config() +{ + if (boost::filesystem::exists(m_votes_config_path)) + { + epee::serialization::load_t_from_json_file(m_votes_config, m_votes_config_path); + } +} +//---------------------------------------------------------------------------------------------------- +void wallet2::load(const std::wstring& wallet_, const std::string& password) +{ + clear(); + prepare_file_names(wallet_); + + m_password = password; + + std::string keys_buff; + std::string body_buff; + + + boost::system::error_code e; + bool exists = boost::filesystem::exists(m_wallet_file, e); + THROW_IF_TRUE_WALLET_EX(e || !exists, error::file_not_found, epee::string_encoding::convert_to_ansii(m_wallet_file)); + boost::filesystem::ifstream data_file; + data_file.open(m_wallet_file, std::ios_base::binary | std::ios_base::in); + THROW_IF_TRUE_WALLET_EX(data_file.fail(), error::file_read_error, epee::string_encoding::convert_to_ansii(m_wallet_file)); + + wallet_file_binary_header wbh = AUTO_VAL_INIT(wbh); + + data_file.read((char*)&wbh, sizeof(wbh)); + THROW_IF_TRUE_WALLET_EX(data_file.fail(), error::file_read_error, epee::string_encoding::convert_to_ansii(m_wallet_file)); + + THROW_IF_TRUE_WALLET_EX(wbh.m_signature != WALLET_FILE_SIGNATURE_OLD && wbh.m_signature != WALLET_FILE_SIGNATURE_V2, error::file_read_error, epee::string_encoding::convert_to_ansii(m_wallet_file)); + THROW_IF_TRUE_WALLET_EX( + wbh.m_cb_keys > WALLET_FILE_MAX_KEYS_SIZE, error::file_read_error, epee::string_encoding::convert_to_ansii(m_wallet_file)); + + + keys_buff.resize(wbh.m_cb_keys); + data_file.read((char*)keys_buff.data(), wbh.m_cb_keys); + wallet2::keys_file_data kf_data = AUTO_VAL_INIT(kf_data); + load_keys(keys_buff, password, wbh.m_signature, kf_data); + + bool need_to_resync = false; + if (wbh.m_ver == WALLET_FILE_BINARY_HEADER_VERSION_INITAL) + { + // old WALLET_FILE_BINARY_HEADER_VERSION version means no encryption + need_to_resync = !tools::portable_unserialize_obj_from_stream(*this, data_file); + WLT_LOG_L1("Detected format: WALLET_FILE_BINARY_HEADER_VERSION_INITAL (need_to_resync=" << need_to_resync << ")"); + } + else if (wbh.m_ver == WALLET_FILE_BINARY_HEADER_VERSION_2) + { + tools::encrypt_chacha_in_filter decrypt_filter(password, kf_data.iv); + boost::iostreams::filtering_istream in; + in.push(decrypt_filter); + in.push(data_file); + need_to_resync = !tools::portable_unserialize_obj_from_stream(*this, in); + WLT_LOG_L1("Detected format: WALLET_FILE_BINARY_HEADER_VERSION_2 (need_to_resync=" << need_to_resync << ")"); + } + else + { + WLT_LOG_L0("Unknown wallet body version(" << wbh.m_ver << "), resync initiated."); + need_to_resync = true; + } + + + + if (m_watch_only && !is_auditable()) + load_keys2ki(true, need_to_resync); + + boost::system::error_code ec = AUTO_VAL_INIT(ec); + m_current_wallet_file_size = boost::filesystem::file_size(wallet_, ec); + + 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() + << ", file_size: " << m_current_wallet_file_size + << ", blockchain_size: " << m_chain.get_blockchain_current_size() + ); + WLT_LOG_L1("[LOADING]Blockchain shortener state: " << ENDL << m_chain.get_internal_state_text()); + + load_votes_config(); + + WLT_LOG_L1("(after loading: pending_key_images: " << m_pending_key_images.size() << ", pki file elements: " << m_pending_key_images_file_container.size() << ", tx_keys: " << m_tx_keys.size() << ")"); + + if (need_to_resync) + { + reset_history(); + WLT_LOG_L0("Unable to load history data from wallet file, wallet will be resynced!"); + } + + THROW_IF_TRUE_WALLET_EX(need_to_resync, error::wallet_load_notice_wallet_restored, epee::string_encoding::convert_to_ansii(m_wallet_file)); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::store() +{ + store(m_wallet_file, m_password); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::store(const std::wstring& path) +{ + store(path, m_password); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::store(const std::wstring& path_to_save, const std::string& password) +{ + LOG_PRINT_L0("(before storing: pending_key_images: " << m_pending_key_images.size() << ", pki file elements: " << m_pending_key_images_file_container.size() << ", tx_keys: " << m_tx_keys.size() << ")"); + + std::string ascii_path_to_save = epee::string_encoding::convert_to_ansii(path_to_save); + + //prepare data + std::string keys_buff; + wallet2::keys_file_data keys_file_data = AUTO_VAL_INIT(keys_file_data); + bool r = store_keys(keys_buff, password, keys_file_data, m_watch_only); + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "failed to store_keys for wallet " << ascii_path_to_save); + + //store data + wallet_file_binary_header wbh = AUTO_VAL_INIT(wbh); + wbh.m_signature = WALLET_FILE_SIGNATURE_V2; + wbh.m_cb_keys = keys_buff.size(); + //@#@ change it to proper + wbh.m_ver = WALLET_FILE_BINARY_HEADER_VERSION_2; + std::string header_buff((const char*)&wbh, sizeof(wbh)); + + uint64_t ts = m_core_runtime_config.get_core_time(); + + // save to tmp file, then rename + boost::filesystem::path tmp_file_path = boost::filesystem::path(path_to_save); + tmp_file_path += L".newtmp_" + std::to_wstring(ts); + + boost::filesystem::ofstream data_file; + data_file.open(tmp_file_path, std::ios_base::binary | std::ios_base::out | std::ios::trunc); + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(!data_file.fail(), "failed to open binary wallet file for saving: " << tmp_file_path.string()); + data_file << header_buff << keys_buff; + + WLT_LOG_L0("Storing to temporary file " << tmp_file_path.string() << " ..."); + //creating encryption stream + tools::encrypt_chacha_out_filter decrypt_filter(m_password, keys_file_data.iv); + boost::iostreams::filtering_ostream out; + out.push(decrypt_filter); + out.push(data_file); + + r = tools::portble_serialize_obj_to_stream(*this, out); + if (!r) + { + data_file.close(); + boost::filesystem::remove(tmp_file_path); // remove tmp file if smth went wrong + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(false, "IO error while storing wallet to " << tmp_file_path.string() << " (portble_serialize_obj_to_stream failed)"); + } + + data_file.flush(); + data_file.close(); + boost::uintmax_t tmp_file_size = boost::filesystem::file_size(tmp_file_path); + WLT_LOG_L0("Stored successfully to temporary file " << tmp_file_path.string() << ", file size=" << tmp_file_size); + + WLT_LOG_L1("[LOADING]Blockchain shortener state: " << ENDL << m_chain.get_internal_state_text()); + + // for the sake of safety perform a double-renaming: wallet file -> old tmp, new tmp -> wallet file, remove old tmp + + boost::filesystem::path tmp_old_file_path = boost::filesystem::path(path_to_save); + tmp_old_file_path += L".oldtmp_" + std::to_wstring(ts); + + if (boost::filesystem::is_regular_file(path_to_save)) + { + boost::filesystem::rename(path_to_save, tmp_old_file_path); + WLT_LOG_L1("Renamed: " << ascii_path_to_save << " -> " << tmp_old_file_path.string()); + } + + boost::filesystem::rename(tmp_file_path, path_to_save); + WLT_LOG_L1("Renamed: " << tmp_file_path.string() << " -> " << ascii_path_to_save); + + if (boost::filesystem::remove(tmp_old_file_path)) + { + WLT_LOG_L1("Removed temporary file: " << tmp_old_file_path.string()); + } + + bool path_to_save_exists = boost::filesystem::is_regular_file(path_to_save); + bool tmp_file_path_exists = boost::filesystem::is_regular_file(tmp_file_path); + bool tmp_old_file_path_exists = boost::filesystem::is_regular_file(tmp_old_file_path); + + boost::system::error_code ec = AUTO_VAL_INIT(ec); + m_current_wallet_file_size = boost::filesystem::file_size(path_to_save, ec); + if (path_to_save_exists && !tmp_file_path_exists && !tmp_old_file_path_exists) + { + + WLT_LOG_L0("Wallet was successfully stored to " << ascii_path_to_save << ", file size=" << m_current_wallet_file_size + << " blockchain_size: " << m_chain.get_blockchain_current_size()); + } + else + { + WLT_LOG_ERROR("Wallet stroing to " << ascii_path_to_save << " might not be successfull: path_to_save_exists=" << path_to_save_exists << ", tmp_file_path_exists=" << tmp_file_path_exists << ", tmp_old_file_path_exists=" << tmp_old_file_path_exists); + throw tools::error::wallet_common_error(LOCATION_STR, "Wallet file storing might not be successfull. Please make sure you have backed up your seed phrase and check log for details."); + } +} +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::get_wallet_file_size()const +{ + return m_current_wallet_file_size; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::set_use_deffered_global_outputs(bool use) +{ + LOG_PRINT_L0("[DEFFERED_MODE]: " << use); + m_use_deffered_global_outputs = use; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::set_use_assets_whitelisting(bool use) +{ + LOG_PRINT_L0("[ASSET_WHITELISTING_MODE]: " << use); + m_use_assets_whitelisting = use; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::store_watch_only(const std::wstring& path_to_save, const std::string& password) const +{ + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(path_to_save != m_wallet_file, "trying to save watch-only wallet to the same wallet file!"); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(!m_watch_only, "saving watch-only wallet into a watch-only wallet is not allowed"); + + // prepare data for watch-only wallet + wallet2 wo; + // wallet2 wo(*this); copy-constructor is not working, so do a this serialization workaround + std::stringstream stream_buffer; + tools::portble_serialize_obj_to_stream(*this, stream_buffer); + tools::portable_unserialize_obj_from_stream(wo, stream_buffer); + + wo.m_watch_only = true; + wo.m_account = m_account; + wo.m_account.make_account_watch_only(); + wo.prepare_file_names(path_to_save); + + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(!boost::filesystem::exists(wo.m_wallet_file), "file " << epee::string_encoding::convert_to_ansii(wo.m_wallet_file) << " already exists"); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(!boost::filesystem::exists(wo.m_pending_ki_file), "file " << epee::string_encoding::convert_to_ansii(wo.m_pending_ki_file) << " already exists"); + + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(wo.m_pending_key_images.empty(), "pending key images is expected to be empty"); + if (!is_auditable()) + { + bool stub = false; + wo.load_keys2ki(true, stub); // to create outkey2ki file + } + + // populate pending key images for spent outputs (this will help to resync watch-only wallet) + for (const auto& tr : m_transfers) + { + + const auto& td = tr.second; + if (!td.is_spent()) + continue; // only spent transfers really need to be stored, because watch-only wallet will not be able to figure out they were spent otherwise + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.m_internal_output_index < td.m_ptx_wallet_info->m_tx.vout.size(), "invalid transfer #" << tr.first); + if (td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].type() != typeid(tx_out_bare)) + continue; + const currency::txout_target_v& out_t = boost::get(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]).target; + if (out_t.type() != typeid(currency::txout_to_key)) + continue; + const crypto::public_key& out_key = boost::get(out_t).key; + wo.m_pending_key_images.insert(std::make_pair(out_key, td.m_key_image)); + wo.m_pending_key_images_file_container.push_back(tools::out_key_to_ki{ out_key, td.m_key_image }); + WLT_LOG_L1("preparing watch-only wallet: added pending ki (" << out_key << ", " << td.m_key_image << ")"); + } + + // TODO additional clearing for watch-only wallet's data + + wo.store(path_to_save, password); +} +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::unlocked_balance() const +{ + uint64_t stub = 0; + uint64_t unlocked_balance = 0; + balance(unlocked_balance, stub, stub, stub); + return unlocked_balance; +} +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::balance(uint64_t& unloked) const +{ + uint64_t fake = 0; + return balance(unloked, fake, fake, fake); +} +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::balance(uint64_t& unlocked, uint64_t& awaiting_in, uint64_t& awaiting_out, uint64_t& mined, const crypto::public_key& asset_id /* = currency::native_coin_asset_id */) const +{ + uint64_t total = 0; + unlocked = 0; + awaiting_in = 0; + awaiting_out = 0; + mined = 0; + std::unordered_map balances; + balance(balances, mined); + auto it = balances.find(asset_id); + if (it != balances.end()) + { + total = it->second.total; + unlocked = it->second.unlocked; + awaiting_in = it->second.awaiting_in; + awaiting_out = it->second.awaiting_out; + } + return total; +} +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::balance(const crypto::public_key& asset_id, uint64_t& unlocked) const +{ + std::unordered_map balances; + uint64_t dummy; + balance(balances, dummy); + auto it = balances.find(asset_id); + if (it == balances.end()) + { + return 0; + } + unlocked = it->second.unlocked; + return it->second.total; +} +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::balance(const crypto::public_key& asset_id) const +{ + uint64_t dummy = 0; + return balance(asset_id, dummy); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::balance(std::unordered_map& balances, uint64_t& mined) const +{ + mined = 0; + m_has_bare_unspent_outputs = false; + + for (auto& tr : m_transfers) + { + auto& td = tr.second; + + if (td.is_spendable() || (td.is_reserved_for_escrow() && !td.is_spent())) + { + wallet_public::asset_balance_entry_base& e = balances[td.get_asset_id()]; + e.total += td.amount(); + if (is_transfer_unlocked(td)) + e.unlocked += td.amount(); + if (td.m_flags & WALLET_TRANSFER_DETAIL_FLAG_MINED_TRANSFER) + { + if (td.m_ptx_wallet_info->m_block_height == 0) + { + //for genesis block we add actual amounts + mined += td.amount(); + } + else { + mined += CURRENCY_BLOCK_REWARD; //this code would work only for cases where block reward is full. For reduced block rewards might need more flexible code (TODO) + } + } + + if (!td.is_zc()) + m_has_bare_unspent_outputs = true; + } + } + + std::unordered_map subtransfers_by_assets_map; + for (auto& utx : m_unconfirmed_txs) + { + for (auto& subtransfer : utx.second.subtransfers) + { + wallet_public::asset_balance_entry_base& e = balances[subtransfer.asset_id]; + subtransfers_by_assets_map[subtransfer.asset_id] = subtransfer.is_income; + if (subtransfer.is_income) + { + e.total += subtransfer.amount; + e.awaiting_in += subtransfer.amount; } else { - // no decoys, the ring consist of one element -- the real stake output - ring.emplace_back(&stake_out_target.key); - stake_input.key_offsets.push_back(td.m_global_output_index); + e.awaiting_out += subtransfer.amount; + if (subtransfer.asset_id == currency::native_coin_asset_id) + { + // this "if" present here only due to sophisticated checks in escrow_custom_test, which + // inaccuracy might be driven by tangled processing of sent transactions and unconfirmed + // transactions in pre-refactoring era (few weeks before this commit) + if (!(utx.second.contract.size() && utx.second.contract[0].state == wallet_public::escrow_contract_details_basic::contract_released_burned)) + { + e.awaiting_out -= currency::get_tx_fee(utx.second.tx); + } + } } - - // sign block actually in coinbase transaction - crypto::hash block_hash = currency::get_block_hash(b); - - // generate sring signature - sig.s.resize(ring.size()); - crypto::generate_ring_signature(block_hash, stake_input.k_image, ring, secret_x, secret_index, sig.s.data()); - - if (epee::log_space::get_set_log_detalisation_level() >= LOG_LEVEL_4) - { - std::stringstream ss; - ss << "GENERATED RING SIGNATURE for PoS block coinbase:" << ENDL << - " block hash: " << block_hash << ENDL << - " key image: " << stake_input.k_image << ENDL << - " ring:" << ENDL; - for (auto el : ring) - ss << " " << *el << ENDL; - ss << " signature:" << ENDL; - for (auto el : sig.s) - ss << " " << el << ENDL; - WLT_LOG_L4(ss.str()); - } - - return true; } - // Zarcanum + //has outgoing entries for each asset + //if (utx.second.has_outgoing_entries()) + //{ + //collect change to unconfirmed + for (const auto& emp_entry : utx.second.employed_entries.receive) + { + auto it_employed_entry = subtransfers_by_assets_map.find(emp_entry.asset_id); + if (it_employed_entry == subtransfers_by_assets_map.end()) + { + LOG_ERROR("Intenral error, check the wallet code at give location"); + continue; + } + if (!(it_employed_entry->second)) // if is_incoming == false, then we need to check for change and add it to total + { + wallet_public::asset_balance_entry_base& e = balances[emp_entry.asset_id]; + e.total += emp_entry.amount; + } + } + //} - WLT_CHECK_AND_ASSERT_MES(td.is_zc(), false, "the transfer [" << pe.wallet_index << "] is not zc type, which is required for zarcanum"); + } + + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::truncate_wallet() +{ + if (m_concise_mode) + { + std::list items_to_remove; + for (auto& tr : m_transfers) + { + if (tr.second.is_spent() && tr.second.m_spent_height != 0 && !(tr.second.m_flags & WALLET_TRANSFER_DETAIL_CONCISE_MODE_PRESERVE) ) + { + if (tr.second.m_spent_height + m_wallet_concise_mode_max_reorg_blocks < m_chain.get_top_block_height()) + { + items_to_remove.push_back(tr.first); + } + } + } + + return truncate_transfers_and_history(items_to_remove); + } + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::truncate_transfers_and_history(const std::list& items_to_remove) +{ + //delete from m_transfers + for (auto item : items_to_remove) + { + auto it = m_transfers.find(item); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it != m_transfers.end(), + "internal error: item to delet " << item << " not found"); + m_transfers.erase(it); + } + + //delete from recent_history + if (m_truncate_history_max_entries != 0 && m_transfer_history.size() > m_truncate_history_max_entries) + { + m_transfer_history.erase(m_transfer_history.begin(), m_transfer_history.end() - m_truncate_history_max_entries); + } + + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::balance(std::list& balances, uint64_t& mined) const +{ + load_whitelisted_tokens_if_not_loaded(); + balances.clear(); + std::unordered_map balances_map; + this->balance(balances_map, mined); + std::unordered_map custom_assets_local = m_custom_assets; + + for (auto& own_asset : m_own_asset_descriptors) + { + if (m_whitelisted_assets.find(own_asset.first) == m_whitelisted_assets.end()) + { + custom_assets_local[own_asset.first] = own_asset.second; + } + } + + asset_descriptor_base native_asset_info = AUTO_VAL_INIT(native_asset_info); + native_asset_info.full_name = CURRENCY_NAME_SHORT_BASE; + native_asset_info.ticker = CURRENCY_NAME_ABR; + native_asset_info.decimal_point = CURRENCY_DISPLAY_DECIMAL_POINT; + custom_assets_local[currency::native_coin_asset_id] = native_asset_info; + + for (const auto& item : balances_map) + { + asset_descriptor_base asset_info = AUTO_VAL_INIT(asset_info); + //check if asset is whitelisted or customly added + + //check if it custom asset + auto it_cust = custom_assets_local.find(item.first); + if (it_cust == custom_assets_local.end()) + { + if (!m_use_assets_whitelisting) + continue; + + auto it_local = m_whitelisted_assets.find(item.first); + if (it_local == m_whitelisted_assets.end()) + { + WLT_LOG_YELLOW("WARNING: unknown asset " << item.first << " found and skipped; it's NOT included in balance", LOG_LEVEL_1); + continue; + } + else + { + asset_info = it_local->second; + } + } + else + { + asset_info = it_cust->second; + custom_assets_local.erase(it_cust); + } + + balances.push_back(wallet_public::asset_balance_entry()); + wallet_public::asset_balance_entry& new_item = balances.back(); + static_cast(new_item) = item.second; + new_item.asset_info.asset_id = item.first; + static_cast(new_item.asset_info) = asset_info; + } + //manually added assets should be always present, at least as zero balanced items + for (auto& asset : custom_assets_local) + { + balances.push_back(wallet_public::asset_balance_entry()); + wallet_public::asset_balance_entry& new_item = balances.back(); + new_item.asset_info.asset_id = asset.first; + static_cast(new_item.asset_info) = asset.second; + } + + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::get_asset_info(const crypto::public_key& asset_id, currency::asset_descriptor_base& asset_info, uint32_t& asset_flags) const +{ + asset_flags = aif_none; + if (asset_id == currency::native_coin_asset_id) + { + asset_info = currency::get_native_coin_asset_descriptor(); + asset_flags |= aif_whitelisted; + return true; + } + + // own asset? + auto it_own = m_own_asset_descriptors.find(asset_id); + if (it_own != m_own_asset_descriptors.end()) + { + asset_info = it_own->second; + asset_flags |= aif_own; + return true; + } + + // whitelisted? + auto it_white = m_whitelisted_assets.find(asset_id); + if (it_white != m_whitelisted_assets.end()) + { + asset_info = it_white->second; + asset_flags |= aif_whitelisted; + return true; + } + + // custom asset? + auto it_cust = m_custom_assets.find(asset_id); + if (it_cust != m_custom_assets.end()) + { + asset_info = it_cust->second; + return true; + } + + return false; +} +//---------------------------------------------------------------------------------------------------- +size_t wallet2::get_asset_decimal_point(const crypto::public_key& asset_id, size_t result_if_not_found /* = 0 */) const +{ + if (asset_id == currency::native_coin_asset_id) + return currency::get_native_coin_asset_descriptor().decimal_point; + + // whitelisted? + auto it_white = m_whitelisted_assets.find(asset_id); + if (it_white != m_whitelisted_assets.end()) + return it_white->second.decimal_point; + + // custom asset? + auto it_cust = m_custom_assets.find(asset_id); + if (it_cust != m_custom_assets.end()) + return it_cust->second.decimal_point; + + auto it_own = m_own_asset_descriptors.find(asset_id); + if (it_own != m_own_asset_descriptors.end()) + return it_own->second.decimal_point; + + return result_if_not_found; // if not overriden, use the 0 decimal point (raw numbers) as the default +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::get_asset_decimal_point(const crypto::public_key& asset_id, size_t* p_decimal_point_result) const +{ + size_t decimal_point = get_asset_decimal_point(asset_id, SIZE_MAX); + if (decimal_point == SIZE_MAX) + return false; + if (p_decimal_point_result != nullptr) + *p_decimal_point_result = decimal_point; + return true; +} +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::balance() const +{ + uint64_t stub = 0; + return balance(stub, stub, stub, stub); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::add_custom_asset_id(const crypto::public_key& asset_id, asset_descriptor_base& asset_descriptor) +{ + currency::COMMAND_RPC_GET_ASSET_INFO::request req = AUTO_VAL_INIT(req); + currency::COMMAND_RPC_GET_ASSET_INFO::response resp = AUTO_VAL_INIT(resp); + req.asset_id = asset_id; + + bool r = m_core_proxy->call_COMMAND_RPC_GET_ASSET_INFO(req, resp); + if (r && resp.status == API_RETURN_CODE_OK) + { + m_custom_assets[asset_id] = resp.asset_descriptor; + asset_descriptor = resp.asset_descriptor; + return true; + } + return false; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::delete_custom_asset_id(const crypto::public_key& asset_id) +{ + const auto it = m_custom_assets.find(asset_id); + if (it != m_custom_assets.end()) + { + m_custom_assets.erase(it); + } + + return true; +} +//---------------------------------------------------------------------------------------------------- +const std::unordered_map& wallet2::get_local_whitelist() const +{ + return m_custom_assets; +} +//---------------------------------------------------------------------------------------------------- +const std::unordered_map& wallet2::get_global_whitelist() const +{ + return m_whitelisted_assets; +} +//---------------------------------------------------------------------------------------------------- +const std::unordered_map& wallet2::get_own_assets() const +{ + return m_own_asset_descriptors; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::load_whitelisted_tokens() const +{ + if (!m_use_assets_whitelisting) + return true; + + m_whitelisted_assets.clear(); + std::string body; + wallet_public::assets_whitelist aw = AUTO_VAL_INIT(aw); + if (epee::net_utils::get_http_json_t(WALLET_ASSETS_WHITELIST_URL, aw)) + { + for (auto it = aw.assets.begin(); it != aw.assets.end(); it++) + { + m_whitelisted_assets[it->asset_id] = static_cast(*it); + } + return true; + } + return false; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::load_whitelisted_tokens_if_not_loaded() const +{ + if (m_whitelist_updated) + { + return true; + } + if (!load_whitelisted_tokens()) + return false; + m_whitelist_updated = true; + return true; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::get_transfers(transfer_container& incoming_transfers) const +{ + incoming_transfers = m_transfers; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::generate_utxo_defragmentation_transaction_if_needed(currency::transaction& tx) +{ + if (!m_defragmentation_tx_enabled) + return false; + + construct_tx_param ctp = get_default_construct_tx_param(); + ctp.create_utxo_defragmentation_tx = true; + finalized_tx ftp{}; + + transfer(ctp, ftp, false, nullptr); + + if (ftp.was_not_prepared) + return false; // no such UTXO were found, not an error + + tx = ftp.tx; + return true; +} +//---------------------------------------------------------------------------------------------------- +std::string wallet2::get_transfers_str(bool include_spent /*= true*/, bool include_unspent /*= true*/, bool show_only_unknown /*= false*/, const std::string& filter_asset_ticker /*= std::string{}*/) const +{ + static const char* header = " index amount ticker g_index flags block tx out# asset id"; + std::stringstream ss; + ss << header << ENDL; + size_t count = 0; + size_t unknown_assets_outs_count = 0; + for (const auto& tr : m_transfers) + { + uint64_t i = tr.first; + const transfer_details& td = tr.second; + + if ((td.is_spent() && !include_spent) || (!td.is_spent() && !include_unspent)) + continue; + + bool is_locked = !is_transfer_unlocked(td); + bool native_coin = td.is_native_coin(); + asset_descriptor_base adb{}; + uint32_t asset_info_flags{}; + if (get_asset_info(td.get_asset_id(), adb, asset_info_flags) == show_only_unknown) + { + if (!show_only_unknown) + ++unknown_assets_outs_count; + continue; + } + + if (!filter_asset_ticker.empty() && adb.ticker != filter_asset_ticker) + continue; + + ss << std::right << (is_locked ? "*" : " ") << + std::setw(5) << i << " " << + std::setw(21) << print_asset_money(td.m_amount, adb.decimal_point) << " " << + std::setw(6) << std::left << (native_coin ? std::string(" ") : adb.ticker) << " " << std::right << + std::setw(7) << td.m_global_output_index << " " << + std::setw(2) << std::setfill('0') << td.m_flags << std::setfill(' ') << ":" << + std::setw(5) << transfer_flags_to_str(td.m_flags) << " " << + std::setw(7) << td.m_ptx_wallet_info->m_block_height << " " << + get_transaction_hash(td.m_ptx_wallet_info->m_tx) << " " << + std::setw(4) << td.m_internal_output_index << " "; + if (native_coin) + ss << " "; + else + ss << td.get_asset_id(); + + ss << ENDL; + + ++count; + } + + ss << "printed " << count << " outputs of " << m_transfers.size() << " total" << ENDL; + if (unknown_assets_outs_count == 1) + ss << "(" << unknown_assets_outs_count << " output with unrecognized asset id is not shown, use 'list_outputs unknown' to see it)" << ENDL; + else if (unknown_assets_outs_count > 1) + ss << "(" << unknown_assets_outs_count << " outputs with unrecognized asset ids are not shown, use 'list_outputs unknown' to see them)" << ENDL; + return ss.str(); +} +//---------------------------------------------------------------------------------------------------- +std::string wallet2::get_balance_str() const +{ + // balance unlocked / [balance total] ticker asset id + // 0.21 / 98.51 DP2 a6974d5874e97e5f4ed5ad0a62f0975edbccb1bb55502fc75c7fe808f12f44d3 + // 190.123456789012 / 199.123456789012 ZANO d6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a + // 98.0 BGTVUW af2b12f3033337f9aea1845a6bc3fc966ed4d13227a3ace7706fca7dbcdaa7e2 + // 1000.034 DP3 d4aba1020f26927571771e04b585b4ffb211f52708d5e4c465bbdfa4a12e6271 + + static const char* header = " balance unlocked / [balance total] ticker asset id"; + std::stringstream ss; + ss << header << ENDL; + + std::list balances; + uint64_t mined = 0; + balance(balances, mined); + + auto native_coin_it = std::find_if(balances.begin(), balances.end(), [&](auto& v) { return v.asset_info.asset_id == currency::native_coin_asset_id; }); + if (native_coin_it != balances.end()) + { + balances.push_front(*native_coin_it); + balances.erase(native_coin_it); + } + + for (const tools::wallet_public::asset_balance_entry& b : balances) + { + ss << " " << std::left << std::setw(21) << print_fixed_decimal_point_with_trailing_spaces(b.unlocked, b.asset_info.decimal_point); + if (b.total == b.unlocked) + ss << std::string(21 + 3, ' '); + else + ss << " / " << std::setw(21) << print_fixed_decimal_point_with_trailing_spaces(b.total, b.asset_info.decimal_point); + ss << " " << std::setw(8) << std::left << b.asset_info.ticker << " " << b.asset_info.asset_id; + if (b.asset_info.asset_id == native_coin_asset_id) + ss << " NATIVE"; + ss << ENDL; + } + + return ss.str(); +} +//---------------------------------------------------------------------------------------------------- +std::string wallet2::get_balance_str_raw() const +{ + // balance unlocked / [balance total] DP asset id + // 0.21 / 98.51 2 a6974d5874e97e5f4ed5ad0a62f0975edbccb1bb55502fc75c7fe808f12f44d3 + // 190.123456789012 / 199.123456789012 12 d6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a + // 98.0 12 af2b12f3033337f9aea1845a6bc3fc966ed4d13227a3ace7706fca7dbcdaa7e2 + // 1000.034 3 d4aba1020f26927571771e04b585b4ffb211f52708d5e4c465bbdfa4a12e6271 + //WHITELIST: + // 7d3f348fbebfffc4e61a3686189cf870ea393e1c88b8f636acbfdacf9e4b2db2 CT + // ... + + static const char* header = " balance unlocked / [balance total] ticker asset id DP flags"; + std::stringstream ss; + ss << header << ENDL; + + uint64_t dummy = 0; + typedef std::unordered_map balances_map_t; + balances_map_t balances_map; + this->balance(balances_map, dummy); + + auto print_map = [&](const balances_map_t& map) { + for (const auto& entry : map) + { + uint32_t asset_flags = 0; + asset_descriptor_base asset_info{}; + bool has_info = get_asset_info(entry.first, asset_info, asset_flags); + ss << " " << std::left << std::setw(21) << print_fixed_decimal_point_with_trailing_spaces(entry.second.unlocked, asset_info.decimal_point); + if (entry.second.total == entry.second.unlocked) + ss << std::string(21 + 3, ' '); + else + ss << " / " << std::setw(21) << print_fixed_decimal_point_with_trailing_spaces(entry.second.total, asset_info.decimal_point); + + ss << " " << std::setw(8) << std::left << asset_info.ticker; + ss << " " << entry.first << " "; + + if (has_info) + ss << std::setw(2) << std::right << (int)asset_info.decimal_point; + else + ss << "??"; + + ss << " "; + + if (entry.first == native_coin_asset_id) + { + ss << "NATIVE"; + } + else if (asset_flags != aif_none) + { + if (asset_flags & aif_own) + ss << "own,"; + if (asset_flags & aif_whitelisted) + ss << "whitelisted,"; + ss.seekp(-1, ss.cur); // trim comma + } + ss << ENDL; + } + }; + + auto balances_map_it = balances_map.find(native_coin_asset_id); + if (balances_map_it != balances_map.end()) + { + balances_map_t native_coin_map; + native_coin_map.insert(*balances_map_it); + balances_map.erase(balances_map_it); + print_map(native_coin_map); + } + print_map(balances_map); + + + //print whitelist + ss << "WHITELIST: " << ENDL; + + for (const auto& entry : m_whitelisted_assets) + { + ss << " " << std::left << entry.first << " " << entry.second.ticker << ENDL; + } + + // print custom list + ss << "CUSTOM LIST: " << ENDL; + + for (const auto& entry : m_custom_assets) + { + ss << " " << std::left << entry.first << " " << entry.second.ticker << ENDL; + } + + // print own list + ss << "OWN DESCRIPTORS LIST: " << ENDL; + + for (const auto& entry : m_own_asset_descriptors) + { + ss << " " << std::left << entry.first << " " << entry.second.ticker << ENDL; + } + + return ss.str(); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::get_payments(const std::string& payment_id, std::list& payments, uint64_t min_height) const +{ + auto range = m_payments.equal_range(payment_id); + 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().view_secret_key); + + // deserialize args + currency::finalized_tx ft = AUTO_VAL_INIT(ft); + bool r = t_unserializable_object_from_blob(ft.ftp, decrypted_src_blob); + THROW_IF_FALSE_WALLET_EX(r, error::wallet_common_error, "Failed to decrypt tx sources blob"); + + // make sure unsigned tx was created with the same keys + THROW_IF_FALSE_WALLET_EX(ft.ftp.spend_pub_key == m_account.get_keys().account_address.spend_public_key, error::wallet_common_error, "The was created in a different wallet, keys missmatch"); + + finalize_transaction(ft.ftp, ft.tx, ft.one_time_key, false); + + // calculate key images for each change output + crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX( + crypto::generate_key_derivation( + m_account.get_keys().account_address.view_public_key, + ft.one_time_key, + derivation), + "internal error: sign_transfer: failed to generate key derivation(" + << m_account.get_keys().account_address.view_public_key + << ", view secret key: " << ft.one_time_key << ")"); + + for (size_t i = 0; i < ft.tx.vout.size(); ++i) + { + VARIANT_SWITCH_BEGIN(ft.tx.vout[i]); + VARIANT_CASE_CONST(tx_out_bare, out) + { + if (out.target.type() != typeid(txout_to_key)) + continue; + const txout_to_key& otk = boost::get(out.target); + + crypto::public_key ephemeral_pub = AUTO_VAL_INIT(ephemeral_pub); + if (!crypto::derive_public_key(derivation, i, m_account.get_keys().account_address.spend_public_key, ephemeral_pub)) + { + WLT_LOG_ERROR("derive_public_key failed for tx " << get_transaction_hash(ft.tx) << ", out # " << i); + } + + if (otk.key == ephemeral_pub) + { + // this is the output to the given keys + // derive secret key and calculate key image + crypto::secret_key ephemeral_sec = AUTO_VAL_INIT(ephemeral_sec); + crypto::derive_secret_key(derivation, i, m_account.get_keys().spend_secret_key, ephemeral_sec); + crypto::key_image ki = AUTO_VAL_INIT(ki); + crypto::generate_key_image(ephemeral_pub, ephemeral_sec, ki); + + ft.outs_key_images.push_back(make_serializable_pair(static_cast(i), ki)); + } + } + VARIANT_CASE_CONST(tx_out_zarcanum, o); + //@#@ + VARIANT_SWITCH_END(); + } + + // serialize and encrypt the result + signed_tx_blob = t_serializable_object_to_blob(ft); + crypto::chacha_crypt(signed_tx_blob, m_account.get_keys().view_secret_key); + + tx = ft.tx; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::sign_transfer_files(const std::string& tx_sources_file, const std::string& signed_tx_file, currency::transaction& tx) +{ + std::string sources_blob; + bool r = epee::file_io_utils::load_file_to_string(tx_sources_file, sources_blob); + THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "failed to open file " << tx_sources_file); + + std::string signed_tx_blob; + sign_transfer(sources_blob, signed_tx_blob, tx); + + r = epee::file_io_utils::save_string_to_file(signed_tx_file, signed_tx_blob); + THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "failed to store signed tx to file " << signed_tx_file); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::get_utxo_distribution(std::map& distribution) +{ + //TODO@#@ + /* + prepare_free_transfers_cache(0); + for (auto ent : m_found_free_amounts) + { + distribution[ent.first] = ent.second.size(); + } + */ + + return false; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::submit_transfer(const std::string& signed_tx_blob, currency::transaction& tx) +{ + // decrypt sources + std::string decrypted_src_blob = crypto::chacha_crypt(signed_tx_blob, m_account.get_keys().view_secret_key); + + // deserialize tx data + currency::finalized_tx ft = AUTO_VAL_INIT(ft); + bool r = t_unserializable_object_from_blob(ft, decrypted_src_blob); + THROW_IF_FALSE_WALLET_EX(r, error::wallet_common_error, "Failed to decrypt signed tx data"); + tx = ft.tx; + crypto::hash tx_hash = get_transaction_hash(tx); + + // foolproof + 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: " << tx_hash); + + try + { + send_transaction_to_network(tx); + } + catch (...) + { + // clear transfers flags if smth went wrong + uint32_t flag = WALLET_TRANSFER_DETAIL_FLAG_SPENT | WALLET_TRANSFER_DETAIL_FLAG_COLD_SIG_RESERVATION; + clear_transfers_from_flag(ft.ftp.selected_transfers, flag, "broadcasting tx " + epee::string_tools::pod_to_hex(tx_hash) + " was unsuccessful"); + throw; + } + + add_sent_tx_detailed_info(tx, ft.ftp.attachments, ft.ftp.prepared_destinations, ft.ftp.selected_transfers); + m_tx_keys.insert(std::make_pair(tx_hash, ft.one_time_key)); + + if (m_watch_only) + { + std::vector> pk_ki_to_be_added; + std::vector> tri_ki_to_be_added; + + for (auto& p : ft.outs_key_images) + { + THROW_IF_FALSE_WALLET_INT_ERR_EX(p.first < tx.vout.size(), "outs_key_images has invalid out index: " << p.first << ", tx.vout.size() = " << tx.vout.size()); + THROW_IF_FALSE_WALLET_INT_ERR_EX(tx.vout[p.first].type() == typeid(tx_out_bare), "Unexpected type in submit_transfer: " << tx.vout[p.first].type().name()); + auto& out = boost::get(tx.vout[p.first]); + THROW_IF_FALSE_WALLET_INT_ERR_EX(out.target.type() == typeid(txout_to_key), "outs_key_images has invalid out type, index: " << p.first); + const txout_to_key& otk = boost::get(out.target); + pk_ki_to_be_added.push_back(std::make_pair(otk.key, p.second)); + } + + THROW_IF_FALSE_WALLET_INT_ERR_EX(tx.vin.size() == ft.ftp.sources.size(), "tx.vin and ft.ftp.sources sizes missmatch"); + for (size_t i = 0; i < tx.vin.size(); ++i) + { + const txin_v& in = tx.vin[i]; + THROW_IF_FALSE_WALLET_CMN_ERR_EX(in.type() == typeid(txin_to_key), "tx " << tx_hash << " has a non txin_to_key input"); + const crypto::key_image& ki = boost::get(in).k_image; + + const auto& src = ft.ftp.sources[i]; + THROW_IF_FALSE_WALLET_INT_ERR_EX(src.real_output < src.outputs.size(), "src.real_output is out of bounds: " << src.real_output); + const crypto::public_key& out_key = src.outputs[src.real_output].stealth_address; + + tri_ki_to_be_added.push_back(std::make_pair(src.transfer_index, ki)); + pk_ki_to_be_added.push_back(std::make_pair(out_key, ki)); + } + + for (auto& p : pk_ki_to_be_added) + { + auto it = m_pending_key_images.find(p.first); + if (it != m_pending_key_images.end()) + { + LOG_PRINT_YELLOW("warning: for tx " << tx_hash << " out pub key " << p.first << " already exist in m_pending_key_images, ki: " << it->second << ", proposed new ki: " << p.second, LOG_LEVEL_0); + } + else + { + m_pending_key_images[p.first] = p.second; + m_pending_key_images_file_container.push_back(tools::out_key_to_ki{ p.first, p.second }); + LOG_PRINT_L2("for tx " << tx_hash << " pending key image added (" << p.first << ", " << p.second << ")"); + } + } + + for (auto& p : tri_ki_to_be_added) + { + //THROW_IF_FALSE_WALLET_INT_ERR_EX(p.first < m_transfers.size(), "incorrect transfer index: " << p.first); + auto& tr = m_transfers.at(p.first); + if (tr.m_key_image != currency::null_ki && tr.m_key_image != p.second) + { + 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; + LOG_PRINT_L2("for tx " << tx_hash << " key image " << p.second << " was associated with transfer # " << p.first); + } + } + + // TODO: print inputs' key images + print_tx_sent_message(tx, "(from submit_transfer)"); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::submit_transfer_files(const std::string& signed_tx_file, currency::transaction& tx) +{ + std::string signed_tx_blob; + bool r = epee::file_io_utils::load_file_to_string(signed_tx_file, signed_tx_blob); + THROW_IF_FALSE_WALLET_EX(r, error::wallet_common_error, std::string("failed to open file ") + signed_tx_file); + + submit_transfer(signed_tx_blob, tx); +} +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::get_recent_transfers_total_count() +{ + return m_transfer_history.size(); +} +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::get_transfer_entries_count() +{ + return m_transfers.size(); +} +//---------------------------------------------------------------------------------------------------- + +template +bool enum_container(iterator_t it_begin, iterator_t it_end, callback_t cb) +{ + for (iterator_t it = it_begin; it != it_end; it++) + { + if (!cb(*it, it - it_begin)) + return true; + } + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::is_defragmentation_transaction(const wallet_public::wallet_transfer_info& wti) +{ + if (wti.employed_entries.receive.size() && wti.employed_entries.spent.size() && wti.subtransfers.size() == 1) + { + if (wti.subtransfers[0].asset_id == currency::native_coin_asset_id && !wti.subtransfers[0].is_income && wti.subtransfers[0].amount == get_tx_fee(wti.tx)) + return true; + } + return false; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::get_recent_transfers_history(std::vector& trs, size_t offset, size_t count, uint64_t& total, uint64_t& last_item_index, bool exclude_mining_txs, bool start_from_end) +{ + if (!count || offset >= m_transfer_history.size()) + return; + + auto cb = [&](wallet_public::wallet_transfer_info& wti, size_t local_offset) { + + if (exclude_mining_txs) + { + if (currency::is_coinbase(wti.tx) || is_defragmentation_transaction(wti)) + return true; + } + trs.push_back(wti); + load_wallet_transfer_info_flags(trs.back()); + last_item_index = offset + local_offset; + trs.back().transfer_internal_index = last_item_index; + + if (wti.remote_addresses.size() == 1) + { + wti.remote_aliases = get_aliases_for_address(wti.remote_addresses[0]); + } + + if (trs.size() >= count) + { + return false; + } + return true; + }; + + if (start_from_end) + enum_container(m_transfer_history.rbegin() + offset, m_transfer_history.rend(), cb); + else + enum_container(m_transfer_history.begin() + offset, m_transfer_history.end(), cb); + + total = m_transfer_history.size(); + +} + +void wallet2::wti_to_csv_entry(std::ostream& ss, const wallet_public::wallet_transfer_info& wti, size_t index) +{ + for (auto& subtr : wti.subtransfers) + { + ss << index << ","; + ss << epee::misc_utils::get_time_str(wti.timestamp) << ","; + ss << print_money(subtr.amount) << ","; + ss << subtr.asset_id << ","; + ss << "\"" << wti.comment << "\","; + ss << "["; + std::copy(wti.remote_addresses.begin(), wti.remote_addresses.end(), std::ostream_iterator(ss, " ")); + ss << "]" << ","; + ss << wti.tx_hash << ","; + ss << wti.height << ","; + ss << wti.unlock_time << ","; + ss << wti.tx_blob_size << ","; + ss << epee::string_tools::buff_to_hex_nodelimer(wti.payment_id) << ","; + ss << "["; + std::copy(wti.remote_aliases.begin(), wti.remote_aliases.end(), std::ostream_iterator(ss, " ")); + ss << "]" << ","; + ss << (subtr.is_income ? "in" : "out") << ","; + ss << (wti.is_service ? "[SERVICE]" : "") << (wti.is_mixing ? "[MIXINS]" : "") << (wti.is_mining ? "[MINING]" : "") << ","; + ss << wti.tx_type << ","; + ss << print_money(wti.fee) << ENDL; + } + +}; + +void wallet2::wti_to_txt_line(std::ostream& ss, const wallet_public::wallet_transfer_info& wti, size_t index) +{ + for (auto& subtr : wti.subtransfers) + { + ss << (subtr.is_income ? "[INC]" : "[OUT]") << "\t" + << epee::misc_utils::get_time_str(wti.timestamp) << "\t" + << print_money(subtr.amount) << "\t" + << subtr.asset_id << "\t" + << print_money(wti.fee) << "\t" + << wti.remote_addresses << "\t" + << wti.comment << ENDL; + } +}; + +void wallet2::wti_to_json_line(std::ostream& ss, const wallet_public::wallet_transfer_info& wti, size_t index) +{ + ss << epee::serialization::store_t_to_json(wti, 4) << ","; +}; + +//---------------------------------------------------------------------------------------------------- +void wallet2::set_connectivity_options(unsigned int timeout) +{ + m_core_proxy->set_connectivity(timeout, WALLET_RCP_COUNT_ATTEMNTS); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::export_transaction_history(std::ostream& ss, const std::string& format, bool include_pos_transactions) +{ + //typedef int(*t_somefunc)(int, int); + typedef void(*playout_cb_type)(std::ostream&, const wallet_public::wallet_transfer_info&, size_t); + playout_cb_type cb_csv = &wallet2::wti_to_csv_entry; + playout_cb_type cb_json = &wallet2::wti_to_json_line; + playout_cb_type cb_plain_text = &wallet2::wti_to_txt_line; + + playout_cb_type cb = cb_csv; + if (format == "json") + { + ss << "{ \"history\": ["; + cb = cb_json; + } + else if (format == "text") + { + cb = cb_plain_text; + } + else + { + //csv by default + ss << "N, Date, Amount, AssetID, Comment, Address, ID, Height, Unlock timestamp, Tx size, Alias, PaymentID, In/Out, Flags, Type, Fee" << ENDL; + } + + + enum_container(m_transfer_history.begin(), m_transfer_history.end(), [&](wallet_public::wallet_transfer_info& wti, size_t index) { + if (!include_pos_transactions) + { + if (currency::is_coinbase(wti.tx)) + return true; + } + wti.fee = currency::get_tx_fee(wti.tx); + cb(ss, wti, index); + return true; + }); + + if (format == "json") + { + ss << "{}]}"; + } + +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::get_transfer_address(const std::string& adr_str, currency::account_public_address& addr, std::string& payment_id) +{ + return m_core_proxy->get_transfer_address(adr_str, addr, payment_id); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::is_transfer_okay_for_pos(const transfer_details& tr, bool is_zarcanum_hf, uint64_t& stake_unlock_time) const +{ + if (is_zarcanum_hf) + { + if (!tr.is_zc()) + return false; + + if (!tr.is_native_coin()) + return false; + } + + if (!tr.is_spendable()) + return false; + + //blockchain conditions + if (!is_transfer_unlocked(tr, true, stake_unlock_time)) + return false; + + //prevent staking of after-last-pow-coins + if (get_blockchain_current_size() - tr.m_ptx_wallet_info->m_block_height <= m_core_runtime_config.min_coinstake_age) + return false; + + if (tr.m_ptx_wallet_info->m_block_height > m_last_pow_block_h) + return false; + + return true; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::get_mining_history(wallet_public::mining_history& hist, uint64_t timestamp_from) +{ + for (auto& tr : m_transfer_history) + { + if (currency::is_coinbase(tr.tx) && tr.tx.vin.size() == 2 && tr.timestamp > timestamp_from) + { + tools::wallet_public::mining_history_entry mhe = AUTO_VAL_INIT(mhe); + mhe.a = tr.get_native_income_amount(); + mhe.t = tr.timestamp; + mhe.h = tr.height; + hist.mined_entries.push_back(mhe); + } + } +} +//---------------------------------------------------------------------------------------------------- +size_t wallet2::get_pos_entries_count() +{ + bool is_zarcanum_hf = is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM); + size_t counter = 0; + + for (const auto& tr : m_transfers) + { + auto& td = tr.second; + + uint64_t stake_unlock_time = 0; + if (!is_transfer_okay_for_pos(td, is_zarcanum_hf, stake_unlock_time)) + continue; + + ++counter; + } + + return counter; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::get_pos_entries(std::vector& entries) +{ + bool is_zarcanum_hf = is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM); + for (const auto& td : m_transfers) + { + uint64_t i = td.first; + auto& tr = td.second; + + uint64_t stake_unlock_time = 0; + if (!is_transfer_okay_for_pos(tr, is_zarcanum_hf, stake_unlock_time)) + continue; + + pos_entry pe = AUTO_VAL_INIT(pe); + pe.amount = tr.amount(); + pe.g_index = tr.m_global_output_index; + pe.keyimage = tr.m_key_image; + pe.wallet_index = i; + pe.stake_unlock_time = stake_unlock_time; + pe.block_timestamp = tr.m_ptx_wallet_info->m_block_timestamp; + entries.push_back(pe); + } + + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::is_in_hardfork_zone(uint64_t hardfork_index) const +{ + return m_core_runtime_config.is_hardfork_active_for_height(hardfork_index, get_blockchain_current_size()); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::proxy_to_daemon(const std::string& uri, const std::string& body, int& response_code, std::string& response_body) +{ + return m_core_proxy->call_COMMAND_RPC_INVOKE(uri, body, response_code, response_body); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::prepare_and_sign_pos_block(const mining_context& cxt, uint64_t full_block_reward, const currency::pos_entry& pe, currency::tx_generation_context& miner_tx_tgc, currency::block& b) const +{ + bool r = false; + //WLT_CHECK_AND_ASSERT_MES(pe.wallet_index < m_transfers.size(), false, "invalid pe.wallet_index: " << pe.wallet_index); + const transfer_details& td = m_transfers.at(pe.wallet_index); + const transaction& source_tx = td.m_ptx_wallet_info->m_tx; + const crypto::public_key source_tx_pub_key = get_tx_pub_key_from_extra(source_tx); + WLT_CHECK_AND_ASSERT_MES(pe.tx_out_index < source_tx.vout.size(), false, "invalid pe.tx_out_index: " << pe.tx_out_index); + const currency::tx_out_v& stake_out_v = source_tx.vout[pe.tx_out_index]; + + // calculate stake_out_derivation and secret_x (derived ephemeral secret key) + crypto::key_derivation stake_out_derivation = AUTO_VAL_INIT(stake_out_derivation); + r = crypto::generate_key_derivation(source_tx_pub_key, m_account.get_keys().view_secret_key, stake_out_derivation); // d = 8 * v * R + WLT_CHECK_AND_ASSERT_MES(r, false, "generate_key_derivation failed, tid: " << pe.wallet_index << ", pe.tx_id: " << pe.tx_id); + crypto::secret_key secret_x = AUTO_VAL_INIT(secret_x); + crypto::derive_secret_key(stake_out_derivation, pe.tx_out_index, m_account.get_keys().spend_secret_key, secret_x); // x = Hs(8 * v * R, i) + s + + if (!cxt.zarcanum) + { + // old PoS with non-hidden amounts WLT_CHECK_AND_ASSERT_MES(b.miner_tx.vin[0].type() == typeid(currency::txin_gen), false, "Wrong input 0 type in transaction: " << b.miner_tx.vin[0].type().name()); - WLT_CHECK_AND_ASSERT_MES(b.miner_tx.vin[1].type() == typeid(currency::txin_zc_input), false, "Wrong input 1 type in transaction: " << b.miner_tx.vin[1].type().name()); - WLT_CHECK_AND_ASSERT_MES(b.miner_tx.signatures.size() == 1 && b.miner_tx.signatures[0].type() == typeid(zarcanum_sig), false, "wrong sig prepared in a PoS block"); - WLT_CHECK_AND_ASSERT_MES(stake_out_v.type() == typeid(tx_out_zarcanum), false, "unexpected stake output type: " << stake_out_v.type().name() << ", expected: tx_out_zarcanum"); - WLT_CHECK_AND_ASSERT_MES(td.m_zc_info_ptr->asset_id == currency::native_coin_asset_id, false, "attempted to stake an output with a non-native asset id"); + WLT_CHECK_AND_ASSERT_MES(b.miner_tx.vin[1].type() == typeid(currency::txin_to_key), false, "Wrong input 1 type in transaction: " << b.miner_tx.vin[1].type().name()); + WLT_CHECK_AND_ASSERT_MES(b.miner_tx.signatures.size() == 1 && b.miner_tx.signatures[0].type() == typeid(NLSAG_sig), false, "wrong sig prepared in a PoS block"); + WLT_CHECK_AND_ASSERT_MES(stake_out_v.type() == typeid(tx_out_bare), false, "unexpected stake output type: " << stake_out_v.type().name() << ", expected: tx_out_bare"); + const tx_out_bare& stake_out = boost::get(stake_out_v); + WLT_CHECK_AND_ASSERT_MES(stake_out.target.type() == typeid(txout_to_key), false, "unexpected stake output target type: " << stake_out.target.type().name() << ", expected: txout_to_key"); - zarcanum_sig& sig = boost::get(b.miner_tx.signatures[0]); - txin_zc_input& stake_input = boost::get(b.miner_tx.vin[1]); - const tx_out_zarcanum& stake_out = boost::get(stake_out_v); + NLSAG_sig& sig = boost::get(b.miner_tx.signatures[0]); + txin_to_key& stake_input = boost::get(b.miner_tx.vin[1]); + const txout_to_key& stake_out_target = boost::get(stake_out.target); - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response decoys_resp = AUTO_VAL_INIT(decoys_resp); - std::vector ring; - uint64_t secret_index = 0; // index of the real stake output + // partially fill stake input + stake_input.k_image = pe.keyimage; + stake_input.amount = pe.amount; // get decoys outputs and construct miner tx - const size_t required_decoys_count = m_core_runtime_config.hf4_minimum_mixins == 0 ? 4 /* <-- for tests */ : m_core_runtime_config.hf4_minimum_mixins; - static bool use_only_forced_to_mix = false; // TODO @#@# set them somewhere else - if (required_decoys_count > 0 && !is_auditable()) + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response decoys_resp = AUTO_VAL_INIT(decoys_resp); + std::vector ring; + uint64_t secret_index = 0; // index of the real stake output + if (m_required_decoys_count > 0 && !is_auditable()) { COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request decoys_req = AUTO_VAL_INIT(decoys_req); - decoys_req.height_upper_limit = m_last_pow_block_h; // request decoys to be either older than, or the same age as stake output's height - decoys_req.use_forced_mix_outs = use_only_forced_to_mix; - decoys_req.decoys_count = required_decoys_count + 1; // one more to be able to skip a decoy in case it hits the real output - decoys_req.amounts.push_back(0); // request one batch of decoys for hidden amounts + decoys_req.height_upper_limit = std::min(m_last_pow_block_h, m_last_known_daemon_height > m_core_runtime_config.min_coinstake_age ? m_last_known_daemon_height - m_core_runtime_config.min_coinstake_age : m_last_pow_block_h); + decoys_req.use_forced_mix_outs = false; + decoys_req.decoys_count = m_required_decoys_count + 1; // one more to be able to skip a decoy in case it hits the real output + decoys_req.amounts.push_back(pe.amount); // request one batch of decoys r = m_core_proxy->call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS(decoys_req, decoys_resp); // TODO @#@# do we need these exceptions? @@ -4933,40 +4823,45 @@ namespace tools THROW_IF_FALSE_WALLET_EX(decoys_resp.status != API_RETURN_CODE_BUSY, error::daemon_busy, "getrandom_outs1.bin"); THROW_IF_FALSE_WALLET_EX(decoys_resp.status == API_RETURN_CODE_OK, error::get_random_outs_error, decoys_resp.status); WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(decoys_resp.outs.size() == 1, "got wrong number of decoys batches: " << decoys_resp.outs.size()); - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(decoys_resp.outs[0].outs.size() == required_decoys_count + 1, "for PoS stake tx got less decoys to mix than requested: " << decoys_resp.outs[0].outs.size() << " < " << required_decoys_count + 1); - auto& decoys = decoys_resp.outs[0].outs; - decoys.emplace_front(td.m_global_output_index, stake_out.stealth_address, stake_out.amount_commitment, stake_out.concealing_point, stake_out.blinded_asset_id); + // we expect that less decoys can be returned than requested, we will use them all anyway + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(decoys_resp.outs[0].outs.size() <= m_required_decoys_count + 1, "for PoS stake tx got greater decoys to mix than requested: " << decoys_resp.outs[0].outs.size() << " < " << m_required_decoys_count + 1); + + auto& ring_candidates = decoys_resp.outs[0].outs; + ring_candidates.emplace_front(td.m_global_output_index, stake_out_target.key); std::unordered_set used_gindices; size_t good_outs_count = 0; - for (auto it = decoys.begin(); it != decoys.end(); ) + for (auto it = ring_candidates.begin(); it != ring_candidates.end(); ) { if (used_gindices.count(it->global_amount_index) != 0) { - it = decoys.erase(it); + it = ring_candidates.erase(it); continue; } used_gindices.insert(it->global_amount_index); - if (++good_outs_count == required_decoys_count + 1) + if (++good_outs_count == m_required_decoys_count + 1) { - decoys.erase(++it, decoys.end()); + ring_candidates.erase(++it, ring_candidates.end()); break; } ++it; } - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(decoys.size() == required_decoys_count + 1, "for PoS stake got less good decoys than required: " << decoys.size() << " < " << required_decoys_count); - decoys.sort([](auto& l, auto& r) { return l.global_amount_index < r.global_amount_index; }); // sort them now (note absolute_sorted_output_offsets_to_relative_in_place() below) + // won't assert that ring_candidates.size() == m_required_decoys_count + 1 here as we will use all the decoys anyway + if (ring_candidates.size() < m_required_decoys_count + 1) + LOG_PRINT_YELLOW("PoS: using " << ring_candidates.size() - 1 << " decoys for mining tx, while " << m_required_decoys_count << " are required", LOG_LEVEL_1); + + ring_candidates.sort([](auto& l, auto& r) { return l.global_amount_index < r.global_amount_index; }); // sort them now (note absolute_sorted_output_offsets_to_relative_in_place() below) uint64_t i = 0; - for (auto& el : decoys) + for (auto& el : ring_candidates) { uint64_t gindex = el.global_amount_index; if (gindex == td.m_global_output_index) secret_index = i; ++i; - ring.emplace_back(el.stealth_address, el.amount_commitment, el.blinded_asset_id, el.concealing_point); + ring.emplace_back(&el.stealth_address); stake_input.key_offsets.push_back(el.global_amount_index); } r = absolute_sorted_output_offsets_to_relative_in_place(stake_input.key_offsets); @@ -4975,3389 +4870,3494 @@ namespace tools else { // no decoys, the ring consist of one element -- the real stake output - ring.emplace_back(stake_out.stealth_address, stake_out.amount_commitment, stake_out.blinded_asset_id, stake_out.concealing_point); + ring.emplace_back(&stake_out_target.key); stake_input.key_offsets.push_back(td.m_global_output_index); } - stake_input.k_image = pe.keyimage; - crypto::point_t stake_out_blinded_asset_id_pt = currency::native_coin_asset_id_pt + td.m_zc_info_ptr->asset_id_blinding_mask * crypto::c_point_X; -#ifndef NDEBUG + // sign block actually in coinbase transaction + crypto::hash block_hash = currency::get_block_hash(b); + + // generate sring signature + sig.s.resize(ring.size()); + crypto::generate_ring_signature(block_hash, stake_input.k_image, ring, secret_x, secret_index, sig.s.data()); + + if (epee::log_space::get_set_log_detalisation_level() >= LOG_LEVEL_4) { - crypto::point_t source_amount_commitment = crypto::c_scalar_1div8 * td.m_amount * stake_out_blinded_asset_id_pt + crypto::c_scalar_1div8 * td.m_zc_info_ptr->amount_blinding_mask * crypto::c_point_G; - WLT_CHECK_AND_ASSERT_MES(stake_out.amount_commitment == source_amount_commitment.to_public_key(), false, "real output amount commitment check failed"); - WLT_CHECK_AND_ASSERT_MES(ring[secret_index].amount_commitment == stake_out.amount_commitment, false, "ring secret member doesn't match with the stake output"); - WLT_CHECK_AND_ASSERT_MES(cxt.stake_amount == td.m_amount, false, "stake_amount missmatch"); + std::stringstream ss; + ss << "GENERATED RING SIGNATURE for PoS block coinbase:" << ENDL << + " block hash: " << block_hash << ENDL << + " key image: " << stake_input.k_image << ENDL << + " ring:" << ENDL; + for (auto el : ring) + ss << " " << *el << ENDL; + ss << " signature:" << ENDL; + for (auto el : sig.s) + ss << " " << el << ENDL; + WLT_LOG_L4(ss.str()); } -#endif - - crypto::hash hash_for_zarcanum_sig = get_block_hash(b); - - WLT_CHECK_AND_ASSERT_MES(miner_tx_tgc.pseudo_out_amount_blinding_masks_sum.is_zero(), false, "pseudo_out_amount_blinding_masks_sum is nonzero"); // it should be zero because there's only one ZC input (stake), and thus only one pseudo out (the sum is non-zero iff POC > 1) - crypto::scalar_t pseudo_out_amount_blinding_mask = miner_tx_tgc.amount_blinding_masks_sum; // sum of outputs' amount blinding masks - - miner_tx_tgc.pseudo_outs_blinded_asset_ids.emplace_back(currency::native_coin_asset_id_pt); // for Zarcanum stake inputs pseudo outputs commitments has explicit native asset id - miner_tx_tgc.pseudo_outs_plus_real_out_blinding_masks.emplace_back(0); - miner_tx_tgc.real_zc_ins_asset_ids.emplace_back(td.m_zc_info_ptr->asset_id); - // TODO @#@# [architecture] the same value is calculated in zarcanum_generate_proof(), consider an impovement - miner_tx_tgc.pseudo_out_amount_commitments_sum += cxt.stake_amount * stake_out_blinded_asset_id_pt + pseudo_out_amount_blinding_mask * crypto::c_point_G; - miner_tx_tgc.real_in_asset_id_blinding_mask_x_amount_sum += td.m_zc_info_ptr->asset_id_blinding_mask * cxt.stake_amount; - - uint8_t err = 0; - r = crypto::zarcanum_generate_proof(hash_for_zarcanum_sig, cxt.kernel_hash, ring, cxt.last_pow_block_id_hashed, cxt.sk.kimage, - secret_x, cxt.secret_q, secret_index, cxt.stake_amount, td.m_zc_info_ptr->asset_id_blinding_mask, cxt.stake_out_amount_blinding_mask, pseudo_out_amount_blinding_mask, - static_cast(sig), &err); - WLT_CHECK_AND_ASSERT_MES(r, false, "zarcanum_generate_proof failed, err: " << (int)err); - - // - // The miner tx prefix should be sealed by now, and the tx hash should be defined. - // Any changes made below should only affect the signatures/proofs and should not impact the prefix hash calculation. - // - crypto::hash miner_tx_id = get_transaction_hash(b.miner_tx); - - // proofs for miner_tx - - // asset surjection proof - currency::zc_asset_surjection_proof asp{}; - r = generate_asset_surjection_proof(miner_tx_id, false, miner_tx_tgc, asp); // has_non_zc_inputs == false because after the HF4 PoS mining is only allowed for ZC stakes inputs - WLT_CHECK_AND_ASSERT_MES(r, false, "generete_asset_surjection_proof failed"); - b.miner_tx.proofs.emplace_back(std::move(asp)); - - // range proofs - currency::zc_outs_range_proof range_proofs{}; - r = generate_zc_outs_range_proof(miner_tx_id, 0, miner_tx_tgc, b.miner_tx.vout, range_proofs); - WLT_CHECK_AND_ASSERT_MES(r, false, "Failed to generate zc_outs_range_proof()"); - b.miner_tx.proofs.emplace_back(std::move(range_proofs)); - - // balance proof - currency::zc_balance_proof balance_proof{}; - r = generate_tx_balance_proof(b.miner_tx, miner_tx_id, miner_tx_tgc, full_block_reward, balance_proof); - WLT_CHECK_AND_ASSERT_MES(r, false, "generate_tx_balance_proof failed"); - b.miner_tx.proofs.emplace_back(std::move(balance_proof)); - - // the following line are for debugging when necessary -- sowle - //err = 0; - //r = crypto::zarcanum_verify_proof(hash_for_zarcanum_sig, cxt.kernel_hash, ring, cxt.last_pow_block_id_hashed, cxt.sk.kimage, cxt.basic_diff, sig, &err); - //WLT_CHECK_AND_ASSERT_MES(r, false, "zarcanum_verify_proof failed with code " << (int)err); return true; } - //------------------------------------------------------------------ - bool wallet2::fill_mining_context(mining_context& ctx) - { - currency::COMMAND_RPC_GET_POS_MINING_DETAILS::request pos_details_req = AUTO_VAL_INIT(pos_details_req); - currency::COMMAND_RPC_GET_POS_MINING_DETAILS::response pos_details_resp = AUTO_VAL_INIT(pos_details_resp); - m_core_proxy->call_COMMAND_RPC_GET_POS_MINING_DETAILS(pos_details_req, pos_details_resp); - if (pos_details_resp.status != API_RETURN_CODE_OK) - return false; - ctx = mining_context{}; - ctx.init(wide_difficulty_type(pos_details_resp.pos_basic_difficulty), pos_details_resp.sm, is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM)); + // Zarcanum - ctx.last_block_hash = pos_details_resp.last_block_hash; - ctx.is_pos_allowed = pos_details_resp.pos_mining_allowed; - ctx.is_pos_sequence_factor_good = pos_details_resp.pos_sequence_factor_is_good; - ctx.starter_timestamp = pos_details_resp.starter_timestamp; - ctx.status = API_RETURN_CODE_NOT_FOUND; - return true; - } - //------------------------------------------------------------------ - bool wallet2::try_mint_pos() - { - return try_mint_pos(m_account.get_public_address()); - } - //------------------------------------------------------------------ - bool wallet2::try_mint_pos(const currency::account_public_address& miner_address) - { - TIME_MEASURE_START_MS(mining_duration_ms); - mining_context ctx = AUTO_VAL_INIT(ctx); - WLT_LOG_L2("Starting PoS mining iteration"); - fill_mining_context(ctx); + WLT_CHECK_AND_ASSERT_MES(td.is_zc(), false, "the transfer [" << pe.wallet_index << "] is not zc type, which is required for zarcanum"); + WLT_CHECK_AND_ASSERT_MES(b.miner_tx.vin[0].type() == typeid(currency::txin_gen), false, "Wrong input 0 type in transaction: " << b.miner_tx.vin[0].type().name()); + WLT_CHECK_AND_ASSERT_MES(b.miner_tx.vin[1].type() == typeid(currency::txin_zc_input), false, "Wrong input 1 type in transaction: " << b.miner_tx.vin[1].type().name()); + WLT_CHECK_AND_ASSERT_MES(b.miner_tx.signatures.size() == 1 && b.miner_tx.signatures[0].type() == typeid(zarcanum_sig), false, "wrong sig prepared in a PoS block"); + WLT_CHECK_AND_ASSERT_MES(stake_out_v.type() == typeid(tx_out_zarcanum), false, "unexpected stake output type: " << stake_out_v.type().name() << ", expected: tx_out_zarcanum"); + WLT_CHECK_AND_ASSERT_MES(td.m_zc_info_ptr->asset_id == currency::native_coin_asset_id, false, "attempted to stake an output with a non-native asset id"); - if (!ctx.is_pos_allowed) + zarcanum_sig& sig = boost::get(b.miner_tx.signatures[0]); + txin_zc_input& stake_input = boost::get(b.miner_tx.vin[1]); + const tx_out_zarcanum& stake_out = boost::get(stake_out_v); + + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response decoys_resp = AUTO_VAL_INIT(decoys_resp); + std::vector ring; + uint64_t secret_index = 0; // index of the real stake output + + // get decoys outputs and construct miner tx + const size_t required_decoys_count = m_core_runtime_config.hf4_minimum_mixins == 0 ? 4 /* <-- for tests */ : m_core_runtime_config.hf4_minimum_mixins; + static bool use_only_forced_to_mix = false; // TODO @#@# set them somewhere else + if (required_decoys_count > 0 && !is_auditable()) + { + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request decoys_req = AUTO_VAL_INIT(decoys_req); + decoys_req.height_upper_limit = m_last_pow_block_h; // request decoys to be either older than, or the same age as stake output's height + decoys_req.use_forced_mix_outs = use_only_forced_to_mix; + decoys_req.decoys_count = required_decoys_count + 1; // one more to be able to skip a decoy in case it hits the real output + decoys_req.amounts.push_back(0); // request one batch of decoys for hidden amounts + + r = m_core_proxy->call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS(decoys_req, decoys_resp); + // TODO @#@# do we need these exceptions? + THROW_IF_FALSE_WALLET_EX(r, error::no_connection_to_daemon, "getrandom_outs1.bin"); + THROW_IF_FALSE_WALLET_EX(decoys_resp.status != API_RETURN_CODE_BUSY, error::daemon_busy, "getrandom_outs1.bin"); + THROW_IF_FALSE_WALLET_EX(decoys_resp.status == API_RETURN_CODE_OK, error::get_random_outs_error, decoys_resp.status); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(decoys_resp.outs.size() == 1, "got wrong number of decoys batches: " << decoys_resp.outs.size()); + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(decoys_resp.outs[0].outs.size() == required_decoys_count + 1, "for PoS stake tx got less decoys to mix than requested: " << decoys_resp.outs[0].outs.size() << " < " << required_decoys_count + 1); + + auto& decoys = decoys_resp.outs[0].outs; + decoys.emplace_front(td.m_global_output_index, stake_out.stealth_address, stake_out.amount_commitment, stake_out.concealing_point, stake_out.blinded_asset_id); + + std::unordered_set used_gindices; + size_t good_outs_count = 0; + for (auto it = decoys.begin(); it != decoys.end(); ) { - WLT_LOG_YELLOW("POS MINING NOT ALLOWED YET", LOG_LEVEL_0); - return true; - } - - if (!ctx.is_pos_sequence_factor_good) - { - WLT_LOG_YELLOW("PoS sequence factor is too high, waiting for a PoW block...", LOG_LEVEL_0); - return true; - } - - std::atomic stop(false); - scan_pos(ctx, stop, [this]() { - size_t blocks_fetched; - refresh(blocks_fetched); - if (blocks_fetched) - { - WLT_LOG_L0("Detected new block, minting interrupted"); - return false; - } - return true; - }, m_core_runtime_config); - - bool res = true; - if (ctx.status == API_RETURN_CODE_OK) - { - res = build_minted_block(ctx, miner_address); - } - TIME_MEASURE_FINISH_MS(mining_duration_ms); - - WLT_LOG_L0("PoS mining: " << ctx.iterations_processed << " iterations finished (" << std::fixed << std::setprecision(2) << (mining_duration_ms / 1000.0f) << "s), status: " << ctx.status << ", " << ctx.total_items_checked << " entries with total amount: " << print_money_brief(ctx.total_amount_checked)); - - return res; - } - //------------------------------------------------------------------ - void wallet2::do_pos_mining_prepare_entry(mining_context& context, const transfer_details& td) - { - //CHECK_AND_ASSERT_MES_NO_RET(transfer_index < m_transfers.size(), "transfer_index is out of bounds: " << transfer_index); - //const transfer_details& td = m_transfers.at(transfer_index); - - crypto::scalar_t amount_blinding_mask{}; - if (td.is_zc()) - amount_blinding_mask = td.m_zc_info_ptr->amount_blinding_mask; - - context.prepare_entry(td.amount(), td.m_key_image, get_tx_pub_key_from_extra(td.m_ptx_wallet_info->m_tx), td.m_internal_output_index, - amount_blinding_mask, m_account.get_keys().view_secret_key); - } - //------------------------------------------------------------------ - bool wallet2::do_pos_mining_iteration(mining_context& context, uint64_t ts) - { - return context.do_iteration(ts); - } - //------------------------------- - bool wallet2::reset_history() - { - std::string pass = m_password; - std::wstring file_path = m_wallet_file; - account_base acc_tmp = m_account; - clear(); - m_account = acc_tmp; - m_password = pass; - prepare_file_names(file_path); - return true; - } - //------------------------------- - bool wallet2::build_minted_block(const mining_context& cxt) - { - return build_minted_block(cxt, m_account.get_public_address()); - } - //------------------------------------------------------------------ - std::string wallet2::get_extra_text_for_block(uint64_t new_block_expected_height) - { - size_t entries_voted = 0; - std::string extra_text = "{"; - for (const auto& e : m_votes_config.entries) - { - if (e.h_start <= new_block_expected_height && e.h_end >= new_block_expected_height) - { - //do vote for/against this - if (entries_voted != 0) - extra_text += ","; - extra_text += "\""; - extra_text += e.proposal_id; - extra_text += "\":"; - extra_text += e.vote ? "1" : "0"; - entries_voted++; - } - } - extra_text += "}"; - if (!entries_voted) - extra_text = ""; - return extra_text; - } - //------------------------------------------------------------------ - bool wallet2::build_minted_block(const mining_context& cxt, const currency::account_public_address& miner_address) - { - //found a block, construct it, sign and push to daemon - WLT_LOG_GREEN("Found kernel, constructing block", LOG_LEVEL_0); - - //WLT_CHECK_AND_ASSERT_MES(cxt.index < m_transfers.size(), false, "cxt.index = " << cxt.index << " is out of bounds"); - const transfer_details& td = m_transfers.at(cxt.index); - - currency::COMMAND_RPC_GETBLOCKTEMPLATE::request tmpl_req = AUTO_VAL_INIT(tmpl_req); - currency::COMMAND_RPC_GETBLOCKTEMPLATE::response tmpl_rsp = AUTO_VAL_INIT(tmpl_rsp); - tmpl_req.wallet_address = get_account_address_as_str(miner_address); - tmpl_req.stakeholder_address = get_account_address_as_str(m_account.get_public_address()); - tmpl_req.pos_block = true; - tmpl_req.extra_text = get_extra_text_for_block(m_chain.get_top_block_height()); - - tmpl_req.pe = AUTO_VAL_INIT(tmpl_req.pe); - tmpl_req.pe.amount = td.amount(); - tmpl_req.pe.block_timestamp = td.m_ptx_wallet_info->m_block_timestamp; - tmpl_req.pe.g_index = td.m_global_output_index; - tmpl_req.pe.keyimage = td.m_key_image; - tmpl_req.pe.stake_unlock_time = cxt.stake_unlock_time; - tmpl_req.pe.tx_id = td.tx_hash(); - tmpl_req.pe.tx_out_index = td.m_internal_output_index; - tmpl_req.pe.wallet_index = cxt.index; - - // mark stake source as spent and make sure it will be restored in case of error - const std::vector stake_transfer_idx_vec{ cxt.index }; - mark_transfers_as_spent(stake_transfer_idx_vec, "stake source"); - bool gracefull_leaving = false; - auto stake_transfer_spent_flag_restorer = epee::misc_utils::create_scope_leave_handler([&]() { - if (!gracefull_leaving) - clear_transfers_from_flag(stake_transfer_idx_vec, WALLET_TRANSFER_DETAIL_FLAG_SPENT, "stake source"); - }); - - // generate UTXO Defragmentation Transaction - to reduce the UTXO set size - transaction udtx{}; - if (generate_utxo_defragmentation_transaction_if_needed(udtx)) - { - tx_to_blob(udtx, tmpl_req.explicit_transaction); - WLT_LOG_GREEN("Note: " << udtx.vin.size() << " inputs were aggregated into UTXO defragmentation tx " << get_transaction_hash(udtx), LOG_LEVEL_0); - } - m_core_proxy->call_COMMAND_RPC_GETBLOCKTEMPLATE(tmpl_req, tmpl_rsp); - WLT_CHECK_AND_ASSERT_MES(tmpl_rsp.status == API_RETURN_CODE_OK, false, "Failed to create block template after kernel hash found! Status: " << tmpl_rsp.status); - - currency::block b = AUTO_VAL_INIT(b); - currency::blobdata block_blob; - bool res = epee::string_tools::parse_hexstr_to_binbuff(tmpl_rsp.blocktemplate_blob, block_blob); - WLT_CHECK_AND_ASSERT_MES(res, false, "parse_hexstr_to_binbuff() failed after kernel hash found!"); - res = parse_and_validate_block_from_blob(block_blob, b); - WLT_CHECK_AND_ASSERT_MES(res, false, "parse_and_validate_block_from_blob() failed after kernel hash found!"); - - if (cxt.last_block_hash != b.prev_id) - { - WLT_LOG_YELLOW("Kernel was found but block is behindhand, b.prev_id=" << b.prev_id << ", last_block_hash=" << cxt.last_block_hash, LOG_LEVEL_0); - return false; - } - - // set the timestamp from stake kernel - b.timestamp = cxt.sk.block_timestamp; - uint64_t current_timestamp = m_core_runtime_config.get_core_time(); - set_block_datetime(current_timestamp, b); - WLT_LOG_MAGENTA("Applying actual timestamp: " << current_timestamp, LOG_LEVEL_2); - - res = prepare_and_sign_pos_block(cxt, tmpl_rsp.block_reward, tmpl_req.pe, tmpl_rsp.miner_tx_tgc, b); - WLT_CHECK_AND_ASSERT_MES(res, false, "Failed to prepare_and_sign_pos_block"); - - crypto::hash block_hash = get_block_hash(b); - WLT_LOG_GREEN("Block " << print16(block_hash) << " @ " << get_block_height(b) << " has been constructed, sending to core...", LOG_LEVEL_0); - - currency::COMMAND_RPC_SUBMITBLOCK2::request subm_req = AUTO_VAL_INIT(subm_req); - currency::COMMAND_RPC_SUBMITBLOCK2::response subm_rsp = AUTO_VAL_INIT(subm_rsp); - subm_req.b = t_serializable_object_to_blob(b); - if (tmpl_req.explicit_transaction.size()) - subm_req.explicit_txs.push_back(hexemizer{ tmpl_req.explicit_transaction }); - - m_core_proxy->call_COMMAND_RPC_SUBMITBLOCK2(subm_req, subm_rsp); - if (subm_rsp.status != API_RETURN_CODE_OK) - { - WLT_LOG_ERROR("Constructed block " << print16(block_hash) << " was rejected by the core, status: " << subm_rsp.status); - return false; - } - WLT_LOG_GREEN("PoS block " << print16(block_hash) << " generated and accepted, congrats!", LOG_LEVEL_0); - m_wcallback->on_pos_block_found(b); - - gracefull_leaving = true; // to prevent source transfer flags be cleared in scope leave handler - return true; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::get_unconfirmed_transfers(std::vector& trs, bool exclude_mining_txs) - { - for (auto& u : m_unconfirmed_txs) - { - if (exclude_mining_txs && currency::is_coinbase(u.second.tx)) + if (used_gindices.count(it->global_amount_index) != 0) { + it = decoys.erase(it); continue; } - trs.push_back(u.second); - load_wallet_transfer_info_flags(trs.back()); + used_gindices.insert(it->global_amount_index); + if (++good_outs_count == required_decoys_count + 1) + { + decoys.erase(++it, decoys.end()); + break; + } + ++it; } - } - //---------------------------------------------------------------------------------------------------- - void wallet2::set_core_runtime_config(const currency::core_runtime_config& pc) - { - m_core_runtime_config = pc; - } - //---------------------------------------------------------------------------------------------------- - currency::core_runtime_config& wallet2::get_core_runtime_config() - { - return m_core_runtime_config; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::is_transfer_unlocked(const transfer_details& td) const - { - uint64_t stub = 0; - return is_transfer_unlocked(td, false, stub); - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::is_transfer_unlocked(const transfer_details& td, bool for_pos_mining, uint64_t& stake_lock_time) const - { - if (td.m_flags & WALLET_TRANSFER_DETAIL_FLAG_BLOCKED) - return false; + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(decoys.size() == required_decoys_count + 1, "for PoS stake got less good decoys than required: " << decoys.size() << " < " << required_decoys_count); - if (td.m_ptx_wallet_info->m_block_height + WALLET_DEFAULT_TX_SPENDABLE_AGE > get_blockchain_current_size()) - return false; + decoys.sort([](auto& l, auto& r) { return l.global_amount_index < r.global_amount_index; }); // sort them now (note absolute_sorted_output_offsets_to_relative_in_place() below) - - - uint64_t unlock_time = get_tx_unlock_time(td.m_ptx_wallet_info->m_tx, td.m_internal_output_index); - if (for_pos_mining && m_core_runtime_config.is_hardfork_active_for_height(1, get_blockchain_current_size())) + uint64_t i = 0; + for (auto& el : decoys) { - //allowed of staking locked coins with - stake_lock_time = unlock_time; - } - else - { - if (!currency::is_tx_spendtime_unlocked(unlock_time, get_blockchain_current_size(), m_core_runtime_config.get_core_time())) - return false; + uint64_t gindex = el.global_amount_index; + if (gindex == td.m_global_output_index) + secret_index = i; + ++i; + ring.emplace_back(el.stealth_address, el.amount_commitment, el.blinded_asset_id, el.concealing_point); + stake_input.key_offsets.push_back(el.global_amount_index); } + r = absolute_sorted_output_offsets_to_relative_in_place(stake_input.key_offsets); + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "absolute_sorted_output_offsets_to_relative_in_place failed"); + } + else + { + // no decoys, the ring consist of one element -- the real stake output + ring.emplace_back(stake_out.stealth_address, stake_out.amount_commitment, stake_out.blinded_asset_id, stake_out.concealing_point); + stake_input.key_offsets.push_back(td.m_global_output_index); + } + stake_input.k_image = pe.keyimage; + + crypto::point_t stake_out_blinded_asset_id_pt = currency::native_coin_asset_id_pt + td.m_zc_info_ptr->asset_id_blinding_mask * crypto::c_point_X; +#ifndef NDEBUG + { + crypto::point_t source_amount_commitment = crypto::c_scalar_1div8 * td.m_amount * stake_out_blinded_asset_id_pt + crypto::c_scalar_1div8 * td.m_zc_info_ptr->amount_blinding_mask * crypto::c_point_G; + WLT_CHECK_AND_ASSERT_MES(stake_out.amount_commitment == source_amount_commitment.to_public_key(), false, "real output amount commitment check failed"); + WLT_CHECK_AND_ASSERT_MES(ring[secret_index].amount_commitment == stake_out.amount_commitment, false, "ring secret member doesn't match with the stake output"); + WLT_CHECK_AND_ASSERT_MES(cxt.stake_amount == td.m_amount, false, "stake_amount missmatch"); + } +#endif + + crypto::hash hash_for_zarcanum_sig = get_block_hash(b); + + WLT_CHECK_AND_ASSERT_MES(miner_tx_tgc.pseudo_out_amount_blinding_masks_sum.is_zero(), false, "pseudo_out_amount_blinding_masks_sum is nonzero"); // it should be zero because there's only one ZC input (stake), and thus only one pseudo out (the sum is non-zero iff POC > 1) + crypto::scalar_t pseudo_out_amount_blinding_mask = miner_tx_tgc.amount_blinding_masks_sum; // sum of outputs' amount blinding masks + + miner_tx_tgc.pseudo_outs_blinded_asset_ids.emplace_back(currency::native_coin_asset_id_pt); // for Zarcanum stake inputs pseudo outputs commitments has explicit native asset id + miner_tx_tgc.pseudo_outs_plus_real_out_blinding_masks.emplace_back(0); + miner_tx_tgc.real_zc_ins_asset_ids.emplace_back(td.m_zc_info_ptr->asset_id); + // TODO @#@# [architecture] the same value is calculated in zarcanum_generate_proof(), consider an impovement + miner_tx_tgc.pseudo_out_amount_commitments_sum += cxt.stake_amount * stake_out_blinded_asset_id_pt + pseudo_out_amount_blinding_mask * crypto::c_point_G; + miner_tx_tgc.real_in_asset_id_blinding_mask_x_amount_sum += td.m_zc_info_ptr->asset_id_blinding_mask * cxt.stake_amount; + + uint8_t err = 0; + r = crypto::zarcanum_generate_proof(hash_for_zarcanum_sig, cxt.kernel_hash, ring, cxt.last_pow_block_id_hashed, cxt.sk.kimage, + secret_x, cxt.secret_q, secret_index, cxt.stake_amount, td.m_zc_info_ptr->asset_id_blinding_mask, cxt.stake_out_amount_blinding_mask, pseudo_out_amount_blinding_mask, + static_cast(sig), &err); + WLT_CHECK_AND_ASSERT_MES(r, false, "zarcanum_generate_proof failed, err: " << (int)err); + + // + // The miner tx prefix should be sealed by now, and the tx hash should be defined. + // Any changes made below should only affect the signatures/proofs and should not impact the prefix hash calculation. + // + crypto::hash miner_tx_id = get_transaction_hash(b.miner_tx); + + // proofs for miner_tx + + // asset surjection proof + currency::zc_asset_surjection_proof asp{}; + r = generate_asset_surjection_proof(miner_tx_id, false, miner_tx_tgc, asp); // has_non_zc_inputs == false because after the HF4 PoS mining is only allowed for ZC stakes inputs + WLT_CHECK_AND_ASSERT_MES(r, false, "generete_asset_surjection_proof failed"); + b.miner_tx.proofs.emplace_back(std::move(asp)); + + // range proofs + currency::zc_outs_range_proof range_proofs{}; + r = generate_zc_outs_range_proof(miner_tx_id, 0, miner_tx_tgc, b.miner_tx.vout, range_proofs); + WLT_CHECK_AND_ASSERT_MES(r, false, "Failed to generate zc_outs_range_proof()"); + b.miner_tx.proofs.emplace_back(std::move(range_proofs)); + + // balance proof + currency::zc_balance_proof balance_proof{}; + r = generate_tx_balance_proof(b.miner_tx, miner_tx_id, miner_tx_tgc, full_block_reward, balance_proof); + WLT_CHECK_AND_ASSERT_MES(r, false, "generate_tx_balance_proof failed"); + b.miner_tx.proofs.emplace_back(std::move(balance_proof)); + + // the following line are for debugging when necessary -- sowle + //err = 0; + //r = crypto::zarcanum_verify_proof(hash_for_zarcanum_sig, cxt.kernel_hash, ring, cxt.last_pow_block_id_hashed, cxt.sk.kimage, cxt.basic_diff, sig, &err); + //WLT_CHECK_AND_ASSERT_MES(r, false, "zarcanum_verify_proof failed with code " << (int)err); + + return true; +} +//------------------------------------------------------------------ +bool wallet2::fill_mining_context(mining_context& ctx) +{ + currency::COMMAND_RPC_GET_POS_MINING_DETAILS::request pos_details_req = AUTO_VAL_INIT(pos_details_req); + currency::COMMAND_RPC_GET_POS_MINING_DETAILS::response pos_details_resp = AUTO_VAL_INIT(pos_details_resp); + m_core_proxy->call_COMMAND_RPC_GET_POS_MINING_DETAILS(pos_details_req, pos_details_resp); + if (pos_details_resp.status != API_RETURN_CODE_OK) + return false; + + ctx = mining_context{}; + ctx.init(wide_difficulty_type(pos_details_resp.pos_basic_difficulty), pos_details_resp.sm, is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM)); + + ctx.last_block_hash = pos_details_resp.last_block_hash; + ctx.is_pos_allowed = pos_details_resp.pos_mining_allowed; + ctx.is_pos_sequence_factor_good = pos_details_resp.pos_sequence_factor_is_good; + ctx.starter_timestamp = pos_details_resp.starter_timestamp; + ctx.status = API_RETURN_CODE_NOT_FOUND; + return true; +} +//------------------------------------------------------------------ +bool wallet2::try_mint_pos() +{ + return try_mint_pos(m_account.get_public_address()); +} +//------------------------------------------------------------------ +bool wallet2::try_mint_pos(const currency::account_public_address& miner_address) +{ + TIME_MEASURE_START_MS(mining_duration_ms); + mining_context ctx = AUTO_VAL_INIT(ctx); + WLT_LOG_L2("Starting PoS mining iteration"); + fill_mining_context(ctx); + + if (!ctx.is_pos_allowed) + { + WLT_LOG_YELLOW("POS MINING NOT ALLOWED YET", LOG_LEVEL_0); return true; } - //---------------------------------------------------------------------------------------------------- - void wallet2::push_offer(const bc_services::offer_details_ex& od, currency::transaction& res_tx) + + if (!ctx.is_pos_sequence_factor_good) { - currency::tx_destination_entry tx_dest; - tx_dest.addr.push_back(m_account.get_keys().account_address); - tx_dest.amount = m_core_runtime_config.tx_default_fee; - std::vector destinations; - std::vector extra; - std::vector attachments; - - bc_services::put_offer_into_attachment(static_cast(od), attachments); - - destinations.push_back(tx_dest); - transfer(destinations, 0, 0, od.fee, extra, attachments, get_current_split_strategy(), tx_dust_policy(DEFAULT_DUST_THRESHOLD), res_tx); - } - //---------------------------------------------------------------------------------------------------- - const transaction& wallet2::get_transaction_by_id(const crypto::hash& tx_hash) - { - for (auto it = m_transfer_history.rbegin(); it != m_transfer_history.rend(); it++) - { - if (it->tx_hash == tx_hash) - return it->tx; - } - ASSERT_MES_AND_THROW("Tx " << tx_hash << " not found in wallet"); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::cancel_offer_by_id(const crypto::hash& tx_id, uint64_t of_ind, uint64_t fee, currency::transaction& res_tx) - { - std::vector extra; - std::vector attachments; - bc_services::cancel_offer co = AUTO_VAL_INIT(co); - co.offer_index = of_ind; - co.tx_id = tx_id; - const transaction& related_tx = get_transaction_by_id(tx_id); - crypto::public_key related_tx_pub_key = get_tx_pub_key_from_extra(related_tx); - currency::keypair ephemeral = AUTO_VAL_INIT(ephemeral); - bool r = currency::derive_ephemeral_key_helper(m_account.get_keys(), related_tx_pub_key, of_ind, ephemeral); - CHECK_AND_ASSERT_THROW_MES(r, "derive_ephemeral_key_helper failed, tx_id: " << tx_id << ", of_ind:" << of_ind); - - blobdata sig_blob = bc_services::make_offer_sig_blob(co); - crypto::generate_signature(crypto::cn_fast_hash(sig_blob.data(), sig_blob.size()), ephemeral.pub, ephemeral.sec, co.sig); - bc_services::put_offer_into_attachment(co, attachments); - - transfer(std::vector(), 0, 0, fee, extra, attachments, get_current_split_strategy(), 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) - { - currency::tx_destination_entry tx_dest; - tx_dest.addr.push_back(m_account.get_keys().account_address); - tx_dest.amount = m_core_runtime_config.tx_default_fee; - std::vector destinations; - std::vector extra; - std::vector attachments; - bc_services::update_offer uo = AUTO_VAL_INIT(uo); - uo.offer_index = of_ind; - uo.tx_id = tx_id; - uo.of = od; - const transaction& related_tx = get_transaction_by_id(tx_id); - crypto::public_key related_tx_pub_key = get_tx_pub_key_from_extra(related_tx); - currency::keypair ephemeral; - bool r = currency::derive_ephemeral_key_helper(m_account.get_keys(), related_tx_pub_key, of_ind, ephemeral); - CHECK_AND_ASSERT_THROW_MES(r, "Failed to derive_ephemeral_key_helper" << tx_id); - - blobdata sig_blob = bc_services::make_offer_sig_blob(uo); - crypto::generate_signature(crypto::cn_fast_hash(sig_blob.data(), sig_blob.size()), ephemeral.pub, ephemeral.sec, uo.sig); - bc_services::put_offer_into_attachment(uo, attachments); - - destinations.push_back(tx_dest); - transfer(destinations, 0, 0, od.fee, extra, attachments, get_current_split_strategy(), tx_dust_policy(DEFAULT_DUST_THRESHOLD), res_tx); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::push_alias_info_to_extra_according_to_hf_status(const currency::extra_alias_entry& ai, std::vector& extra) - { - if (m_core_runtime_config.is_hardfork_active_for_height(2, get_top_block_height())) - { - // after HF2 - extra.push_back(ai); - } - else - { - // before HF2 - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(!ai.m_address.is_auditable(), "auditable addresses are not supported in aliases prior to HF2"); - extra.push_back(ai.to_old()); - } - } - //---------------------------------------------------------------------------------------------------- - uint64_t wallet2::get_alias_cost(const std::string& alias) - { - currency::COMMAND_RPC_GET_ALIAS_REWARD::request req = AUTO_VAL_INIT(req); - currency::COMMAND_RPC_GET_ALIAS_REWARD::response rsp = AUTO_VAL_INIT(rsp); - req.alias = alias; - if (!m_core_proxy->call_COMMAND_RPC_GET_ALIAS_REWARD(req, rsp)) - { - throw std::runtime_error(std::string("Failed to get alias cost")); - } - - return rsp.reward; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::request_alias_registration(currency::extra_alias_entry& ai, currency::transaction& res_tx, uint64_t fee, uint64_t reward, const crypto::secret_key& authority_key) - { - if (!validate_alias_name(ai.m_alias)) - { - throw std::runtime_error(std::string("wrong alias characters: ") + ai.m_alias); - } - - if (ai.m_alias.size() < ALIAS_MINIMUM_PUBLIC_SHORT_NAME_ALLOWED) - { - if (authority_key == currency::null_skey) - { - throw std::runtime_error(std::string("Short aliases is not allowed without authority key: ") + ALIAS_SHORT_NAMES_VALIDATION_PUB_KEY); - } - crypto::public_key authority_pub = AUTO_VAL_INIT(authority_pub); - bool r = crypto::secret_key_to_public_key(authority_key, authority_pub); - CHECK_AND_ASSERT_THROW_MES(r, "Failed to generate pub key from secrete authority key"); - - if (string_tools::pod_to_hex(authority_pub) != ALIAS_SHORT_NAMES_VALIDATION_PUB_KEY) - { - throw std::runtime_error(std::string("Short aliases is not allowed to register by this authority key")); - } - r = currency::sign_extra_alias_entry(ai, authority_pub, authority_key); - CHECK_AND_ASSERT_THROW_MES(r, "Failed to sign alias update"); - WLT_LOG_L2("Generated update alias info: " << ENDL - << "alias: " << ai.m_alias << ENDL - << "signature: " << currency::print_t_array(ai.m_sign) << ENDL - << "signed(owner) pub key: " << m_account.get_keys().account_address.spend_public_key << ENDL - << "to address: " << get_account_address_as_str(ai.m_address) << ENDL - << "sign_buff_hash: " << currency::get_sign_buff_hash_for_alias_update(ai) - ); - } - - if (!reward) - { - reward = get_alias_cost(ai.m_alias); - } - - std::vector destinations; - std::vector extra; - std::vector attachments; - - push_alias_info_to_extra_according_to_hf_status(ai, extra); - - currency::tx_destination_entry tx_dest_alias_reward; - tx_dest_alias_reward.addr.resize(1); - get_aliases_reward_account(tx_dest_alias_reward.addr.back()); - tx_dest_alias_reward.amount = reward; - tx_dest_alias_reward.flags |= tx_destination_entry_flags::tdef_explicit_native_asset_id | tx_destination_entry_flags::tdef_zero_amount_blinding_mask; - destinations.push_back(tx_dest_alias_reward); - - transfer(destinations, 0, 0, fee, extra, attachments, get_current_split_strategy(), tx_dust_policy(DEFAULT_DUST_THRESHOLD), res_tx, CURRENCY_TO_KEY_OUT_RELAXED, false); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::deploy_new_asset(const currency::asset_descriptor_base& asset_info, const std::vector& destinations, currency::transaction& result_tx, crypto::public_key& new_asset_id) - { - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(asset_info.decimal_point <= 18, "too big decimal point: " << asset_info.decimal_point); - - asset_descriptor_operation asset_reg_info = AUTO_VAL_INIT(asset_reg_info); - asset_reg_info.descriptor = asset_info; - asset_reg_info.operation_type = ASSET_DESCRIPTOR_OPERATION_REGISTER; - construct_tx_param ctp = get_default_construct_tx_param(); - ctp.dsts = destinations; - ctp.extra.push_back(asset_reg_info); - ctp.need_at_least_1_zc = true; - - finalized_tx ft = AUTO_VAL_INIT(ft); - this->transfer(ctp, ft, true, nullptr); - result_tx = ft.tx; - //get generated asset id - currency::asset_descriptor_operation ado = AUTO_VAL_INIT(ado); - bool r = get_type_in_variant_container(result_tx.extra, ado); - CHECK_AND_ASSERT_THROW_MES(r, "Failed find asset info in tx"); - CHECK_AND_ASSERT_THROW_MES(get_or_calculate_asset_id(ado, nullptr, &new_asset_id), "get_or_calculate_asset_id failed"); - - m_custom_assets[new_asset_id] = ado.descriptor; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::emit_asset(const crypto::public_key asset_id, std::vector& destinations, currency::transaction& result_tx) - { - - auto own_asset_entry_it = m_own_asset_descriptors.find(asset_id); - CHECK_AND_ASSERT_THROW_MES(own_asset_entry_it != m_own_asset_descriptors.end(), "Failed find asset_id " << asset_id << " in own assets list"); - COMMAND_RPC_GET_ASSET_INFO::request req; - req.asset_id = asset_id; - COMMAND_RPC_GET_ASSET_INFO::response rsp; - bool r = m_core_proxy->call_COMMAND_RPC_GET_ASSET_INFO(req, rsp); - CHECK_AND_ASSERT_THROW_MES(r, "Failed to call_COMMAND_RPC_GET_ASSET_INFO"); - - asset_descriptor_operation asset_emmit_info = AUTO_VAL_INIT(asset_emmit_info); - asset_emmit_info.descriptor = rsp.asset_descriptor; - asset_emmit_info.operation_type = ASSET_DESCRIPTOR_OPERATION_EMIT; - asset_emmit_info.opt_asset_id = asset_id; - construct_tx_param ctp = get_default_construct_tx_param(); - ctp.dsts = destinations; - ctp.extra.push_back(asset_emmit_info); - ctp.need_at_least_1_zc = true; - ctp.ado_current_asset_owner = rsp.asset_descriptor.owner; - //ctp.asset_deploy_control_key = own_asset_entry_it->second.control_key; - - for (auto& dst : ctp.dsts) - { - dst.asset_id = null_pkey; // emit operation requires null_pkey for emitting asset outputs, fix it ad-hoc here - } - - finalized_tx ft = AUTO_VAL_INIT(ft); - this->transfer(ctp, ft, true, nullptr); - result_tx = ft.tx; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::update_asset(const crypto::public_key asset_id, const currency::asset_descriptor_base new_descriptor, currency::transaction& result_tx) - { - auto own_asset_entry_it = m_own_asset_descriptors.find(asset_id); - CHECK_AND_ASSERT_THROW_MES(own_asset_entry_it != m_own_asset_descriptors.end(), "Failed find asset_id " << asset_id << " in own assets list"); - - asset_descriptor_operation asset_update_info = AUTO_VAL_INIT(asset_update_info); - asset_update_info.descriptor = new_descriptor; - asset_update_info.operation_type = ASSET_DESCRIPTOR_OPERATION_UPDATE; - asset_update_info.opt_asset_id = asset_id; - construct_tx_param ctp = get_default_construct_tx_param(); - ctp.extra.push_back(asset_update_info); - ctp.need_at_least_1_zc = true; - currency::asset_descriptor_base adb = AUTO_VAL_INIT(adb); - bool r = this->daemon_get_asset_info(asset_id, adb); - CHECK_AND_ASSERT_THROW_MES(r, "Failed to get asset info from daemon"); - ctp.ado_current_asset_owner = adb.owner; - - finalized_tx ft = AUTO_VAL_INIT(ft); - this->transfer(ctp, ft, true, nullptr); - result_tx = ft.tx; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::transfer_asset_ownership(const crypto::public_key asset_id, const crypto::public_key& new_owner, currency::transaction& result_tx) - { - auto own_asset_entry_it = m_own_asset_descriptors.find(asset_id); - CHECK_AND_ASSERT_THROW_MES(own_asset_entry_it != m_own_asset_descriptors.end(), "Failed find asset_id " << asset_id << " in own assets list"); - - currency::asset_descriptor_base adb = AUTO_VAL_INIT(adb); - bool r = this->daemon_get_asset_info(asset_id, adb); - CHECK_AND_ASSERT_THROW_MES(r, "Failed to get asset info from daemon"); - - asset_descriptor_operation asset_update_info = AUTO_VAL_INIT(asset_update_info); - asset_update_info.descriptor = adb; - asset_update_info.operation_type = ASSET_DESCRIPTOR_OPERATION_UPDATE; - asset_update_info.opt_asset_id = asset_id; - asset_update_info.descriptor.owner = new_owner; - construct_tx_param ctp = get_default_construct_tx_param(); - ctp.ado_current_asset_owner = adb.owner; - ctp.extra.push_back(asset_update_info); - - finalized_tx ft = AUTO_VAL_INIT(ft); - this->transfer(ctp, ft, true, nullptr); - result_tx = ft.tx; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::burn_asset(const crypto::public_key asset_id, uint64_t amount_to_burn, currency::transaction& result_tx) - { - //auto own_asset_entry_it = m_own_asset_descriptors.find(asset_id); - //CHECK_AND_ASSERT_THROW_MES(own_asset_entry_it != m_own_asset_descriptors.end(), "Failed find asset_id " << asset_id << " in own assets list"); - COMMAND_RPC_GET_ASSET_INFO::request req; - req.asset_id = asset_id; - COMMAND_RPC_GET_ASSET_INFO::response rsp; - bool r = m_core_proxy->call_COMMAND_RPC_GET_ASSET_INFO(req, rsp); - CHECK_AND_ASSERT_THROW_MES(r, "Failed to call_COMMAND_RPC_GET_ASSET_INFO"); - - - - asset_descriptor_operation asset_burn_info = AUTO_VAL_INIT(asset_burn_info); - asset_burn_info.descriptor = rsp.asset_descriptor; - - CHECK_AND_ASSERT_THROW_MES(asset_burn_info.descriptor.current_supply >= amount_to_burn, "Wrong amount to burn (current_supply" << asset_burn_info.descriptor.current_supply << " is less then " << amount_to_burn << ")"); - - currency::tx_destination_entry dst_to_burn = AUTO_VAL_INIT(dst_to_burn); - dst_to_burn.amount = amount_to_burn; - dst_to_burn.asset_id = asset_id; - - asset_burn_info.operation_type = ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN; - asset_burn_info.opt_asset_id = asset_id; - construct_tx_param ctp = get_default_construct_tx_param(); - ctp.extra.push_back(asset_burn_info); - ctp.need_at_least_1_zc = true; - ctp.ado_current_asset_owner = rsp.asset_descriptor.owner; - ctp.dsts.push_back(dst_to_burn); - - finalized_tx ft = AUTO_VAL_INIT(ft); - this->transfer(ctp, ft, true, nullptr); - result_tx = ft.tx; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::daemon_get_asset_info(const crypto::public_key& asset_id, currency::asset_descriptor_base& adb) - { - COMMAND_RPC_GET_ASSET_INFO::request req; - req.asset_id = asset_id; - COMMAND_RPC_GET_ASSET_INFO::response rsp; - bool r = m_core_proxy->call_COMMAND_RPC_GET_ASSET_INFO(req, rsp); - CHECK_AND_ASSERT_MES(r, false, "Failed to call_COMMAND_RPC_GET_ASSET_INFO"); - adb = rsp.asset_descriptor; + WLT_LOG_YELLOW("PoS sequence factor is too high, waiting for a PoW block...", LOG_LEVEL_0); return true; } - //---------------------------------------------------------------------------------------------------- - void wallet2::request_alias_update(currency::extra_alias_entry& ai, currency::transaction& res_tx, uint64_t fee) - { - COMMAND_RPC_GET_ALIAS_DETAILS::request req; - req.alias = ai.m_alias; - COMMAND_RPC_GET_ALIAS_DETAILS::response rsp = AUTO_VAL_INIT(rsp); - bool r = m_core_proxy->call_COMMAND_RPC_GET_ALIAS_DETAILS(req, rsp); - CHECK_AND_ASSERT_THROW_MES(r, "Failed to call_COMMAND_RPC_GET_ALIAS_DETAILS"); - CHECK_AND_ASSERT_THROW_MES(rsp.status == API_RETURN_CODE_OK, "call_COMMAND_RPC_GET_ALIAS_DETAILS response: " << rsp.status); - - - currency::account_public_address addr = AUTO_VAL_INIT(addr); - currency::get_account_address_from_str(addr, rsp.alias_details.address); - - CHECK_AND_ASSERT_THROW_MES(m_account.get_public_address().spend_public_key == addr.spend_public_key && - m_account.get_public_address().view_public_key == addr.view_public_key, "call_COMMAND_RPC_GET_ALIAS_DETAILS: ownership is not confirmed"); - - if (!validate_alias_name(ai.m_alias)) + std::atomic stop(false); + scan_pos(ctx, stop, [this]() { + size_t blocks_fetched; + refresh(blocks_fetched); + if (blocks_fetched) { - throw std::runtime_error(std::string("wrong alias characters: ") + ai.m_alias); + WLT_LOG_L0("Detected new block, minting interrupted"); + return false; } - r = currency::sign_extra_alias_entry(ai, m_account.get_keys().account_address.spend_public_key, m_account.get_keys().spend_secret_key); + return true; + }, m_core_runtime_config); + + bool res = true; + if (ctx.status == API_RETURN_CODE_OK) + { + res = build_minted_block(ctx, miner_address); + } + TIME_MEASURE_FINISH_MS(mining_duration_ms); + + WLT_LOG_L0("PoS mining: " << ctx.iterations_processed << " iterations finished (" << std::fixed << std::setprecision(2) << (mining_duration_ms / 1000.0f) << "s), status: " << ctx.status << ", " << ctx.total_items_checked << " entries with total amount: " << print_money_brief(ctx.total_amount_checked)); + + return res; +} +//------------------------------------------------------------------ +void wallet2::do_pos_mining_prepare_entry(mining_context& context, const transfer_details& td) +{ + //CHECK_AND_ASSERT_MES_NO_RET(transfer_index < m_transfers.size(), "transfer_index is out of bounds: " << transfer_index); + //const transfer_details& td = m_transfers.at(transfer_index); + + crypto::scalar_t amount_blinding_mask{}; + if (td.is_zc()) + amount_blinding_mask = td.m_zc_info_ptr->amount_blinding_mask; + + context.prepare_entry(td.amount(), td.m_key_image, get_tx_pub_key_from_extra(td.m_ptx_wallet_info->m_tx), td.m_internal_output_index, + amount_blinding_mask, m_account.get_keys().view_secret_key); +} +//------------------------------------------------------------------ +bool wallet2::do_pos_mining_iteration(mining_context& context, uint64_t ts) +{ + return context.do_iteration(ts); +} +//------------------------------- +bool wallet2::reset_history() +{ + std::string pass = m_password; + std::wstring file_path = m_wallet_file; + account_base acc_tmp = m_account; + clear(); + m_account = acc_tmp; + m_password = pass; + prepare_file_names(file_path); + return true; +} +//------------------------------- +bool wallet2::build_minted_block(const mining_context& cxt) +{ + return build_minted_block(cxt, m_account.get_public_address()); +} +//------------------------------------------------------------------ +std::string wallet2::get_extra_text_for_block(uint64_t new_block_expected_height) +{ + size_t entries_voted = 0; + std::string extra_text = "{"; + for (const auto& e : m_votes_config.entries) + { + if (e.h_start <= new_block_expected_height && e.h_end >= new_block_expected_height) + { + //do vote for/against this + if (entries_voted != 0) + extra_text += ","; + extra_text += "\""; + extra_text += e.proposal_id; + extra_text += "\":"; + extra_text += e.vote ? "1" : "0"; + entries_voted++; + } + } + extra_text += "}"; + if (!entries_voted) + extra_text = ""; + return extra_text; +} +//------------------------------------------------------------------ +bool wallet2::build_minted_block(const mining_context& cxt, const currency::account_public_address& miner_address) +{ + //found a block, construct it, sign and push to daemon + WLT_LOG_GREEN("Found kernel, constructing block", LOG_LEVEL_0); + + //WLT_CHECK_AND_ASSERT_MES(cxt.index < m_transfers.size(), false, "cxt.index = " << cxt.index << " is out of bounds"); + const transfer_details& td = m_transfers.at(cxt.index); + + currency::COMMAND_RPC_GETBLOCKTEMPLATE::request tmpl_req = AUTO_VAL_INIT(tmpl_req); + currency::COMMAND_RPC_GETBLOCKTEMPLATE::response tmpl_rsp = AUTO_VAL_INIT(tmpl_rsp); + tmpl_req.wallet_address = get_account_address_as_str(miner_address); + tmpl_req.stakeholder_address = get_account_address_as_str(m_account.get_public_address()); + tmpl_req.pos_block = true; + tmpl_req.extra_text = get_extra_text_for_block(m_chain.get_top_block_height()); + + tmpl_req.pe = AUTO_VAL_INIT(tmpl_req.pe); + tmpl_req.pe.amount = td.amount(); + tmpl_req.pe.block_timestamp = td.m_ptx_wallet_info->m_block_timestamp; + tmpl_req.pe.g_index = td.m_global_output_index; + tmpl_req.pe.keyimage = td.m_key_image; + tmpl_req.pe.stake_unlock_time = cxt.stake_unlock_time; + tmpl_req.pe.tx_id = td.tx_hash(); + tmpl_req.pe.tx_out_index = td.m_internal_output_index; + tmpl_req.pe.wallet_index = cxt.index; + + // mark stake source as spent and make sure it will be restored in case of error + const std::vector stake_transfer_idx_vec{ cxt.index }; + mark_transfers_as_spent(stake_transfer_idx_vec, "stake source"); + bool gracefull_leaving = false; + auto stake_transfer_spent_flag_restorer = epee::misc_utils::create_scope_leave_handler([&]() { + if (!gracefull_leaving) + clear_transfers_from_flag(stake_transfer_idx_vec, WALLET_TRANSFER_DETAIL_FLAG_SPENT, "stake source"); + }); + + // generate UTXO Defragmentation Transaction - to reduce the UTXO set size + transaction udtx{}; + if (generate_utxo_defragmentation_transaction_if_needed(udtx)) + { + tx_to_blob(udtx, tmpl_req.explicit_transaction); + WLT_LOG_GREEN("Note: " << udtx.vin.size() << " inputs were aggregated into UTXO defragmentation tx " << get_transaction_hash(udtx), LOG_LEVEL_0); + } + m_core_proxy->call_COMMAND_RPC_GETBLOCKTEMPLATE(tmpl_req, tmpl_rsp); + WLT_CHECK_AND_ASSERT_MES(tmpl_rsp.status == API_RETURN_CODE_OK, false, "Failed to create block template after kernel hash found! Status: " << tmpl_rsp.status); + + currency::block b = AUTO_VAL_INIT(b); + currency::blobdata block_blob; + bool res = epee::string_tools::parse_hexstr_to_binbuff(tmpl_rsp.blocktemplate_blob, block_blob); + WLT_CHECK_AND_ASSERT_MES(res, false, "parse_hexstr_to_binbuff() failed after kernel hash found!"); + res = parse_and_validate_block_from_blob(block_blob, b); + WLT_CHECK_AND_ASSERT_MES(res, false, "parse_and_validate_block_from_blob() failed after kernel hash found!"); + + if (cxt.last_block_hash != b.prev_id) + { + WLT_LOG_YELLOW("Kernel was found but block is behindhand, b.prev_id=" << b.prev_id << ", last_block_hash=" << cxt.last_block_hash, LOG_LEVEL_0); + return false; + } + + // set the timestamp from stake kernel + b.timestamp = cxt.sk.block_timestamp; + uint64_t current_timestamp = m_core_runtime_config.get_core_time(); + set_block_datetime(current_timestamp, b); + WLT_LOG_MAGENTA("Applying actual timestamp: " << current_timestamp, LOG_LEVEL_2); + + res = prepare_and_sign_pos_block(cxt, tmpl_rsp.block_reward, tmpl_req.pe, tmpl_rsp.miner_tx_tgc, b); + WLT_CHECK_AND_ASSERT_MES(res, false, "Failed to prepare_and_sign_pos_block"); + + crypto::hash block_hash = get_block_hash(b); + WLT_LOG_GREEN("Block " << print16(block_hash) << " @ " << get_block_height(b) << " has been constructed, sending to core...", LOG_LEVEL_0); + + currency::COMMAND_RPC_SUBMITBLOCK2::request subm_req = AUTO_VAL_INIT(subm_req); + currency::COMMAND_RPC_SUBMITBLOCK2::response subm_rsp = AUTO_VAL_INIT(subm_rsp); + subm_req.b = t_serializable_object_to_blob(b); + if (tmpl_req.explicit_transaction.size()) + subm_req.explicit_txs.push_back(hexemizer{ tmpl_req.explicit_transaction }); + + m_core_proxy->call_COMMAND_RPC_SUBMITBLOCK2(subm_req, subm_rsp); + if (subm_rsp.status != API_RETURN_CODE_OK) + { + WLT_LOG_ERROR("Constructed block " << print16(block_hash) << " was rejected by the core, status: " << subm_rsp.status); + return false; + } + WLT_LOG_GREEN("PoS block " << print16(block_hash) << " generated and accepted, congrats!", LOG_LEVEL_0); + m_wcallback->on_pos_block_found(b); + + gracefull_leaving = true; // to prevent source transfer flags be cleared in scope leave handler + return true; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::get_unconfirmed_transfers(std::vector& trs, bool exclude_mining_txs) +{ + for (auto& u : m_unconfirmed_txs) + { + if (exclude_mining_txs && currency::is_coinbase(u.second.tx)) + { + continue; + } + trs.push_back(u.second); + load_wallet_transfer_info_flags(trs.back()); + } +} +//---------------------------------------------------------------------------------------------------- +void wallet2::set_core_runtime_config(const currency::core_runtime_config& pc) +{ + m_core_runtime_config = pc; +} +//---------------------------------------------------------------------------------------------------- +currency::core_runtime_config& wallet2::get_core_runtime_config() +{ + return m_core_runtime_config; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::is_transfer_unlocked(const transfer_details& td) const +{ + uint64_t stub = 0; + return is_transfer_unlocked(td, false, stub); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::is_transfer_unlocked(const transfer_details& td, bool for_pos_mining, uint64_t& stake_lock_time) const +{ + if (td.m_flags & WALLET_TRANSFER_DETAIL_FLAG_BLOCKED) + return false; + + if (td.m_ptx_wallet_info->m_block_height + WALLET_DEFAULT_TX_SPENDABLE_AGE > get_blockchain_current_size()) + return false; + + + + uint64_t unlock_time = get_tx_unlock_time(td.m_ptx_wallet_info->m_tx, td.m_internal_output_index); + if (for_pos_mining && m_core_runtime_config.is_hardfork_active_for_height(1, get_blockchain_current_size())) + { + //allowed of staking locked coins with + stake_lock_time = unlock_time; + } + else + { + if (!currency::is_tx_spendtime_unlocked(unlock_time, get_blockchain_current_size(), m_core_runtime_config.get_core_time())) + return false; + } + return true; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::push_offer(const bc_services::offer_details_ex& od, currency::transaction& res_tx) +{ + currency::tx_destination_entry tx_dest; + tx_dest.addr.push_back(m_account.get_keys().account_address); + tx_dest.amount = m_core_runtime_config.tx_default_fee; + std::vector destinations; + std::vector extra; + std::vector attachments; + + bc_services::put_offer_into_attachment(static_cast(od), attachments); + + destinations.push_back(tx_dest); + transfer(destinations, 0, 0, od.fee, extra, attachments, get_current_split_strategy(), tx_dust_policy(DEFAULT_DUST_THRESHOLD), res_tx); +} +//---------------------------------------------------------------------------------------------------- +const transaction& wallet2::get_transaction_by_id(const crypto::hash& tx_hash) +{ + for (auto it = m_transfer_history.rbegin(); it != m_transfer_history.rend(); it++) + { + if (it->tx_hash == tx_hash) + return it->tx; + } + ASSERT_MES_AND_THROW("Tx " << tx_hash << " not found in wallet"); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::cancel_offer_by_id(const crypto::hash& tx_id, uint64_t of_ind, uint64_t fee, currency::transaction& res_tx) +{ + std::vector extra; + std::vector attachments; + bc_services::cancel_offer co = AUTO_VAL_INIT(co); + co.offer_index = of_ind; + co.tx_id = tx_id; + const transaction& related_tx = get_transaction_by_id(tx_id); + crypto::public_key related_tx_pub_key = get_tx_pub_key_from_extra(related_tx); + currency::keypair ephemeral = AUTO_VAL_INIT(ephemeral); + bool r = currency::derive_ephemeral_key_helper(m_account.get_keys(), related_tx_pub_key, of_ind, ephemeral); + CHECK_AND_ASSERT_THROW_MES(r, "derive_ephemeral_key_helper failed, tx_id: " << tx_id << ", of_ind:" << of_ind); + + blobdata sig_blob = bc_services::make_offer_sig_blob(co); + crypto::generate_signature(crypto::cn_fast_hash(sig_blob.data(), sig_blob.size()), ephemeral.pub, ephemeral.sec, co.sig); + bc_services::put_offer_into_attachment(co, attachments); + + transfer(std::vector(), 0, 0, fee, extra, attachments, get_current_split_strategy(), 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) +{ + currency::tx_destination_entry tx_dest; + tx_dest.addr.push_back(m_account.get_keys().account_address); + tx_dest.amount = m_core_runtime_config.tx_default_fee; + std::vector destinations; + std::vector extra; + std::vector attachments; + bc_services::update_offer uo = AUTO_VAL_INIT(uo); + uo.offer_index = of_ind; + uo.tx_id = tx_id; + uo.of = od; + const transaction& related_tx = get_transaction_by_id(tx_id); + crypto::public_key related_tx_pub_key = get_tx_pub_key_from_extra(related_tx); + currency::keypair ephemeral; + bool r = currency::derive_ephemeral_key_helper(m_account.get_keys(), related_tx_pub_key, of_ind, ephemeral); + CHECK_AND_ASSERT_THROW_MES(r, "Failed to derive_ephemeral_key_helper" << tx_id); + + blobdata sig_blob = bc_services::make_offer_sig_blob(uo); + crypto::generate_signature(crypto::cn_fast_hash(sig_blob.data(), sig_blob.size()), ephemeral.pub, ephemeral.sec, uo.sig); + bc_services::put_offer_into_attachment(uo, attachments); + + destinations.push_back(tx_dest); + transfer(destinations, 0, 0, od.fee, extra, attachments, get_current_split_strategy(), tx_dust_policy(DEFAULT_DUST_THRESHOLD), res_tx); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::push_alias_info_to_extra_according_to_hf_status(const currency::extra_alias_entry& ai, std::vector& extra) +{ + if (m_core_runtime_config.is_hardfork_active_for_height(2, get_top_block_height())) + { + // after HF2 + extra.push_back(ai); + } + else + { + // before HF2 + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(!ai.m_address.is_auditable(), "auditable addresses are not supported in aliases prior to HF2"); + extra.push_back(ai.to_old()); + } +} +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::get_alias_cost(const std::string& alias) +{ + currency::COMMAND_RPC_GET_ALIAS_REWARD::request req = AUTO_VAL_INIT(req); + currency::COMMAND_RPC_GET_ALIAS_REWARD::response rsp = AUTO_VAL_INIT(rsp); + req.alias = alias; + if (!m_core_proxy->call_COMMAND_RPC_GET_ALIAS_REWARD(req, rsp)) + { + throw std::runtime_error(std::string("Failed to get alias cost")); + } + + return rsp.reward; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::request_alias_registration(currency::extra_alias_entry& ai, currency::transaction& res_tx, uint64_t fee, uint64_t reward, const crypto::secret_key& authority_key) +{ + if (!validate_alias_name(ai.m_alias)) + { + throw std::runtime_error(std::string("wrong alias characters: ") + ai.m_alias); + } + + if (ai.m_alias.size() < ALIAS_MINIMUM_PUBLIC_SHORT_NAME_ALLOWED) + { + if (authority_key == currency::null_skey) + { + throw std::runtime_error(std::string("Short aliases is not allowed without authority key: ") + ALIAS_SHORT_NAMES_VALIDATION_PUB_KEY); + } + crypto::public_key authority_pub = AUTO_VAL_INIT(authority_pub); + bool r = crypto::secret_key_to_public_key(authority_key, authority_pub); + CHECK_AND_ASSERT_THROW_MES(r, "Failed to generate pub key from secrete authority key"); + + if (string_tools::pod_to_hex(authority_pub) != ALIAS_SHORT_NAMES_VALIDATION_PUB_KEY) + { + throw std::runtime_error(std::string("Short aliases is not allowed to register by this authority key")); + } + r = currency::sign_extra_alias_entry(ai, authority_pub, authority_key); CHECK_AND_ASSERT_THROW_MES(r, "Failed to sign alias update"); WLT_LOG_L2("Generated update alias info: " << ENDL << "alias: " << ai.m_alias << ENDL << "signature: " << currency::print_t_array(ai.m_sign) << ENDL << "signed(owner) pub key: " << m_account.get_keys().account_address.spend_public_key << ENDL - << "transfered to address: " << get_account_address_as_str(ai.m_address) << ENDL + << "to address: " << get_account_address_as_str(ai.m_address) << ENDL << "sign_buff_hash: " << currency::get_sign_buff_hash_for_alias_update(ai) ); - - std::vector destinations; - std::vector extra; - std::vector attachments; - - push_alias_info_to_extra_according_to_hf_status(ai, extra); - - transfer(destinations, 0, 0, fee, extra, attachments, get_current_split_strategy(), tx_dust_policy(DEFAULT_DUST_THRESHOLD), res_tx, CURRENCY_TO_KEY_OUT_RELAXED, false); - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::check_available_sources(std::list& amounts) - { - /* - std::list > holds; - amounts.sort(); - bool res = true; - for (uint64_t am : amounts) - { - holds.push_back(std::vector()); - std::vector& current_holds = holds.back(); - uint64_t found = select_transfers(am, 0, DEFAULT_DUST_THRESHOLD, current_holds); - if (found < am) - { - res = false; - break; - } - mark_transfers_with_flag(current_holds, WALLET_TRANSFER_DETAIL_FLAG_BLOCKED, "check_available_sources"); - } - - for (auto& h: holds) - { - clear_transfers_from_flag(h, WALLET_TRANSFER_DETAIL_FLAG_BLOCKED, "check_available_sources"); - add_transfers_to_transfers_cache(h); - } - - - WLT_LOG_MAGENTA("[CHECK_AVAILABLE_SOURCES]: " << amounts << " res: " << res << ENDL <<" holds: " << holds, LOG_LEVEL_0); - return res; - */ - return false; - } - //---------------------------------------------------------------------------------------------------- - std::string get_random_rext(size_t len) - { - std::string buff(len / 2, 0); - crypto::generate_random_bytes(len / 2, (void*)buff.data()); - return string_tools::buff_to_hex_nodelimer(buff); - } - //---------------------------------------------------------------------------------------------------- - - // local_transfers_struct - structure to avoid copying the whole m_transfers - struct local_transfers_struct - { - local_transfers_struct(transfer_container& tf) :l_transfers_ref(tf) - {} - - transfer_container& l_transfers_ref; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(l_transfers_ref) - END_KV_SERIALIZE_MAP() - }; - - void wallet2::dump_trunsfers(std::stringstream& ss, bool verbose) const - { - if (verbose) - { - - ss << "{ \"transfers\": [" << ENDL; - for (const auto& tr : m_transfers) - { - uint64_t i = tr.first; - const transfer_details& td = tr.second; - ss << "{ \"i\": " << i << "," << ENDL; - ss << "\"entry\": " << epee::serialization::store_t_to_json(td) << "}," << ENDL; - } - } - else - { - boost::io::ios_flags_saver ifs(ss); - ss << "index amount spent_h g_index block block_ts flg tx out# key image" << ENDL; - for (const auto& tr : m_transfers) - { - uint64_t i = tr.first; - const transfer_details& td = tr.second; - ss << std::right << - std::setw(5) << i << " " << - std::setw(21) << print_money(td.amount()) << " " << - std::setw(7) << td.m_spent_height << " " << - std::setw(7) << td.m_global_output_index << " " << - std::setw(6) << td.m_ptx_wallet_info->m_block_height << " " << - std::setw(10) << td.m_ptx_wallet_info->m_block_timestamp << " " << - std::setw(4) << td.m_flags << " " << - get_transaction_hash(td.m_ptx_wallet_info->m_tx) << " " << - std::setw(4) << td.m_internal_output_index << " " << - td.m_key_image << ENDL; - } - } - } - //---------------------------------------------------------------------------------------------------- - std::string wallet2::dump_trunsfers(bool verbose) const - { - std::stringstream ss; - dump_trunsfers(ss, verbose); - return ss.str(); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::dump_key_images(std::stringstream& ss) - { - for (auto& ki : m_key_images) - { - ss << "[" << ki.first << "]: " << ki.second << ENDL; - } - } - void wallet2::get_multisig_transfers(multisig_transfer_container& ms_transfers) - { - ms_transfers = m_multisig_transfers; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::get_contracts(escrow_contracts_container& contracts) - { - contracts = m_contracts; - return true; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::build_escrow_release_templates(crypto::hash multisig_id, - uint64_t fee, - currency::transaction& tx_release_template, - currency::transaction& tx_burn_template, - const bc_services::contract_private_details& ecrow_details) - { - construct_tx_param construct_params = AUTO_VAL_INIT(construct_params); - construct_params.fee = fee; - construct_params.multisig_id = multisig_id; - construct_params.split_strategy_id = get_current_split_strategy(); - construct_params.dsts.resize(2); - //0 - addr_a - //1 - addr_b - construct_params.dsts[0].addr.push_back(ecrow_details.a_addr); - construct_params.dsts[1].addr.push_back(ecrow_details.b_addr); - - - //generate normal escrow release - construct_params.dsts[0].amount = ecrow_details.amount_a_pledge; - construct_params.dsts[1].amount = ecrow_details.amount_b_pledge + ecrow_details.amount_to_pay; - - //in case of ecrow_details.amount_a_pledge == 0 then exclude a - if (construct_params.dsts[0].amount == 0) - construct_params.dsts.erase(construct_params.dsts.begin()); - - - tx_service_attachment tsa = AUTO_VAL_INIT(tsa); - tsa.service_id = BC_ESCROW_SERVICE_ID; - tsa.instruction = BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_NORMAL; - construct_params.extra.push_back(tsa); - { - currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); - ftp.tx_version = this->get_current_tx_version(); - prepare_transaction(construct_params, ftp); - crypto::secret_key sk = AUTO_VAL_INIT(sk); - finalize_transaction(ftp, tx_release_template, sk, false); - } - - //generate burn escrow - construct_params.dsts.resize(1); - construct_params.dsts[0].addr.clear(); - construct_params.dsts[0].addr.push_back(null_pub_addr); - construct_params.dsts[0].amount = ecrow_details.amount_a_pledge + ecrow_details.amount_b_pledge + ecrow_details.amount_to_pay; - - construct_params.extra.clear(); - tsa.instruction = BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_BURN; - construct_params.extra.push_back(tsa); - { - currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); - ftp.tx_version = this->get_current_tx_version(); - prepare_transaction(construct_params, ftp); - crypto::secret_key sk = AUTO_VAL_INIT(sk); - finalize_transaction(ftp, tx_burn_template, sk, false); - } - } - //---------------------------------------------------------------------------------------------------- - void wallet2::build_escrow_cancel_template(crypto::hash multisig_id, - uint64_t expiration_period, - currency::transaction& tx_cancel_template, - const bc_services::contract_private_details& ecrow_details) - { - auto it = m_multisig_transfers.find(multisig_id); - THROW_IF_FALSE_WALLET_EX(it != m_multisig_transfers.end(), error::wallet_internal_error, - "unable to find multisig id"); - - THROW_IF_FALSE_WALLET_EX(it->second.amount() > (ecrow_details.amount_a_pledge + ecrow_details.amount_to_pay + ecrow_details.amount_b_pledge), error::wallet_internal_error, - "multisig id out amount no more than escrow total amount"); - - construct_tx_param construct_params = AUTO_VAL_INIT(construct_params); - currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); - ftp.tx_version = this->get_current_tx_version(); - 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 = get_current_split_strategy(); - construct_params.dsts.resize(2); - //0 - addr_a - //1 - addr_b - construct_params.dsts[0].addr.push_back(ecrow_details.a_addr); - construct_params.dsts[1].addr.push_back(ecrow_details.b_addr); - - - //generate cancel escrow proposal - construct_params.dsts[0].amount = ecrow_details.amount_a_pledge + ecrow_details.amount_to_pay; - construct_params.dsts[1].amount = ecrow_details.amount_b_pledge; - - if (construct_params.dsts[0].amount == 0) - construct_params.dsts.erase(construct_params.dsts.begin()); - else if (construct_params.dsts[1].amount == 0) - construct_params.dsts.erase(construct_params.dsts.begin() + 1); - - tx_service_attachment tsa = AUTO_VAL_INIT(tsa); - tsa.service_id = BC_ESCROW_SERVICE_ID; - tsa.instruction = BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_CANCEL; - construct_params.extra.push_back(tsa); - currency::etc_tx_details_expiration_time expir = AUTO_VAL_INIT(expir); - expir.v = m_core_runtime_config.get_core_time() + expiration_period; - construct_params.extra.push_back(expir); - - prepare_transaction(construct_params, ftp); - crypto::secret_key sk = AUTO_VAL_INIT(sk); - finalize_transaction(ftp, tx_cancel_template, sk, false); } - //---------------------------------------------------------------------------------------------------- - void wallet2::build_escrow_template(const bc_services::contract_private_details& ecrow_details, - size_t fake_outputs_count, - uint64_t unlock_time, - uint64_t expiration_time, - uint64_t b_release_fee, - const std::string& payment_id, - currency::transaction& tx, - std::vector& selected_transfers, - crypto::secret_key& one_time_key) + if (!reward) { - 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 = get_current_split_strategy(); - ctp.tx_outs_attr = CURRENCY_TO_KEY_OUT_RELAXED; - ctp.unlock_time = unlock_time; - - etc_tx_details_expiration_time t = AUTO_VAL_INIT(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; - ctp.extra.push_back(att); - - 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; - ctp.attachments.push_back(att); - } - - currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); - ftp.tx_version = this->get_current_tx_version(); - prepare_transaction(ctp, ftp); - - 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, const std::vector& received, uint64_t expiration, const crypto::hash& related_tx_id) - { - // check all elements in selected_transfers for being already mentioned in m_money_expirations - std::vector selected_transfers_local; - for (auto transfer_index : selected_transfers) - { - bool found = false; - for (auto it = m_money_expirations.begin(); !found && it != m_money_expirations.end(); ++it) - { - auto& st = it->selected_transfers; - found = std::find(st.begin(), st.end(), transfer_index) != st.end(); - } - if (!found) - selected_transfers_local.push_back(transfer_index); // populate selected_transfers_local only with indices, not containing in m_money_expirations - } - - if (selected_transfers_local.empty()) - return; - - m_money_expirations.push_back(AUTO_VAL_INIT(expiration_entry_info())); - m_money_expirations.back().expiration_time = expiration; - m_money_expirations.back().selected_transfers = selected_transfers_local; - m_money_expirations.back().related_tx_id = related_tx_id; - m_money_expirations.back().receved = received; - - std::stringstream ss; - for (auto tr_ind : m_money_expirations.back().selected_transfers) - { - //THROW_IF_FALSE_WALLET_INT_ERR_EX(tr_ind < m_transfers.size(), "invalid transfer index"); - uint32_t flags_before = m_transfers.at(tr_ind).m_flags; - m_transfers.at(tr_ind).m_flags |= WALLET_TRANSFER_DETAIL_FLAG_BLOCKED; - m_transfers.at(tr_ind).m_flags |= WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION; - ss << " " << std::right << std::setw(4) << tr_ind << " " << std::setw(21) << print_money(m_transfers.at(tr_ind).amount()) << " " - << std::setw(2) << std::left << flags_before << " -> " << std::setw(2) << std::left << m_transfers.at(tr_ind).m_flags << " " - << get_transaction_hash(m_transfers.at(tr_ind).m_ptx_wallet_info->m_tx) << std::endl; - } - WLT_LOG_GREEN(m_money_expirations.back().selected_transfers.size() << " transfer(s) added to expiration list:" << ENDL << - "index amount flags tx hash" << ENDL << - ss.str() << ", expire(s) at: " << expiration, LOG_LEVEL_0); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::remove_transfer_from_expiration_list(uint64_t transfer_index) - { - //THROW_IF_FALSE_WALLET_INT_ERR_EX(transfer_index < m_transfers.size(), "invalid transfer index"); - auto& tr_entry = m_transfers.at(transfer_index); - for (auto it = m_money_expirations.begin(); it != m_money_expirations.end(); /* nothing */) - { - auto& st = it->selected_transfers; - auto jt = std::find(st.begin(), st.end(), transfer_index); - if (jt != st.end()) - { - WLT_LOG_GREEN("Transfer [" << transfer_index << "], amount: " << print_money(tr_entry.amount()) << ", tx: " << get_transaction_hash(tr_entry.m_ptx_wallet_info->m_tx) << - " was removed from the expiration list", LOG_LEVEL_0); - st.erase(jt); - if (st.empty()) - { - it = m_money_expirations.erase(it); - continue; - } - } - ++it; - } - // clear proposal reservation flag and blocked flag - uint32_t flags_before = tr_entry.m_flags; - tr_entry.m_flags &= ~WALLET_TRANSFER_DETAIL_FLAG_BLOCKED; - tr_entry.m_flags &= ~WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION; - if (flags_before != tr_entry.m_flags) - { - WLT_LOG_BLUE("Transfer [" << transfer_index << "] was cleared from escrow proposal reservation, flags: " << flags_before << " -> " << tr_entry.m_flags << ", reason: intentional removing from expiration list", LOG_LEVEL_0); - } - - // (don't change m_spent flag, because transfer status is unclear - the caller should take care of it) - } - //---------------------------------------------------------------------------------------------------- - void wallet2::send_escrow_proposal(const wallet_public::create_proposal_param& wp, - currency::transaction& proposal_tx, - currency::transaction& escrow_template_tx) - { - return send_escrow_proposal(wp.details, wp.fake_outputs_count, wp.unlock_time, wp.expiration_period, wp.fee, wp.b_fee, wp.payment_id, proposal_tx, escrow_template_tx); - } - //---------------------------------------------------------------------------------------------------- - 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, - uint64_t fee, - uint64_t b_release_fee, - const std::string& payment_id, - currency::transaction& tx, - currency::transaction& template_tx) - { - if (!is_connected_to_net()) - { - THROW_IF_TRUE_WALLET_EX(true, error::wallet_internal_error, - "Transfer attempt while daemon offline"); - } - - 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; - 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, get_multisig_out_index(template_tx.vout)); - - 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, contract: " + epee::string_tools::pod_to_hex(ms_id)); - - 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; - 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); - - 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 = get_current_split_strategy(); - ctp.tx_outs_attr = CURRENCY_TO_KEY_OUT_RELAXED; - ctp.unlock_time = unlock_time; - - currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); - ftp.tx_version = this->get_current_tx_version(); - try - { - prepare_transaction(ctp, ftp); - crypto::secret_key sk = AUTO_VAL_INIT(sk); - finalize_transaction(ftp, tx, sk, false); - } - catch (...) - { - clear_transfers_from_flag(selected_transfers_for_template, mask_to_mark_escrow_template_locked_transfers, "undo prepared escrow template tx"); // don't forget to unlock template transfers if smth went wrong - add_transfers_to_transfers_cache(selected_transfers_for_template); - throw; - } - - send_transaction_to_network(tx); - - 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.attachments, ftp.prepared_destinations, ftp.selected_transfers); - - print_tx_sent_message(tx, "(from multisig)", fee); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::create_htlc_proposal(uint64_t amount, const currency::account_public_address& addr, uint64_t lock_blocks_count, currency::transaction& tx, const crypto::hash& htlc_hash, std::string& origin) - { - construct_tx_param ctp = get_default_construct_tx_param(); - ctp.fee = TX_DEFAULT_FEE; - ctp.dsts.resize(1); - ctp.dsts.back().addr.push_back(addr); - ctp.dsts.back().amount = amount; - destination_option_htlc_out& htlc_option = ctp.dsts.back().htlc_options; - htlc_option.expiration = lock_blocks_count; //about 12 hours - htlc_option.htlc_hash = htlc_hash; - - currency::create_and_add_tx_payer_to_container_from_address(ctp.extra, - get_account().get_keys().account_address, get_top_block_height(), get_core_runtime_config()); - - finalized_tx ft = AUTO_VAL_INIT(ft); - this->transfer(ctp, ft, true, nullptr); - origin = ft.htlc_origin; - tx = ft.tx; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::get_list_of_active_htlc(std::list& htlcs, bool only_redeem_txs) - { - for (auto htlc_entry : m_active_htlcs_txid) - { - //auto it = m_transfers.find(htlc_entry.second); - //if (it == m_transfers.end()) - // continue; - //const transfer_details& td = it->second; - const transfer_details& td = m_transfers.at(htlc_entry.second); - if (only_redeem_txs && !(td.m_flags & WALLET_TRANSFER_DETAIL_FLAG_HTLC_REDEEM)) - { - continue; - } - wallet_public::htlc_entry_info entry = AUTO_VAL_INIT(entry); - entry.tx_id = htlc_entry.first; - if (td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].type() != typeid(tx_out_bare)) - { - //@#@ - LOG_ERROR("Unexpected output type in get_list_of_active_htlc:" << td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].type().name()); - continue; - } - const tx_out_bare out_b = boost::get(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]); - entry.amount = out_b.amount; - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(out_b.target.type() == typeid(txout_htlc), - "[get_list_of_active_htlc]Internal error: unexpected type of out"); - const txout_htlc& htlc = boost::get(out_b.target); - entry.sha256_hash = htlc.htlc_hash; - - currency::tx_payer payer = AUTO_VAL_INIT(payer); - if (currency::get_type_in_variant_container(td.varian_options, payer)) - entry.counterparty_address = payer.acc_addr; - - entry.is_redeem = td.m_flags & WALLET_TRANSFER_DETAIL_FLAG_HTLC_REDEEM ? true : false; - htlcs.push_back(entry); - } - } - //---------------------------------------------------------------------------------------------------- - void wallet2::redeem_htlc(const crypto::hash& htlc_tx_id, const std::string& origin) - { - currency::transaction result_tx = AUTO_VAL_INIT(result_tx); - return redeem_htlc(htlc_tx_id, origin, result_tx); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::redeem_htlc(const crypto::hash& htlc_tx_id, const std::string& origin, currency::transaction& result_tx) - { - - construct_tx_param ctp = get_default_construct_tx_param(); - ctp.fee = TX_DEFAULT_FEE; - ctp.htlc_tx_id = htlc_tx_id; - ctp.htlc_origin = origin; - ctp.dsts.resize(1); - ctp.dsts.back().addr.push_back(m_account.get_keys().account_address); - - auto it = m_active_htlcs_txid.find(htlc_tx_id); - WLT_THROW_IF_FALSE_WITH_CODE(it != m_active_htlcs_txid.end(), - "htlc not found with tx_id = " << htlc_tx_id, API_RETURN_CODE_NOT_FOUND); - - ctp.dsts.back().amount = m_transfers.at(it->second).amount() - ctp.fee; - this->transfer(ctp, result_tx, true, nullptr); - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::check_htlc_redeemed(const crypto::hash& htlc_tx_id, std::string& origin, crypto::hash& redeem_tx_id) - { - auto it = m_active_htlcs_txid.find(htlc_tx_id); - - WLT_THROW_IF_FALSE_WITH_CODE(it != m_active_htlcs_txid.end(), - "htlc not found with tx_id = " << htlc_tx_id, API_RETURN_CODE_NOT_FOUND); - - transfer_details_extra_option_htlc_info htlc_options = AUTO_VAL_INIT(htlc_options); - if (!currency::get_type_in_variant_container(m_transfers.at(it->second).varian_options, htlc_options)) - { - return false; - } - if (htlc_options.origin.size()) - { - origin = htlc_options.origin; - redeem_tx_id = htlc_options.redeem_tx_id; - return true; - } - return false; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::create_ionic_swap_proposal(const wallet_public::ionic_swap_proposal_info& proposal_details, const currency::account_public_address& destination_addr, wallet_public::ionic_swap_proposal& proposal) - { - std::vector selected_transfers_for_template; - - return build_ionic_swap_template(proposal_details, destination_addr, proposal, selected_transfers_for_template); - - //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 ionic_swap"); - //return true; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::build_ionic_swap_template(const wallet_public::ionic_swap_proposal_info& proposal_detais, const currency::account_public_address& destination_addr, - wallet_public::ionic_swap_proposal& proposal, - std::vector& selected_transfers) - { - WLT_THROW_IF_FALSE_WITH_CODE(proposal_detais.fee_paid_by_a >= get_current_minimum_network_fee(), "Error at build_ionic_swap_template, ", API_RETURN_CODE_WALLET_FEE_TOO_LOW); - - construct_tx_param ctp = get_default_construct_tx_param(); - - //ctp.fake_outputs_count = proposal_detais.mixins; - ctp.fee = proposal_detais.fee_paid_by_a; - ctp.flags = TX_FLAG_SIGNATURE_MODE_SEPARATE; - ctp.mark_tx_as_complete = false; - ctp.crypt_address = destination_addr; - - ctp.dsts.resize(proposal_detais.to_finalizer.size() + proposal_detais.to_initiator.size()); - size_t i = 0; - // Here is an proposed for exchange funds - for (; i != proposal_detais.to_finalizer.size(); i++) - { - ctp.dsts[i].amount = proposal_detais.to_finalizer[i].amount; - ctp.dsts[i].amount_to_provide = proposal_detais.to_finalizer[i].amount; - ctp.dsts[i].flags |= tx_destination_entry_flags::tdef_explicit_amount_to_provide; - ctp.dsts[i].addr.push_back(destination_addr); - ctp.dsts[i].asset_id = proposal_detais.to_finalizer[i].asset_id; - } - // Here is an expected in return funds - std::vector for_expiration_list; - for (size_t j = 0; j != proposal_detais.to_initiator.size(); j++, i++) - { - ctp.dsts[i].amount = proposal_detais.to_initiator[j].amount; - ctp.dsts[i].amount_to_provide = 0; - ctp.dsts[i].flags |= tx_destination_entry_flags::tdef_explicit_amount_to_provide; - ctp.dsts[i].addr.push_back(m_account.get_public_address()); - ctp.dsts[i].asset_id = proposal_detais.to_initiator[j].asset_id; - for_expiration_list.push_back(payment_details_subtransfer{ ctp.dsts[i].asset_id, ctp.dsts[i].amount }); - } - - currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); - ftp.mode_separate_fee = ctp.fee; - ftp.tx_version = this->get_current_tx_version(); - prepare_transaction(ctp, ftp); - - selected_transfers = ftp.selected_transfers; - currency::finalized_tx finalize_result = AUTO_VAL_INIT(finalize_result); - finalize_transaction(ftp, finalize_result, false); - for (uint64_t i : selected_transfers) - m_transfers.at(i).m_flags &= ~WALLET_TRANSFER_DETAIL_FLAG_BLOCKED; - - //add_transfers_to_expiration_list(selected_transfers, for_expiration_list, this->get_core_runtime_config().get_core_time() + proposal_detais.expiration_time, currency::null_hash); - - //wrap it all - proposal.tx_template = finalize_result.tx; - wallet_public::ionic_swap_proposal_context ispc = AUTO_VAL_INIT(ispc); - ispc.gen_context = finalize_result.ftp.gen_context; - //ispc.one_time_skey = finalize_result.one_time_key; - std::string proposal_context_blob = t_serializable_object_to_blob(ispc); - proposal.encrypted_context = crypto::chacha_crypt(static_cast(proposal_context_blob), finalize_result.derivation); - return true; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::get_ionic_swap_proposal_info(const std::string& raw_proposal, wallet_public::ionic_swap_proposal_info& proposal_info) const - { - wallet_public::ionic_swap_proposal proposal = AUTO_VAL_INIT(proposal); - bool r = t_unserializable_object_from_blob(proposal, raw_proposal); - THROW_IF_TRUE_WALLET_EX(!r, error::wallet_internal_error, "Failed to parse proposal"); - return get_ionic_swap_proposal_info(proposal, proposal_info); - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::get_ionic_swap_proposal_info(const wallet_public::ionic_swap_proposal& proposal, wallet_public::ionic_swap_proposal_info& proposal_info) const - { - wallet_public::ionic_swap_proposal_context ionic_context = AUTO_VAL_INIT(ionic_context); - return get_ionic_swap_proposal_info(proposal, proposal_info, ionic_context); - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::get_ionic_swap_proposal_info(const wallet_public::ionic_swap_proposal& proposal, wallet_public::ionic_swap_proposal_info& proposal_info, wallet_public::ionic_swap_proposal_context& ionic_context) const - { - const transaction& tx = proposal.tx_template; - crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); - std::vector outs; - bool r = lookup_acc_outs(m_account.get_keys(), tx, outs, derivation); - THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "Failed to lookup_acc_outs for tx: " << get_transaction_hash(tx)); - - if (!outs.size()) - { - return false; - } - - //decrypt context - std::string decrypted_raw_context = crypto::chacha_crypt(proposal.encrypted_context, derivation); - r = t_unserializable_object_from_blob(ionic_context, decrypted_raw_context); - THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "Failed to unserialize decrypted ionic_context"); - - r = validate_tx_details_against_tx_generation_context(tx, ionic_context.gen_context); - THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "validate_tx_details_against_tx_generation_context failed"); - - std::unordered_map amounts_provided_by_a; - - std::unordered_map ammounts_to_a; //amounts to Alice (the one who created proposal), should be NOT funded - std::unordered_map ammounts_to_b; //amounts to Bob (the one who received proposal), should BE funded - std::vector bob_outs; - bob_outs.resize(proposal.tx_template.vout.size()); - - for (const auto& o : outs) - { - THROW_IF_FALSE_WALLET_INT_ERR_EX(ionic_context.gen_context.asset_ids.size() > o.index, "Tx gen context has mismatch with tx(asset_ids) "); - THROW_IF_FALSE_WALLET_INT_ERR_EX(ionic_context.gen_context.asset_ids[o.index].to_public_key() == o.asset_id, "Tx gen context has mismatch with tx(asset_id != asset_id) "); - THROW_IF_FALSE_WALLET_INT_ERR_EX(ionic_context.gen_context.amounts[o.index].m_u64[0] == o.amount, "Tx gen context has mismatch with tx(amount != amount)"); - - ammounts_to_b[o.asset_id] += o.amount; - bob_outs[o.index] = true; - } - size_t i = 0; - //validate outputs against decrypted tx generation context - for (i = 0; i != tx.vout.size(); i++) - { - - if (bob_outs[i]) - { - continue; - } - - crypto::public_key asset_id = ionic_context.gen_context.asset_ids[i].to_public_key(); - uint64_t amount = ionic_context.gen_context.amounts[i].m_u64[0]; - ammounts_to_a[asset_id] += amount; - } - - //read amounts already provided by third party - size_t zc_current_index = 0; //some inputs might be old ones, so it's asset id assumed as native and there is no entry for it in real_zc_ins_asset_ids - //THROW_IF_FALSE_WALLET_INT_ERR_EX(ionic_context.gen_context.input_amounts.size() == tx.vin.size(), "Tx gen context has mismatch with tx(amount != amount)"); - for (i = 0; i != tx.vin.size(); i++) - { - //size_t mx = 0; - uint64_t amount = 0; - crypto::public_key in_asset_id = currency::native_coin_asset_id; - if (tx.vin[i].type() == typeid(txin_zc_input)) - { - in_asset_id = ionic_context.gen_context.real_zc_ins_asset_ids[zc_current_index].to_public_key(); - amount = ionic_context.gen_context.zc_input_amounts[zc_current_index]; - zc_current_index++; - //mx = boost::get(tx.vin[i]).key_offsets.size() - 1; - } - else if (tx.vin[i].type() == typeid(txin_to_key)) - { - amount = boost::get(tx.vin[i]).amount; - //mx = boost::get(tx.vin[i]).key_offsets.size() - 1; - } - else - { - WLT_LOG_RED("Unexpected type of input in ionic_swap tx: " << tx.vin[i].type().name(), LOG_LEVEL_0); - return false; - } - amounts_provided_by_a[in_asset_id] += amount; - - //if (proposal_info.mixins == 0 || proposal_info.mixins > mx) - //{ - // proposal_info.mixins = mx; - //} - - } - - //this might be 0, if Alice don't want to pay fee herself - proposal_info.fee_paid_by_a = currency::get_tx_fee(tx); - if (proposal_info.fee_paid_by_a) - { - THROW_IF_FALSE_WALLET_INT_ERR_EX(amounts_provided_by_a[currency::native_coin_asset_id] >= proposal_info.fee_paid_by_a, "Fee mentioned as specified but not provided by A"); - amounts_provided_by_a[currency::native_coin_asset_id] -= proposal_info.fee_paid_by_a; - } - - //proposal_info.fee = currency::get_tx_fee(tx); - //need to make sure that funds for Bob properly funded - for (const auto& a : ammounts_to_b) - { - uint64_t amount_sent_back_to_initiator = ammounts_to_a[a.first]; - - if (amounts_provided_by_a[a.first] < (a.second + amount_sent_back_to_initiator)) - { - WLT_LOG_RED("Amount[" << a.first << "] provided by Alice(" << amounts_provided_by_a[a.first] << ") is less then transfered to Bob(" << a.second << ")", LOG_LEVEL_0); - return false; - } - amounts_provided_by_a[a.first] -= (amount_sent_back_to_initiator + a.second); - proposal_info.to_finalizer.push_back(view::asset_funds{ a.first, a.second }); - //clean accounted assets - ammounts_to_a.erase(ammounts_to_a.find(a.first)); - if (amounts_provided_by_a[a.first] > 0) - { - WLT_LOG_RED("Amount[" << a.first << "] provided by Alice has unused leftovers: " << amounts_provided_by_a[a.first], LOG_LEVEL_0); - return false; - } - } - - //need to see what Alice actually expect in return - for (const auto& a : ammounts_to_a) - { - //now amount provided by A should be less or equal to what we have in a.second - if (amounts_provided_by_a[a.first] > a.second) - { - //could be fee - WLT_LOG_RED("Amount[" << a.first << "] provided by Alice has unused leftovers: " << amounts_provided_by_a[a.first], LOG_LEVEL_0); - return false; - } - - proposal_info.to_initiator.push_back(view::asset_funds{ a.first, a.second - amounts_provided_by_a[a.first] }); - } - - return true; + reward = get_alias_cost(ai.m_alias); } - //---------------------------------------------------------------------------------------------------- - bool wallet2::accept_ionic_swap_proposal(const std::string& raw_proposal, currency::transaction& result_tx) + std::vector destinations; + std::vector extra; + std::vector attachments; + + push_alias_info_to_extra_according_to_hf_status(ai, extra); + + currency::tx_destination_entry tx_dest_alias_reward; + tx_dest_alias_reward.addr.resize(1); + get_aliases_reward_account(tx_dest_alias_reward.addr.back()); + tx_dest_alias_reward.amount = reward; + tx_dest_alias_reward.flags |= tx_destination_entry_flags::tdef_explicit_native_asset_id | tx_destination_entry_flags::tdef_zero_amount_blinding_mask; + destinations.push_back(tx_dest_alias_reward); + + transfer(destinations, 0, 0, fee, extra, attachments, get_current_split_strategy(), tx_dust_policy(DEFAULT_DUST_THRESHOLD), res_tx, CURRENCY_TO_KEY_OUT_RELAXED, false); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::deploy_new_asset(const currency::asset_descriptor_base& asset_info, const std::vector& destinations, currency::transaction& result_tx, crypto::public_key& new_asset_id) +{ + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(asset_info.decimal_point <= 18, "too big decimal point: " << asset_info.decimal_point); + + asset_descriptor_operation asset_reg_info = AUTO_VAL_INIT(asset_reg_info); + asset_reg_info.descriptor = asset_info; + asset_reg_info.operation_type = ASSET_DESCRIPTOR_OPERATION_REGISTER; + construct_tx_param ctp = get_default_construct_tx_param(); + ctp.dsts = destinations; + ctp.extra.push_back(asset_reg_info); + ctp.need_at_least_1_zc = true; + + finalized_tx ft = AUTO_VAL_INIT(ft); + this->transfer(ctp, ft, true, nullptr); + result_tx = ft.tx; + //get generated asset id + currency::asset_descriptor_operation ado = AUTO_VAL_INIT(ado); + bool r = get_type_in_variant_container(result_tx.extra, ado); + CHECK_AND_ASSERT_THROW_MES(r, "Failed find asset info in tx"); + CHECK_AND_ASSERT_THROW_MES(get_or_calculate_asset_id(ado, nullptr, &new_asset_id), "get_or_calculate_asset_id failed"); + + m_custom_assets[new_asset_id] = ado.descriptor; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::emit_asset(const crypto::public_key asset_id, std::vector& destinations, currency::transaction& result_tx) +{ + + auto own_asset_entry_it = m_own_asset_descriptors.find(asset_id); + CHECK_AND_ASSERT_THROW_MES(own_asset_entry_it != m_own_asset_descriptors.end(), "Failed find asset_id " << asset_id << " in own assets list"); + COMMAND_RPC_GET_ASSET_INFO::request req; + req.asset_id = asset_id; + COMMAND_RPC_GET_ASSET_INFO::response rsp; + bool r = m_core_proxy->call_COMMAND_RPC_GET_ASSET_INFO(req, rsp); + CHECK_AND_ASSERT_THROW_MES(r, "Failed to call_COMMAND_RPC_GET_ASSET_INFO"); + + asset_descriptor_operation asset_emmit_info = AUTO_VAL_INIT(asset_emmit_info); + asset_emmit_info.descriptor = rsp.asset_descriptor; + asset_emmit_info.operation_type = ASSET_DESCRIPTOR_OPERATION_EMIT; + asset_emmit_info.opt_asset_id = asset_id; + construct_tx_param ctp = get_default_construct_tx_param(); + ctp.dsts = destinations; + ctp.extra.push_back(asset_emmit_info); + ctp.need_at_least_1_zc = true; + ctp.ado_current_asset_owner = rsp.asset_descriptor.owner; + //ctp.asset_deploy_control_key = own_asset_entry_it->second.control_key; + + for (auto& dst : ctp.dsts) { - wallet_public::ionic_swap_proposal proposal = AUTO_VAL_INIT(proposal); - bool r = t_unserializable_object_from_blob(proposal, raw_proposal); - THROW_IF_TRUE_WALLET_EX(!r, error::wallet_internal_error, "Failed to parse proposal info"); - - return accept_ionic_swap_proposal(proposal, result_tx); - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::accept_ionic_swap_proposal(const wallet_public::ionic_swap_proposal& proposal, currency::transaction& result_tx) - { - mode_separate_context msc = AUTO_VAL_INIT(msc); - msc.tx_for_mode_separate = proposal.tx_template; - result_tx = msc.tx_for_mode_separate; - - wallet_public::ionic_swap_proposal_context ionic_context = AUTO_VAL_INIT(ionic_context); - bool r = get_ionic_swap_proposal_info(proposal, msc.proposal_info, ionic_context); - THROW_IF_TRUE_WALLET_EX(!r, error::wallet_internal_error, "Failed to get info from proposal"); - - std::unordered_map balances; - uint64_t mined = 0; - this->balance(balances, mined); - //validate balances needed - uint64_t native_amount_required = 0; - for (const auto& item : msc.proposal_info.to_initiator) - { - if (balances[item.asset_id].unlocked < item.amount) - { - THROW_IF_FALSE_WALLET_EX(false, error::not_enough_money, balances[item.asset_id].unlocked, item.amount, 0 /*fee*/, item.asset_id, get_asset_decimal_point(item.asset_id)); - } - if (item.asset_id == currency::native_coin_asset_id) - { - native_amount_required = item.amount; - } - } - - // balances is ok, check if fee is added to tx - uint64_t additional_fee = 0; - if (msc.proposal_info.fee_paid_by_a < m_core_runtime_config.tx_default_fee) - { - additional_fee = m_core_runtime_config.tx_default_fee - msc.proposal_info.fee_paid_by_a; - if (balances[currency::native_coin_asset_id].unlocked < additional_fee + native_amount_required) - { - THROW_IF_FALSE_WALLET_EX(false, error::not_enough_money, balances[currency::native_coin_asset_id].unlocked, native_amount_required, additional_fee, currency::native_coin_asset_id); - } - } - - //everything is seemed to be ok - - construct_tx_param construct_param = get_default_construct_tx_param(); - construct_param.fee = additional_fee; - - crypto::secret_key one_time_key = ionic_context.gen_context.tx_key.sec; // TODO: figure out this mess with tx sec key -- sowle - 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.need_at_least_1_zc = true; - - //build transaction - currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); - ftp.tx_version = this->get_current_tx_version(); - ftp.gen_context = ionic_context.gen_context; - prepare_transaction(construct_param, ftp, msc); - - - - try - { - finalize_transaction(ftp, result_tx, one_time_key, true); - } - catch (...) - { - clear_transfers_from_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, std::string("exception in finalize_transaction, tx: ") + epee::string_tools::pod_to_hex(get_transaction_hash(result_tx))); - throw; - } - mark_transfers_as_spent(ftp.selected_transfers, std::string("Proposal has been accepted with tx <" + epee::string_tools::pod_to_hex(get_transaction_hash(result_tx))) + ">"); - return true; - } - //---------------------------------------------------------------------------------------------------- - - // Signing and auth - bool wallet2::sign_buffer(const std::string& buff, crypto::signature& sig) - { - crypto::hash h = crypto::cn_fast_hash(buff.data(), buff.size()); - crypto::generate_signature(h, m_account.get_public_address().spend_public_key, m_account.get_keys().spend_secret_key, sig); - return true; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::validate_sign(const std::string& buff, const crypto::signature& sig, const crypto::public_key& pkey) - { - crypto::hash h = crypto::cn_fast_hash(buff.data(), buff.size()); - return crypto::check_signature(h, pkey, sig); - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::encrypt_buffer(const std::string& buff, std::string& res_buff) - { - res_buff = buff; - crypto::chacha_crypt(res_buff, m_account.get_keys().view_secret_key); - return true; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::decrypt_buffer(const std::string& buff, std::string& res_buff) - { - res_buff = buff; - crypto::chacha_crypt(res_buff, m_account.get_keys().view_secret_key); - return true; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::prepare_tx_sources_for_defragmentation_tx(std::vector& sources, std::vector& selected_indicies, uint64_t& found_money) - { - if (!m_defragmentation_tx_enabled) - return false; - - std::stringstream ss; - if (epee::log_space::log_singletone::get_log_detalisation_level() >= LOG_LEVEL_2) - ss << "preparing sources for utxo defragmentation tx:"; - for (const auto& tr : m_transfers)//size_t i = 0, size = m_transfers .size(); i < size && selected_indicies.size() < m_max_utxo_count_for_defragmentation_tx; ++i) - { - if (selected_indicies.size() >= m_max_utxo_count_for_defragmentation_tx) - break; - uint64_t i = tr.first; - const auto& td = tr.second; - if (!td.is_native_coin() || td.m_amount > m_max_allowed_output_amount_for_defragmentation_tx) - continue; - - uint64_t fake_outs_count_for_td = m_decoys_count_for_defragmentation_tx == SIZE_MAX ? (td.is_zc() ? m_core_runtime_config.hf4_minimum_mixins : CURRENCY_DEFAULT_DECOY_SET_SIZE) : m_decoys_count_for_defragmentation_tx; - if (is_transfer_ready_to_go(td, fake_outs_count_for_td)) - { - found_money += td.m_amount; - selected_indicies.push_back(i); - if (epee::log_space::log_singletone::get_log_detalisation_level() >= LOG_LEVEL_2) - ss << " selected transfer #" << i << ", amount: " << print_money_brief(td.m_amount) << ", height: " << td.m_ptx_wallet_info->m_block_height << ", " << (td.is_zc() ? "ZC" : " "); - } - } - - if (selected_indicies.size() < m_min_utxo_count_for_defragmentation_tx || found_money <= TX_MINIMUM_FEE) - { - // too few outputs were found, hence don't create a defragmentation tx - selected_indicies.clear(); - found_money = 0; - return false; - } - - WLT_LOG(ss.str(), LOG_LEVEL_2); - - return prepare_tx_sources(m_decoys_count_for_defragmentation_tx == SIZE_MAX ? CURRENCY_DEFAULT_DECOY_SET_SIZE : m_decoys_count_for_defragmentation_tx, sources, selected_indicies); - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::prepare_tx_sources(assets_selection_context& needed_money_map, size_t fake_outputs_count, uint64_t dust_threshold, std::vector& sources, std::vector& selected_indicies) - { - try - { - select_transfers(needed_money_map, fake_outputs_count, dust_threshold, selected_indicies); // always returns true, TODO consider refactoring -- sowle - return prepare_tx_sources(fake_outputs_count, sources, selected_indicies); - } - catch (...) - { - // if smth went wrong -- invalidate transfers cache to trigger its regeneration on the next use - // it is necessary because it may be in invalid state (some items might be erased within select_indices_for_transfer() or expand_selection_with_zc_input()) - m_found_free_amounts.clear(); - throw; - } - } - //---------------------------------------------------------------------------------------------------- - void wallet2::prefetch_global_indicies_if_needed(const std::vector& selected_indicies) - { - //std::list> txs; - //std::list indices_that_requested_global_indicies; - for (uint64_t i : selected_indicies) - { - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(m_transfers.at(i).m_global_output_index != WALLET_GLOBAL_OUTPUT_INDEX_UNDEFINED, - "m_transfers.at(" << i << ").m_global_output_index is WALLET_GLOBAL_OUTPUT_INDEX_UNDEFINED"); - //indices_that_requested_global_indicies.push_back(i); - //txs.push_back(m_transfers.at(i).m_ptx_wallet_info->m_tx); - //} - } - - /* - std::vector > outputs_for_all_txs; - fetch_tx_global_indixes(txs, outputs_for_all_txs); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(txs.size() == outputs_for_all_txs.size(), "missmatch sizes txs.size() == outputs_for_all_txs.size()"); - auto it_indices = indices_that_requested_global_indicies.begin(); - auto it_ooutputs = outputs_for_all_txs.begin(); - for (; it_ooutputs != outputs_for_all_txs.end();) - { - transfer_details& td = m_transfers.at(*it_indices); - td.m_global_output_index = (*it_ooutputs)[td.m_internal_output_index]; - it_ooutputs++; it_indices++; - }*/ - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::prepare_tx_sources(size_t fake_outputs_count, std::vector& sources, const std::vector& selected_indicies) - { - return prepare_tx_sources(fake_outputs_count, false, sources, selected_indicies); - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::prepare_tx_sources(size_t fake_outputs_count_, bool use_all_decoys_if_found_less_than_required, std::vector& sources, const std::vector& selected_indicies) - { - typedef COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry out_entry; - typedef currency::tx_source_entry::output_entry tx_output_entry; - - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response daemon_resp = AUTO_VAL_INIT(daemon_resp); - //we should request even of fake_outputs_count == 0, since for for postzarcanum this era this param is redefined - //todo: remove if(true) block later if this code will be settled - if (true) - { - size_t fake_outputs_count = fake_outputs_count_; - uint64_t zarcanum_start_from = m_core_runtime_config.hard_forks.m_height_the_hardfork_n_active_after[ZANO_HARDFORK_04_ZARCANUM]; - uint64_t current_size = m_chain.get_blockchain_current_size(); - - bool need_to_request = fake_outputs_count != 0; - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS3::request req = AUTO_VAL_INIT(req); - req.height_upper_limit = m_last_pow_block_h; // request decoys to be either older than, or the same age as stake output's height - req.use_forced_mix_outs = false; // TODO: add this feature to UI later - //req.decoys_count = fake_outputs_count + 1; // one more to be able to skip a decoy in case it hits the real output - for (uint64_t i : selected_indicies) - { - req.amounts.push_back(COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS3::offsets_distribution()); - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS3::offsets_distribution& rdisttib = req.amounts.back(); - - auto it = m_transfers.find(i); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it != m_transfers.end(), - "internal error: index in m_tranfers " << i << " not found"); - - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it->second.m_ptx_wallet_info->m_tx.vout.size() > it->second.m_internal_output_index, - "m_internal_output_index = " << it->second.m_internal_output_index << - " is greater or equal to outputs count = " << it->second.m_ptx_wallet_info->m_tx.vout.size()); - - //rdisttib.own_global_index = it->m_global_output_index; - //check if we have Zarcanum era output of pre-Zarcanum - if (it->second.is_zc()) - { - if (this->is_auditable()) - continue; - //Zarcanum era - rdisttib.amount = 0; - //generate distribution in Zarcanum hardfork - build_distribution_for_input(rdisttib.global_offsets, it->second.m_global_output_index); - need_to_request = true; - } - else - { - //for prezarcanum era use flat distribution - rdisttib.amount = it->second.m_amount; - rdisttib.global_offsets.resize(fake_outputs_count + 1, 0); - } - } - if (need_to_request) - { - size_t attempt_count = 0; - while (true) - { - daemon_resp = COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response(); - bool r = m_core_proxy->call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS3(req, daemon_resp); - THROW_IF_FALSE_WALLET_EX(r, error::no_connection_to_daemon, "getrandom_outs3.bin"); - if (daemon_resp.status == API_RETURN_CODE_FAIL) - { - if (attempt_count < 10) - { - attempt_count++; - continue; - } - else - { - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(daemon_resp.outs.size() == selected_indicies.size(), - "unable to exacute getrandom_outs2.bin after 10 attempts with code API_RETURN_CODE_FAIL, there must be problems with mixins"); - } - } - THROW_IF_FALSE_WALLET_EX(daemon_resp.status != API_RETURN_CODE_BUSY, error::daemon_busy, "getrandom_outs.bin"); - THROW_IF_FALSE_WALLET_EX(daemon_resp.status == API_RETURN_CODE_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_outs2.bin, wrong amounts count = " << daemon_resp.outs.size() << ", expected: " << selected_indicies.size()); - break; - } - - std::vector scanty_outs; - THROW_IF_FALSE_WALLET_EX(daemon_resp.outs.size() == req.amounts.size(), error::not_enough_outs_to_mix, scanty_outs, fake_outputs_count); - - if (!use_all_decoys_if_found_less_than_required) - { - // make sure we have received the requested number of decoys - for (size_t i = 0; i != daemon_resp.outs.size(); i++) - if (req.amounts[i].amount != 0 && daemon_resp.outs[i].outs.size() != req.amounts[i].global_offsets.size()) - scanty_outs.push_back(daemon_resp.outs[i]); - THROW_IF_FALSE_WALLET_EX(scanty_outs.empty(), error::not_enough_outs_to_mix, scanty_outs, fake_outputs_count); - } - } - } - - //lets prefetch m_global_output_index for selected_indicies - //this days doesn't prefetch, only validated that prefetch is not needed - prefetch_global_indicies_if_needed(selected_indicies); - - //prepare inputs - size_t i = 0; - for (uint64_t J : selected_indicies) - { - auto it = m_transfers.find(J); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it != m_transfers.end(), "internal error: J " << J << " not found in m_transfers"); - - - sources.push_back(AUTO_VAL_INIT(currency::tx_source_entry())); - currency::tx_source_entry& src = sources.back(); - transfer_details& td = it->second; - src.transfer_index = J; - src.amount = td.amount(); - src.asset_id = td.get_asset_id(); - size_t fake_outputs_count = fake_outputs_count_; - //redefine for hardfork - if (td.is_zc() && !this->is_auditable()) - fake_outputs_count = m_core_runtime_config.hf4_minimum_mixins; - - - //paste mixin transaction - if (daemon_resp.outs.size()) - { - if (td.is_zc()) - { - //get rid of unneeded - select_decoys(daemon_resp.outs[i], td.m_global_output_index); - } - else - { - //TODO: make sure we have exact count needed - } - - daemon_resp.outs[i].outs.sort([](const out_entry& a, const out_entry& b) {return a.global_amount_index < b.global_amount_index; }); - for (out_entry& daemon_oe : daemon_resp.outs[i].outs) - { - if (td.m_global_output_index == daemon_oe.global_amount_index) - continue; - tx_output_entry oe = AUTO_VAL_INIT(oe); - oe.amount_commitment = daemon_oe.amount_commitment; - oe.concealing_point = daemon_oe.concealing_point; - oe.out_reference = daemon_oe.global_amount_index; - oe.stealth_address = daemon_oe.stealth_address; - oe.blinded_asset_id = daemon_oe.blinded_asset_id; // TODO @#@# BAD DESIGN, consider refactoring -- sowle - src.outputs.push_back(oe); - if (src.outputs.size() >= fake_outputs_count) - break; - } - } - - //paste real transaction to the random index - auto it_to_insert = std::find_if(src.outputs.begin(), src.outputs.end(), [&](const tx_output_entry& a) - { - if (a.out_reference.type().hash_code() == typeid(uint64_t).hash_code()) - return static_cast(boost::get(a.out_reference) >= td.m_global_output_index); - return false; // TODO: implement deterministics real output placement in case there're ref_by_id outs - }); - //size_t real_index = src.outputs.size() ? (rand() % src.outputs.size() ):0; - tx_output_entry real_oe = AUTO_VAL_INIT(real_oe); - real_oe.out_reference = td.m_global_output_index; // TODO: use ref_by_id when neccessary - VARIANT_SWITCH_BEGIN(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]); - VARIANT_CASE_CONST(tx_out_bare, o) - { - VARIANT_SWITCH_BEGIN(o.target); - VARIANT_CASE_CONST(txout_to_key, o) - real_oe.stealth_address = o.key; - VARIANT_CASE_CONST(txout_htlc, htlc) - real_oe.stealth_address = htlc.pkey_refund; - VARIANT_CASE_OTHER() - { - WLT_THROW_IF_FALSE_WITH_CODE(false, - "Internal error: unexpected type of target: " << o.target.type().name(), - API_RETURN_CODE_INTERNAL_ERROR); - } - VARIANT_SWITCH_END(); - } - VARIANT_CASE_CONST(tx_out_zarcanum, o); - real_oe.amount_commitment = o.amount_commitment; // TODO @#@# consider using shorter code like in sweep_below() (or better reuse it) - real_oe.concealing_point = o.concealing_point; - real_oe.stealth_address = o.stealth_address; - real_oe.blinded_asset_id = o.blinded_asset_id; - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.is_zc(), "transfer #" << J << ", amount: " << print_money_brief(td.amount()) << " is not a ZC"); - src.real_out_amount_blinding_mask = td.m_zc_info_ptr->amount_blinding_mask; - src.real_out_asset_id_blinding_mask = td.m_zc_info_ptr->asset_id_blinding_mask; - src.asset_id = td.m_zc_info_ptr->asset_id; -#ifndef NDEBUG - WLT_CHECK_AND_ASSERT_MES(crypto::point_t(src.asset_id) + src.real_out_asset_id_blinding_mask * crypto::c_point_X == crypto::point_t(real_oe.blinded_asset_id).modify_mul8(), false, "real_out_asset_id_blinding_mask doesn't match real_oe.blinded_asset_id"); - WLT_CHECK_AND_ASSERT_MES(td.m_amount * crypto::point_t(real_oe.blinded_asset_id).modify_mul8() + src.real_out_amount_blinding_mask * crypto::c_point_G == crypto::point_t(real_oe.amount_commitment).modify_mul8(), false, "real_out_amount_blinding_mask doesn't match real_oe.amount_commitment"); -#endif - VARIANT_SWITCH_END(); - - auto interted_it = src.outputs.insert(it_to_insert, real_oe); - src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_ptx_wallet_info->m_tx); - src.real_output = interted_it - src.outputs.begin(); - src.real_output_in_tx_index = td.m_internal_output_index; - - if (epee::log_space::get_set_log_detalisation_level() >= LOG_LEVEL_1) - { - std::stringstream ss; - ss << "source entry [" << i << "], td_idx: " << J << ", "; - print_source_entry(ss, src); - WLT_LOG_L1(ss.str()); - } - - ++i; - } - return true; + dst.asset_id = null_pkey; // emit operation requires null_pkey for emitting asset outputs, fix it ad-hoc here } + finalized_tx ft = AUTO_VAL_INIT(ft); + this->transfer(ctp, ft, true, nullptr); + result_tx = ft.tx; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::update_asset(const crypto::public_key asset_id, const currency::asset_descriptor_base new_descriptor, currency::transaction& result_tx) +{ + auto own_asset_entry_it = m_own_asset_descriptors.find(asset_id); + CHECK_AND_ASSERT_THROW_MES(own_asset_entry_it != m_own_asset_descriptors.end(), "Failed find asset_id " << asset_id << " in own assets list"); - //---------------------------------------------------------------------------------------------------------------- - template - typename t_obj_container::value_type extract_random_from_container(t_obj_container& container) + asset_descriptor_operation asset_update_info = AUTO_VAL_INIT(asset_update_info); + asset_update_info.descriptor = new_descriptor; + asset_update_info.operation_type = ASSET_DESCRIPTOR_OPERATION_UPDATE; + asset_update_info.opt_asset_id = asset_id; + construct_tx_param ctp = get_default_construct_tx_param(); + ctp.extra.push_back(asset_update_info); + ctp.need_at_least_1_zc = true; + currency::asset_descriptor_base adb = AUTO_VAL_INIT(adb); + bool r = this->daemon_get_asset_info(asset_id, adb); + CHECK_AND_ASSERT_THROW_MES(r, "Failed to get asset info from daemon"); + ctp.ado_current_asset_owner = adb.owner; + + finalized_tx ft = AUTO_VAL_INIT(ft); + this->transfer(ctp, ft, true, nullptr); + result_tx = ft.tx; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::transfer_asset_ownership(const crypto::public_key asset_id, const crypto::public_key& new_owner, currency::transaction& result_tx) +{ + auto own_asset_entry_it = m_own_asset_descriptors.find(asset_id); + CHECK_AND_ASSERT_THROW_MES(own_asset_entry_it != m_own_asset_descriptors.end(), "Failed find asset_id " << asset_id << " in own assets list"); + + currency::asset_descriptor_base adb = AUTO_VAL_INIT(adb); + bool r = this->daemon_get_asset_info(asset_id, adb); + CHECK_AND_ASSERT_THROW_MES(r, "Failed to get asset info from daemon"); + + asset_descriptor_operation asset_update_info = AUTO_VAL_INIT(asset_update_info); + asset_update_info.descriptor = adb; + asset_update_info.operation_type = ASSET_DESCRIPTOR_OPERATION_UPDATE; + asset_update_info.opt_asset_id = asset_id; + asset_update_info.descriptor.owner = new_owner; + construct_tx_param ctp = get_default_construct_tx_param(); + ctp.ado_current_asset_owner = adb.owner; + ctp.extra.push_back(asset_update_info); + + finalized_tx ft = AUTO_VAL_INIT(ft); + this->transfer(ctp, ft, true, nullptr); + result_tx = ft.tx; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::burn_asset(const crypto::public_key asset_id, uint64_t amount_to_burn, currency::transaction& result_tx) +{ + //auto own_asset_entry_it = m_own_asset_descriptors.find(asset_id); + //CHECK_AND_ASSERT_THROW_MES(own_asset_entry_it != m_own_asset_descriptors.end(), "Failed find asset_id " << asset_id << " in own assets list"); + COMMAND_RPC_GET_ASSET_INFO::request req; + req.asset_id = asset_id; + COMMAND_RPC_GET_ASSET_INFO::response rsp; + bool r = m_core_proxy->call_COMMAND_RPC_GET_ASSET_INFO(req, rsp); + CHECK_AND_ASSERT_THROW_MES(r, "Failed to call_COMMAND_RPC_GET_ASSET_INFO"); + + + + asset_descriptor_operation asset_burn_info = AUTO_VAL_INIT(asset_burn_info); + asset_burn_info.descriptor = rsp.asset_descriptor; + + CHECK_AND_ASSERT_THROW_MES(asset_burn_info.descriptor.current_supply >= amount_to_burn, "Wrong amount to burn (current_supply" << asset_burn_info.descriptor.current_supply << " is less then " << amount_to_burn << ")"); + + currency::tx_destination_entry dst_to_burn = AUTO_VAL_INIT(dst_to_burn); + dst_to_burn.amount = amount_to_burn; + dst_to_burn.asset_id = asset_id; + + asset_burn_info.operation_type = ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN; + asset_burn_info.opt_asset_id = asset_id; + construct_tx_param ctp = get_default_construct_tx_param(); + ctp.extra.push_back(asset_burn_info); + ctp.need_at_least_1_zc = true; + ctp.ado_current_asset_owner = rsp.asset_descriptor.owner; + ctp.dsts.push_back(dst_to_burn); + + finalized_tx ft = AUTO_VAL_INIT(ft); + this->transfer(ctp, ft, true, nullptr); + result_tx = ft.tx; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::daemon_get_asset_info(const crypto::public_key& asset_id, currency::asset_descriptor_base& adb) +{ + COMMAND_RPC_GET_ASSET_INFO::request req; + req.asset_id = asset_id; + COMMAND_RPC_GET_ASSET_INFO::response rsp; + bool r = m_core_proxy->call_COMMAND_RPC_GET_ASSET_INFO(req, rsp); + CHECK_AND_ASSERT_MES(r, false, "Failed to call_COMMAND_RPC_GET_ASSET_INFO"); + adb = rsp.asset_descriptor; + return true; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::request_alias_update(currency::extra_alias_entry& ai, currency::transaction& res_tx, uint64_t fee) +{ + COMMAND_RPC_GET_ALIAS_DETAILS::request req; + req.alias = ai.m_alias; + COMMAND_RPC_GET_ALIAS_DETAILS::response rsp = AUTO_VAL_INIT(rsp); + bool r = m_core_proxy->call_COMMAND_RPC_GET_ALIAS_DETAILS(req, rsp); + CHECK_AND_ASSERT_THROW_MES(r, "Failed to call_COMMAND_RPC_GET_ALIAS_DETAILS"); + + CHECK_AND_ASSERT_THROW_MES(rsp.status == API_RETURN_CODE_OK, "call_COMMAND_RPC_GET_ALIAS_DETAILS response: " << rsp.status); + + + currency::account_public_address addr = AUTO_VAL_INIT(addr); + currency::get_account_address_from_str(addr, rsp.alias_details.address); + + CHECK_AND_ASSERT_THROW_MES(m_account.get_public_address().spend_public_key == addr.spend_public_key && + m_account.get_public_address().view_public_key == addr.view_public_key, "call_COMMAND_RPC_GET_ALIAS_DETAILS: ownership is not confirmed"); + + if (!validate_alias_name(ai.m_alias)) { - auto it = container.begin(); - std::advance(it, (crypto::rand() % container.size())); - typename t_obj_container::value_type obj = *it; - container.erase(it); - return obj; + throw std::runtime_error(std::string("wrong alias characters: ") + ai.m_alias); } - //---------------------------------------------------------------------------------------------------------------- - void wallet2::select_decoys(currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& amount_entry, uint64_t own_g_index) + r = currency::sign_extra_alias_entry(ai, m_account.get_keys().account_address.spend_public_key, m_account.get_keys().spend_secret_key); + CHECK_AND_ASSERT_THROW_MES(r, "Failed to sign alias update"); + WLT_LOG_L2("Generated update alias info: " << ENDL + << "alias: " << ai.m_alias << ENDL + << "signature: " << currency::print_t_array(ai.m_sign) << ENDL + << "signed(owner) pub key: " << m_account.get_keys().account_address.spend_public_key << ENDL + << "transfered to address: " << get_account_address_as_str(ai.m_address) << ENDL + << "sign_buff_hash: " << currency::get_sign_buff_hash_for_alias_update(ai) + ); + + std::vector destinations; + std::vector extra; + std::vector attachments; + + push_alias_info_to_extra_according_to_hf_status(ai, extra); + + transfer(destinations, 0, 0, fee, extra, attachments, get_current_split_strategy(), tx_dust_policy(DEFAULT_DUST_THRESHOLD), res_tx, CURRENCY_TO_KEY_OUT_RELAXED, false); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::check_available_sources(std::list& amounts) +{ + /* + std::list > holds; + amounts.sort(); + bool res = true; + for (uint64_t am : amounts) { - THROW_IF_FALSE_WALLET_INT_ERR_EX(amount_entry.amount == 0, "Amount is not 0 in zc decoys entry"); - typedef currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry out_entry; - - //TODO: This strategy would be a subject for continuous refactoring - - //first take all real transactions if ther are some - std::list local_outs; - std::list coinbases; - - while (amount_entry.outs.size() && local_outs.size() != m_core_runtime_config.hf4_minimum_mixins) + holds.push_back(std::vector()); + std::vector& current_holds = holds.back(); + uint64_t found = select_transfers(am, 0, DEFAULT_DUST_THRESHOLD, current_holds); + if (found < am) { - out_entry entry = extract_random_from_container(amount_entry.outs); - - // - if (entry.global_amount_index == own_g_index) - { - continue; - } - - //skip auditable - if ((entry.flags & (RANDOM_OUTPUTS_FOR_AMOUNTS_FLAGS_NOT_ALLOWED))) - { - continue; - } - if (entry.flags & (RANDOM_OUTPUTS_FOR_AMOUNTS_FLAGS_COINBASE)) - { - coinbases.push_back(entry); - continue; - } - - - local_outs.push_back(entry); - } - - //extend with coin base outs if needed - while (coinbases.size() && local_outs.size() != m_core_runtime_config.hf4_minimum_mixins) - { - out_entry entry = extract_random_from_container(coinbases); - local_outs.push_back(entry); - } - - THROW_IF_FALSE_WALLET_INT_ERR_EX(local_outs.size() == m_core_runtime_config.hf4_minimum_mixins, "Amount is not 0 in zc decoys entry"); - amount_entry.outs = local_outs; - } - //---------------------------------------------------------------------------------------------------------------- - void wallet2::build_distribution_for_input(std::vector& offsets, uint64_t own_index) - { - decoy_selection_generator zarcanum_decoy_set_generator; - zarcanum_decoy_set_generator.init(get_actual_zc_global_index()); - - THROW_IF_FALSE_WALLET_INT_ERR_EX(zarcanum_decoy_set_generator.is_initialized(), "zarcanum_decoy_set_generator are not initialized"); - if (m_core_runtime_config.hf4_minimum_mixins) - { - uint64_t actual_zc_index = get_actual_zc_global_index(); - offsets = zarcanum_decoy_set_generator.generate_unique_reversed_distribution(actual_zc_index - 1 > WALLET_FETCH_RANDOM_OUTS_SIZE ? WALLET_FETCH_RANDOM_OUTS_SIZE : actual_zc_index - 1, own_index); - } - } - //---------------------------------------------------------------------------------------------------------------- - bool wallet2::prepare_tx_sources(crypto::hash multisig_id, std::vector& sources, uint64_t& found_money) - { - 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: " + epee::string_tools::pod_to_hex(multisig_id)); - THROW_IF_FALSE_WALLET_INT_ERR_EX(!it->second.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)); - - 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()"); - //@#@ - THROW_IF_FALSE_WALLET_INT_ERR_EX(it->second.m_ptx_wallet_info->m_tx.vout[it->second.m_internal_output_index].type() == typeid(tx_out_bare), "Unknown type id in prepare_tx_sources: " << it->second.m_ptx_wallet_info->m_tx.vout[it->second.m_internal_output_index].type().name()); - const tx_out_bare& out = boost::get(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(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); - src.multisig_id = multisig_id; - src.ms_sigs_count = ms_out.minimum_sigs; - src.ms_keys_count = ms_out.keys.size(); - return true; - } - //---------------------------------------------------------------------------------------------------------------- - bool wallet2::prepare_tx_sources_htlc(crypto::hash htlc_tx_id, const std::string& origin, std::vector& sources, uint64_t& found_money) - { - typedef currency::tx_source_entry::output_entry tx_output_entry; - //lets figure out, if we have active htlc for this htlc - auto it = m_active_htlcs_txid.find(htlc_tx_id); - if (it == m_active_htlcs_txid.end()) - { - WLT_THROW_IF_FALSE_WITH_CODE(false, - "htlc not found with tx_id = " << htlc_tx_id, API_RETURN_CODE_NOT_FOUND); - } - - //WLT_THROW_IF_FALSE_WITH_CODE(m_transfers.size() > it->second, - // "Internal error: index in m_active_htlcs_txid <" << it->second << "> is bigger then size of m_transfers <" << m_transfers.size() << ">", API_RETURN_CODE_INTERNAL_ERROR); - - const transfer_details& td = m_transfers.at(it->second); - //@#@ - WLT_THROW_IF_FALSE_WITH_CODE(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].type() == typeid(tx_out_bare), - "Unexpected out type in prepare_tx_sources_htlc:" << td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].type().name(), API_RETURN_CODE_INTERNAL_ERROR); - - const tx_out_bare& out_bare = boost::get(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]); - WLT_THROW_IF_FALSE_WITH_CODE(out_bare.target.type() == typeid(txout_htlc), - "Unexpected type in active htlc", API_RETURN_CODE_INTERNAL_ERROR); - - const txout_htlc& htlc_out = boost::get(out_bare.target); - bool use_sha256 = !(htlc_out.flags & CURRENCY_TXOUT_HTLC_FLAGS_HASH_TYPE_MASK); - - //check origin - WLT_THROW_IF_FALSE_WITH_CODE(origin.size() != 0, - "Origin for htlc is empty", API_RETURN_CODE_BAD_ARG); - - crypto::hash htlc_calculated_hash = currency::null_hash; - if (use_sha256) - { - htlc_calculated_hash = crypto::sha256_hash(origin.data(), origin.size()); - } - else - { - htlc_calculated_hash = crypto::RIPEMD160_hash_256(origin.data(), origin.size()); - } - WLT_THROW_IF_FALSE_WITH_CODE(htlc_calculated_hash == htlc_out.htlc_hash, - "Origin hash is missmatched with txout_htlc", API_RETURN_CODE_HTLC_ORIGIN_HASH_MISSMATCHED); - - sources.push_back(AUTO_VAL_INIT(currency::tx_source_entry())); - currency::tx_source_entry& src = sources.back(); - tx_output_entry real_oe = AUTO_VAL_INIT(real_oe); - real_oe.out_reference = td.m_global_output_index; // TODO: use ref_by_id when necessary - real_oe.stealth_address = htlc_out.pkey_redeem; - src.outputs.push_back(real_oe); //m_global_output_index should be prefetched - src.amount = found_money = td.amount(); - src.real_output_in_tx_index = td.m_internal_output_index; - src.real_output = 0;//no mixins supposed to be in htlc - src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_ptx_wallet_info->m_tx); - src.htlc_origin = origin; - return true; - - } - //---------------------------------------------------------------------------------------------------------------- - assets_selection_context wallet2::get_needed_money(uint64_t fee, const std::vector& dsts) - { - assets_selection_context amounts_map; - amounts_map[currency::native_coin_asset_id].needed_amount = fee; - for (auto& dt : dsts) - { - if (dt.asset_id == currency::null_pkey) - continue; //this destination for emmition only - - THROW_IF_TRUE_WALLET_EX(0 == dt.amount, error::zero_destination); - uint64_t money_to_add = dt.amount; - if (dt.amount_to_provide || dt.flags & tx_destination_entry_flags::tdef_explicit_amount_to_provide) - money_to_add = dt.amount_to_provide; - - amounts_map[dt.asset_id].needed_amount += money_to_add; - THROW_IF_TRUE_WALLET_EX(amounts_map[dt.asset_id].needed_amount < money_to_add, error::tx_sum_overflow, dsts, fee); - //clean up empty entries - if (amounts_map[dt.asset_id].needed_amount == 0) - { - amounts_map.erase(amounts_map.find(dt.asset_id)); - } - } - return amounts_map; - } - //---------------------------------------------------------------------------------------------------------------- - void wallet2::set_disable_tor_relay(bool disable) - { - m_disable_tor_relay = disable; - } - //---------------------------------------------------------------------------------------------------------------- - void wallet2::notify_state_change(const std::string& state_code, const std::string& details) - { - m_wcallback->on_tor_status_change(state_code); - } - //---------------------------------------------------------------------------------------------------------------- - void wallet2::send_transaction_to_network(const transaction& tx) - { -#ifndef DISABLE_TOR - if (!m_disable_tor_relay) - { - //TODO check that core synchronized - //epee::net_utils::levin_client2 p2p_client; - - //make few attempts - tools::levin_over_tor_client p2p_client; - p2p_client.get_transport().set_notifier(this); - bool succeseful_sent = false; - for (size_t i = 0; i != 3; i++) - { - if (!p2p_client.connect("144.76.183.143", 2121, 10000)) - { - continue;//THROW_IF_FALSE_WALLET_EX(false, error::no_connection_to_daemon, "Failed to connect to TOR node"); - } - - - currency::NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request p2p_req = AUTO_VAL_INIT(p2p_req); - currency::NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::response p2p_rsp = AUTO_VAL_INIT(p2p_rsp); - p2p_req.txs.push_back(t_serializable_object_to_blob(tx)); - this->notify_state_change(WALLET_LIB_STATE_SENDING); - epee::net_utils::invoke_remote_command2(NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::ID, p2p_req, p2p_rsp, p2p_client); - p2p_client.disconnect(); - if (p2p_rsp.code == API_RETURN_CODE_OK) - { - this->notify_state_change(WALLET_LIB_SENT_SUCCESS); - succeseful_sent = true; - break; - } - this->notify_state_change(WALLET_LIB_SEND_FAILED); - //checking if transaction got relayed to other nodes and - //return; - } - if (!succeseful_sent) - { - this->notify_state_change(WALLET_LIB_SEND_FAILED); - THROW_IF_FALSE_WALLET_EX(succeseful_sent, error::no_connection_to_daemon, "Faile to build TOR stream"); - } - } - else -#endif // - { - COMMAND_RPC_SEND_RAW_TX::request req; - req.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(tx)); - COMMAND_RPC_SEND_RAW_TX::response daemon_send_resp; - bool r = m_core_proxy->call_COMMAND_RPC_SEND_RAW_TX(req, daemon_send_resp); - THROW_IF_TRUE_WALLET_EX(!r, error::no_connection_to_daemon, "sendrawtransaction"); - THROW_IF_TRUE_WALLET_EX(daemon_send_resp.status == API_RETURN_CODE_BUSY, error::daemon_busy, "sendrawtransaction"); - THROW_IF_TRUE_WALLET_EX(daemon_send_resp.status == API_RETURN_CODE_DISCONNECTED, error::no_connection_to_daemon, "Transfer attempt while daemon offline"); - THROW_IF_TRUE_WALLET_EX(daemon_send_resp.status != API_RETURN_CODE_OK, error::tx_rejected, tx, daemon_send_resp.status); - - WLT_LOG_L2("transaction " << get_transaction_hash(tx) << " generated ok and sent to daemon:" << ENDL << currency::obj_to_json_str(tx)); - } - - } - //---------------------------------------------------------------------------------------------------------------- - void wallet2::add_sent_tx_detailed_info(const transaction& tx, const std::vector& decrypted_att, - const std::vector& destinations, - const std::vector& selected_transfers) - { - payment_id_t payment_id; - get_payment_id_from_decrypted_container(decrypted_att, payment_id); - - std::vector recipients; - std::unordered_set used_addresses; - for (const auto& d : destinations) - { - for (const auto& addr : d.addr) - { - if (used_addresses.insert(addr).second && addr != m_account.get_public_address()) - recipients.push_back(payment_id.empty() ? get_account_address_as_str(addr) : get_account_address_and_payment_id_as_str(addr, payment_id)); - } - } - if (!recipients.size()) - { - //transaction send to ourself - recipients.push_back(payment_id.empty() ? get_account_address_as_str(m_account.get_public_address()) : get_account_address_and_payment_id_as_str(m_account.get_public_address(), payment_id)); - } - - add_sent_unconfirmed_tx(tx, recipients, selected_transfers, destinations); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::mark_transfers_with_flag(const std::vector& selected_transfers, uint32_t flag, const std::string& reason /* = empty_string */, bool throw_if_flag_already_set /* = false */) - { - // check all selected transfers prior to flag change - for (uint64_t i : selected_transfers) - { - //WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(i < m_transfers.size(), "invalid transfer index given: " << i << ", m_transfers.size() == " << m_transfers.size()); - if (throw_if_flag_already_set) - { - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX((m_transfers.at(i).m_flags & flag) == 0, "transfer #" << i << " already has flag " << flag << ": " << m_transfers.at(i).m_flags << ", transfer info:" << ENDL << epee::serialization::store_t_to_json(m_transfers.at(i))); - } - } - - for (uint64_t i : selected_transfers) - { - uint32_t flags_before = m_transfers.at(i).m_flags; - m_transfers.at(i).m_flags |= flag; - WLT_LOG_L1("marking transfer #" << std::setfill('0') << std::right << std::setw(3) << i << " with flag " << flag << " : " << flags_before << " -> " << m_transfers.at(i).m_flags << - (reason.empty() ? "" : ", reason: ") << reason); - } - } - //---------------------------------------------------------------------------------------------------- - void wallet2::clear_transfers_from_flag(const std::vector& selected_transfers, uint32_t flag, const std::string& reason /* = empty_string */) noexcept - { - TRY_ENTRY(); - for (uint64_t i : selected_transfers) - { - //if (i >= m_transfers.size()) - //{ - // WLT_LOG_ERROR("INTERNAL ERROR: i: " << i << " >= m_transfers.size() : " << m_transfers.size()); - // continue; - //} - auto& tr_entry = m_transfers.at(i); - uint32_t flags_before = tr_entry.m_flags; - tr_entry.m_flags &= ~flag; - WLT_LOG_L1("clearing transfer #" << std::setfill('0') << std::right << std::setw(3) << i << " from flag " << flag << " : " << flags_before << " -> " << tr_entry.m_flags << - (reason.empty() ? "" : ", reason: ") << reason); - } - CATCH_ENTRY_NO_RETURN(); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::exception_handler() - { - m_found_free_amounts.clear(); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::exception_handler() const - { - // do nothing - // TODO: is it correct? - } - //---------------------------------------------------------------------------------------------------- - void wallet2::mark_transfers_as_spent(const std::vector& selected_transfers, const std::string& reason /* = empty_string */) - { - // TODO: design a safe undo for this operation - mark_transfers_with_flag(selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, reason); - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::extract_offers_from_transfer_entry(size_t i, std::unordered_map& offers_local) - { - //TODO: this code supports only one market(offer) instruction per transaction - load_wallet_transfer_info_flags(m_transfer_history[i]); - switch (m_transfer_history[i].tx_type) - { - case GUI_TX_TYPE_PUSH_OFFER: - { - bc_services::offer_details od; - if (!get_type_in_variant_container(m_transfer_history[i].marketplace_entries, od)) - { - WLT_LOG_ERROR("Transaction history entry " << i << " market as type " << m_transfer_history[i].tx_type << " but get_type_in_variant_container returned false for bc_services::offer_details"); - break; - } - crypto::hash h = null_hash; - h = m_transfer_history[i].tx_hash; - bc_services::offer_details_ex& ode = offers_local[h]; - ode = AUTO_VAL_INIT(bc_services::offer_details_ex()); - static_cast(ode) = od; - //fill extra fields - ode.tx_hash = m_transfer_history[i].tx_hash; - ode.index_in_tx = 0; // TODO: handle multiple offers in tx, now only one per tx is supported - ode.timestamp = m_transfer_history[i].timestamp; - ode.fee = m_transfer_history[i].fee; - ode.stopped = false; + res = false; break; } - case GUI_TX_TYPE_UPDATE_OFFER: - { - bc_services::update_offer uo; - if (!get_type_in_variant_container(m_transfer_history[i].marketplace_entries, uo)) - { - WLT_LOG_ERROR("Transaction history entry " << i << " market as type " << m_transfer_history[i].tx_type << " but get_type_in_variant_container returned false for update_offer"); - break; - } - crypto::hash h = null_hash; - h = m_transfer_history[i].tx_hash; - bc_services::offer_details_ex& ode = offers_local[h]; - ode = AUTO_VAL_INIT(bc_services::offer_details_ex()); - static_cast(ode) = uo.of; - //fill extra fields - ode.tx_hash = m_transfer_history[i].tx_hash; - ode.index_in_tx = 0; - ode.fee = m_transfer_history[i].fee; - ode.stopped = false; - ode.tx_original_hash = uo.tx_id; - //remove old transaction - crypto::hash h_old = uo.tx_id; - auto it = offers_local.find(h_old); - if (it == offers_local.end()) - { - WLT_LOG_L3("Unable to find original tx record " << h_old << " in update offer " << h); - break; - } - //keep original timestamp - ode.timestamp = it->second.timestamp; - offers_local.erase(it); - break; - } - case GUI_TX_TYPE_CANCEL_OFFER: - { - bc_services::cancel_offer co; - if (!get_type_in_variant_container(m_transfer_history[i].marketplace_entries, co)) - { - WLT_LOG_ERROR("Transaction history entry " << i << " market as type " << m_transfer_history[i].tx_type << " but get_type_in_variant_container returned false for cancel_offer"); - break; - } - crypto::hash h = co.tx_id; - auto it = offers_local.find(h); - if (it == offers_local.end()) - { - WLT_LOG_L3("Unable to find original tx record " << h << " in cancel offer " << h); - break; - } - offers_local.erase(it); - - } - default: - ; - } - - return true; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::select_my_offers(std::list& offers) - { - std::unordered_map offers_local; - - if (!m_transfer_history.size()) - return true; - - uint64_t stop_timestamp = m_core_runtime_config.get_core_time() - OFFER_MAXIMUM_LIFE_TIME; - - size_t i = m_transfer_history.size() - 1; - for (; i != 0; i--) - { - if (m_transfer_history[i].timestamp < stop_timestamp) - { - i++; - break; - } - } - if (i == 0 && m_transfer_history[0].timestamp < stop_timestamp) - i++; - if (i >= m_transfer_history.size()) - return true; - - for (; i != m_transfer_history.size(); i++) - { - extract_offers_from_transfer_entry(i, offers_local); - } - for (const auto& o : offers_local) - offers.push_back(o.second); - - return true; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::get_actual_offers(std::list& offers) - { - select_my_offers(offers); - return true; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::expand_selection_with_zc_input(assets_selection_context& needed_money_map, uint64_t fake_outputs_count, std::vector& selected_indexes) - { - free_amounts_cache_type& found_free_amounts = m_found_free_amounts[currency::native_coin_asset_id]; - auto& asset_needed_money_item = needed_money_map[currency::native_coin_asset_id]; - //need to add ZC input - for (auto it = found_free_amounts.begin(); it != found_free_amounts.end(); it++) - { - for (auto it_in_amount = it->second.begin(); it_in_amount != it->second.end(); it_in_amount++) - { - if (!m_transfers.at(*it_in_amount).is_zc()) - { - continue; - } - - if (is_transfer_ready_to_go(m_transfers.at(*it->second.begin()), fake_outputs_count)) - { - asset_needed_money_item.found_amount += it->first; - selected_indexes.push_back(*it_in_amount); - it->second.erase(it_in_amount); - if (!it->second.size()) - { - found_free_amounts.erase(it); - } - return true; - } - } - } - WLT_THROW_IF_FALSE_WALLET_EX_MES(false, error::no_zc_inputs, "At least one ZC is required for the operation, but none were found"); - return true; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::select_indices_for_transfer(assets_selection_context& needed_money_map, uint64_t fake_outputs_count, std::vector& selected_indexes) - { - for (auto& item : needed_money_map) - { - if (item.second.needed_amount == 0) - continue; - - const crypto::public_key asset_id = item.first; - asset_descriptor_base asset_info{}; - uint32_t asset_flags = 0; - if (!get_asset_info(asset_id, asset_info, asset_flags)) - WLT_LOG_L1("select_indices_for_transfer: unknown asset id: " << asset_id); - - auto asset_cache_it = m_found_free_amounts.find(asset_id); - THROW_IF_FALSE_WALLET_EX(asset_cache_it != m_found_free_amounts.end(), error::not_enough_money, item.second.found_amount, item.second.needed_amount, (uint64_t)0, asset_id, asset_info.decimal_point); - item.second.found_amount = select_indices_for_transfer(selected_indexes, asset_cache_it->second, item.second.needed_amount, fake_outputs_count, asset_id, asset_info.decimal_point); - THROW_IF_FALSE_WALLET_EX(item.second.found_amount >= item.second.needed_amount, error::not_enough_money, item.second.found_amount, item.second.needed_amount, (uint64_t)0, asset_id, asset_info.decimal_point); - } - if (m_current_context.pconstruct_tx_param && m_current_context.pconstruct_tx_param->need_at_least_1_zc) - { - bool found_zc_input = false; - for (auto i : selected_indexes) - { - if (m_transfers.at(i).is_zc()) - { - found_zc_input = true; - break; - } - } - if (!found_zc_input) - { - expand_selection_with_zc_input(needed_money_map, fake_outputs_count, selected_indexes); - } - } - - return true; - } - //---------------------------------------------------------------------------------------------------- - uint64_t wallet2::select_indices_for_transfer(std::vector& selected_indexes, free_amounts_cache_type& found_free_amounts, uint64_t needed_money, uint64_t fake_outputs_count_, - const crypto::public_key& asset_id, size_t decimal_point) - { - WLT_LOG_GREEN("Selecting indices for transfer of " << print_money_brief(needed_money, decimal_point) << " with " << fake_outputs_count_ << " fake outs, found_free_amounts.size()=" << found_free_amounts.size() << - (asset_id == native_coin_asset_id ? std::string() : std::string(", asset_id: ") + crypto::pod_to_hex(asset_id)) << "...", LOG_LEVEL_0); - uint64_t found_money = 0; - size_t outputs_found = 0; - std::string selected_amounts_str; - while (found_money < needed_money && found_free_amounts.size()) - { - auto it = found_free_amounts.lower_bound(needed_money - found_money); - if (!(it != found_free_amounts.end() && it->second.size())) - { - it = --found_free_amounts.end(); - WLT_CHECK_AND_ASSERT_MES(it->second.size(), 0, "internal error: empty found_free_amounts map"); - } - uint64_t fake_outputs_count = fake_outputs_count_; - if (!this->is_auditable() && m_transfers.at(*it->second.begin()).is_zc()) - { - fake_outputs_count = m_core_runtime_config.hf4_minimum_mixins; - } - if (is_transfer_ready_to_go(m_transfers.at(*it->second.begin()), fake_outputs_count)) - { - found_money += it->first; - selected_indexes.push_back(*it->second.begin()); - WLT_LOG_L2("Selected index: " << *it->second.begin() << ", transfer_details: " << ENDL << epee::serialization::store_t_to_json(m_transfers.at(*it->second.begin()))); - selected_amounts_str += (selected_amounts_str.empty() ? "" : "+") + print_money_brief(it->first, decimal_point); - ++outputs_found; - } - it->second.erase(it->second.begin()); - if (!it->second.size()) - found_free_amounts.erase(it); - - } - - WLT_LOG_GREEN("Found " << print_money_brief(found_money, decimal_point) << " as " << outputs_found << " out(s): " << selected_amounts_str << ", found_free_amounts.size()=" << found_free_amounts.size() << - (asset_id == native_coin_asset_id ? std::string() : std::string(", asset_id: ") + crypto::pod_to_hex(asset_id)), LOG_LEVEL_0); - return found_money; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::is_transfer_ready_to_go(const transfer_details& td, uint64_t fake_outputs_count) const - { - if (is_transfer_able_to_go(td, fake_outputs_count) && is_transfer_unlocked(td)) - { - return true; - } - return false; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::is_transfer_able_to_go(const transfer_details& td, uint64_t fake_outputs_count) const - { - if (!td.is_spendable()) - return false; - - const tx_out_v& out_v = td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]; - - uint8_t mix_attr = CURRENCY_TO_KEY_OUT_RELAXED; - if (get_mix_attr_from_tx_out_v(out_v, mix_attr)) - { - if (!currency::is_mixattr_applicable_for_fake_outs_counter(td.m_ptx_wallet_info->m_tx.version, mix_attr, fake_outputs_count, m_core_runtime_config)) - return false; - } - - VARIANT_SWITCH_BEGIN(out_v); - VARIANT_CASE_CONST(tx_out_bare, o); - if (o.target.type() == typeid(txout_htlc)) - { - if (fake_outputs_count != 0) - return false; - } - VARIANT_SWITCH_END(); - - return true; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::prepare_free_transfers_cache(uint64_t fake_outputs_count) - { - WLT_LOG_L2("Preparing transfers_cache..."); - uint64_t count = 0; - if (!m_found_free_amounts.size() || fake_outputs_count != m_fake_outputs_count) - { - m_found_free_amounts.clear(); - for (const auto& tr : m_transfers) - { - uint64_t i = tr.first; - const transfer_details& td = tr.second; - uint64_t fake_outputs_count_local = fake_outputs_count; - if (td.m_zc_info_ptr) - { - //zarcanum out, redefine fake_outputs_count - fake_outputs_count_local = this->is_auditable() ? 0 : m_core_runtime_config.hf4_minimum_mixins; - } - if (is_transfer_able_to_go(td, fake_outputs_count_local)) - { - //@#@ - m_found_free_amounts[td.get_asset_id()][td.amount()].insert(i); - count++; - } - } - m_fake_outputs_count = fake_outputs_count; - } - - WLT_LOG_L2("Transfers_cache prepared. " << count << " items cached for " << m_found_free_amounts.size() << " amounts"); - return true; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::add_transfers_to_transfers_cache(const std::vector& indexs) - { - //@#@ - for (auto i : indexs) - add_transfer_to_transfers_cache(m_transfers.at(i).amount(), i, m_transfers.at(i).get_asset_id()); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::add_transfer_to_transfers_cache(uint64_t amount, uint64_t index, const crypto::public_key& asset_id /* = currency::native_coin_asset_id */) - { - m_found_free_amounts[asset_id][amount].insert(index); - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::select_transfers(assets_selection_context& needed_money_map, size_t fake_outputs_count, uint64_t /*dust_threshold*/, std::vector& selected_indicies) - { - prepare_free_transfers_cache(fake_outputs_count); - return select_indices_for_transfer(needed_money_map, fake_outputs_count, selected_indicies); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::add_sent_unconfirmed_tx(const currency::transaction& tx, - const std::vector& recipients, - const std::vector& selected_indicies, - const std::vector& splitted_dsts) - { - PROFILE_FUNC("wallet2::add_sent_unconfirmed_tx"); - process_transaction_context ptc(tx); - ptc.recipients = recipients; - for (auto addr : recipients) - ptc.remote_aliases.push_back(get_alias_for_address(addr)); - - handle_unconfirmed_tx(ptc); - wallet_public::wallet_transfer_info& unconfirmed_wti = misc_utils::get_or_insert_value_initialized(m_unconfirmed_txs, currency::get_transaction_hash(tx)); - //override some info that might be missing - unconfirmed_wti.selected_indicies = selected_indicies; - } - //---------------------------------------------------------------------------------------------------- - std::string wallet2::get_alias_for_address(const std::string& addr) - { - std::vector aliases = get_aliases_for_address(addr); - if (aliases.size()) - return aliases.front(); - return ""; - } - //---------------------------------------------------------------------------------------------------- - std::vector wallet2::get_aliases_for_address(const std::string& addr) - { - PROFILE_FUNC("wallet2::get_alias_for_address"); - currency::COMMAND_RPC_GET_ALIASES_BY_ADDRESS::request req = addr; - currency::COMMAND_RPC_GET_ALIASES_BY_ADDRESS::response res = AUTO_VAL_INIT(res); - std::vector aliases; - if (!m_core_proxy->call_COMMAND_RPC_GET_ALIASES_BY_ADDRESS(req, res)) - { - WLT_LOG_L0("Failed to COMMAND_RPC_GET_ALIASES_BY_ADDRESS"); - return aliases; - } - for (auto& e : res.alias_info_list) - { - aliases.push_back(e.alias); - } - return aliases; - } - //---------------------------------------------------------------------------------------------------- - 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, - currency::transaction& tx) - { - transfer(dsts, fake_outputs_count, unlock_time, fee, extra, attachments, get_current_split_strategy(), 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) - { - currency::transaction tx; - transfer(dsts, fake_outputs_count, unlock_time, fee, extra, attachments, tx); - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::is_connected_to_net() - { - currency::COMMAND_RPC_GET_INFO::request req = AUTO_VAL_INIT(req); - currency::COMMAND_RPC_GET_INFO::response res = AUTO_VAL_INIT(res); - if (!m_core_proxy->call_COMMAND_RPC_GET_INFO(req, res)) - { - WLT_LOG_L0("Failed to COMMAND_RPC_GET_INFO"); - return false; - } - return (res.synchronized_connections_count) ? true : false; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::process_genesis_if_needed(const currency::block& genesis, const std::vector* pglobal_indexes) - { - if (!m_transfers.empty() || !m_key_images.empty()) - return; - - THROW_IF_TRUE_WALLET_EX(get_blockchain_current_size() > 1, error::wallet_internal_error, "Can't change wallet genesis block once the blockchain has been populated"); - - crypto::hash genesis_hash = get_block_hash(genesis); - if (get_blockchain_current_size() == 1 && m_chain.get_genesis() != genesis_hash) - WLT_LOG_L0("Changing genesis block for wallet " << m_account.get_public_address_str() << ":" << ENDL << " " << m_chain.get_genesis() << " -> " << genesis_hash); - - //m_blockchain.clear(); - - //m_blockchain.push_back(genesis_hash); - m_chain.set_genesis(genesis_hash); - m_last_bc_timestamp = genesis.timestamp; - - WLT_LOG_L2("Processing genesis block: " << genesis_hash); - process_new_transaction(genesis.miner_tx, 0, genesis, pglobal_indexes); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::set_genesis(const crypto::hash& genesis_hash) - { - THROW_IF_TRUE_WALLET_EX(get_blockchain_current_size() != 1, error::wallet_internal_error, "Can't change wallet genesis hash once the blockchain has been populated"); - WLT_LOG_L0("Changing genesis hash for wallet " << m_account.get_public_address_str() << ":" << ENDL << " " << m_chain.get_genesis() << " -> " << genesis_hash); - m_chain.set_genesis(genesis_hash); - } - //---------------------------------------------------------------------------------------------------- - 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 - << 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.", - LOG_LEVEL_0); - } - //---------------------------------------------------------------------------------------------------- - uint64_t wallet2::get_tx_expiration_median() const - { - currency::COMMAND_RPC_GET_CURRENT_CORE_TX_EXPIRATION_MEDIAN::request req = AUTO_VAL_INIT(req); - currency::COMMAND_RPC_GET_CURRENT_CORE_TX_EXPIRATION_MEDIAN::response res = AUTO_VAL_INIT(res); - m_core_proxy->call_COMMAND_RPC_GET_CURRENT_CORE_TX_EXPIRATION_MEDIAN(req, res); - - if (res.status != API_RETURN_CODE_OK) - { - WLT_LOG_ERROR("COMMAND_RPC_GET_CURRENT_CORE_TX_EXPIRATION_MEDIAN failed, status: " << res.status); - return 0; - } - - return res.expiration_median; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::print_source_entry(std::stringstream& output, const currency::tx_source_entry& src) const - { - std::stringstream ss; - for (auto& el : src.outputs) - ss << el.out_reference << " "; - - output << "amount: " << print_money_brief(src.amount, get_asset_decimal_point(src.asset_id)) << (src.is_zc() ? "" : " (bare)"); - - if (src.asset_id != currency::native_coin_asset_id) - output << " (" << print16(src.asset_id) << ")"; - - output << ", real_output: " << src.real_output - << ", real_output_in_tx_index: " << src.real_output_in_tx_index - << ", indexes: " << ss.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; - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::is_need_to_split_outputs() - { - return !is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::prepare_tx_destinations(const assets_selection_context& needed_money_map, - detail::split_strategy_id_t destination_split_strategy_id, - const tx_dust_policy& dust_policy, - const std::vector& dsts, - uint8_t tx_flags, - std::vector& final_destinations) - { - - /* - let's account all processes assets, so if there are some destinations - that haven't been present in needed_money_map we can add it to final destinations - (could be in ionic swaps for example) - */ - std::unordered_set processed_assets; - for (auto& el : needed_money_map) - { - prepare_tx_destinations(el.second.needed_amount, el.second.found_amount, destination_split_strategy_id, dust_policy, dsts, el.first, final_destinations); - processed_assets.insert(el.first); - } - - if (is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM)) - { - // special case for asset minting destinations - for (auto& dst : dsts) - if (dst.asset_id == currency::null_pkey || processed_assets.count(dst.asset_id) == 0) - final_destinations.emplace_back(dst.amount, dst.addr, dst.asset_id); - - //exclude destinations that supposed to be burned (for ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN) - for (size_t i = 0; i < final_destinations.size(); ) - { - if (final_destinations[i].addr.size() == 0) - { - final_destinations.erase(final_destinations.begin() + i); - } - else - { - i++; - } - } - - if (!(tx_flags & TX_FLAG_SIGNATURE_MODE_SEPARATE)) - { - if (final_destinations.empty()) - { - // if there's no destinations -- make CURRENCY_TX_MIN_ALLOWED_OUTS empty destinations - for (size_t i = 0; i < CURRENCY_TX_MIN_ALLOWED_OUTS; ++i) - final_destinations.emplace_back(0, m_account.get_public_address()); - } - else if (final_destinations.size() < CURRENCY_TX_MIN_ALLOWED_OUTS) - { - // if there's not ehough destinations items (i.e. outputs), split the last one - tx_destination_entry de = final_destinations.back(); - final_destinations.pop_back(); - size_t items_to_be_added = CURRENCY_TX_MIN_ALLOWED_OUTS - final_destinations.size(); - // TODO: consider allowing to set them somewhere - size_t num_digits_to_keep = CURRENCY_TX_OUTS_RND_SPLIT_DIGITS_TO_KEEP; - decompose_amount_randomly(de.amount, [&](uint64_t amount) { de.amount = amount; final_destinations.push_back(de); }, items_to_be_added, num_digits_to_keep); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(final_destinations.size() == CURRENCY_TX_MIN_ALLOWED_OUTS, - "can't get necessary number of outputs using decompose_amount_randomly(), got " << final_destinations.size() << " while mininum is " << CURRENCY_TX_MIN_ALLOWED_OUTS); - } - } - } - } - //---------------------------------------------------------------------------------------------------- - 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, - const crypto::public_key& asset_id, - std::vector& final_destinations) - { - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(found_money >= needed_money, "found_money = " << print_money_brief(found_money) << " is less than needed_money = " << print_money_brief(needed_money) << ", assed_id: " << asset_id); - - if (is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM)) - { - for (auto& dst : dsts) - { - if (dst.asset_id == asset_id) - final_destinations.emplace_back(dst); - } - if (found_money > needed_money) - final_destinations.emplace_back(found_money - needed_money, m_account.get_public_address(), asset_id); // returning back the change - } - else - { - // pre-HF4 - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(asset_id == currency::native_coin_asset_id, "assets are not allowed prior to HF4"); - 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().account_address); - change_dts.amount = found_money - needed_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_destinations, 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); - - if (0 != dust && !dust_policy.add_to_fee) - { - final_destinations.emplace_back(dust, dust_policy.addr_for_dust); - } - } - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::prepare_transaction(construct_tx_param& ctp, currency::finalize_tx_param& ftp, const mode_separate_context& msc) - { - - SET_CONTEXT_OBJ_FOR_SCOPE(pconstruct_tx_param, ctp); - SET_CONTEXT_OBJ_FOR_SCOPE(pfinalize_tx_param, ftp); - SET_CONTEXT_OBJ_FOR_SCOPE(pmode_separate_context, msc); - - TIME_MEASURE_START_MS(get_needed_money_time); - - const currency::transaction& tx_for_mode_separate = msc.tx_for_mode_separate; - assets_selection_context needed_money_map = get_needed_money(ctp.fee, ctp.dsts); - if (this->is_auditable() && ctp.fake_outputs_count > 0) - { - WLT_THROW_IF_FALSE_WITH_CODE(false, "WALLET_RPC_ERROR_CODE_WRONG_MIXINS_FOR_AUDITABLE_WALLET", "WALLET_RPC_ERROR_CODE_WRONG_MIXINS_FOR_AUDITABLE_WALLET"); - } - ftp.ado_current_asset_owner = ctp.ado_current_asset_owner; - ftp.pthirdparty_sign_handler = ctp.pthirdparty_sign_handler; - // - // TODO @#@# need to do refactoring over this part to support hidden amounts and asset_id - // - 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"); - if (ftp.tx_version > TRANSACTION_VERSION_PRE_HF4) - { - for (const auto& el : msc.proposal_info.to_initiator) - needed_money_map[el.asset_id].needed_amount += el.amount; - } - - if (msc.escrow) - needed_money_map[currency::native_coin_asset_id].needed_amount += (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.create_utxo_defragmentation_tx) - { - try - { - if (!prepare_tx_sources_for_defragmentation_tx(ftp.sources, ftp.selected_transfers, needed_money_map[currency::native_coin_asset_id].found_amount)) - return false; - } - catch (const error::not_enough_outs_to_mix&) { return false; } // if there's not enough decoys, return false to indicate minor non-fatal error - } - else if (ctp.htlc_tx_id != currency::null_hash) - { - //htlc - //@#@ need to do refactoring over this part to support hidden amounts and asset_id - prepare_tx_sources_htlc(ctp.htlc_tx_id, ctp.htlc_origin, ftp.sources, needed_money_map[currency::native_coin_asset_id].found_amount); - WLT_THROW_IF_FALSE_WITH_CODE(ctp.dsts.size() == 1, - "htlc: unexpected ctp.dsts.size() =" << ctp.dsts.size(), API_RETURN_CODE_INTERNAL_ERROR); - - WLT_THROW_IF_FALSE_WITH_CODE(needed_money_map[currency::native_coin_asset_id].found_amount > ctp.fee, - "htlc: found money less then fee", API_RETURN_CODE_INTERNAL_ERROR); - - //fill amount - ctp.dsts.begin()->amount = needed_money_map[currency::native_coin_asset_id].found_amount - ctp.fee; - - } - else if (ctp.multisig_id != currency::null_hash) - { - //multisig - //@#@ need to do refactoring over this part to support hidden amounts and asset_id - prepare_tx_sources(ctp.multisig_id, ftp.sources, needed_money_map[currency::native_coin_asset_id].found_amount); - } - else - { - //regular tx - prepare_tx_sources(needed_money_map, ctp.fake_outputs_count, ctp.dust_policy.dust_threshold, ftp.sources, ftp.selected_transfers); - } - TIME_MEASURE_FINISH_MS(prepare_tx_sources_time); - - TIME_MEASURE_START_MS(prepare_tx_destinations_time); - prepare_tx_destinations(needed_money_map, static_cast(ctp.split_strategy_id), ctp.dust_policy, ctp.dsts, ctp.flags, 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; - ftp.spend_pub_key = m_account.get_public_address().spend_public_key; - - /* 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);*/ - return true; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::finalize_transaction(currency::finalize_tx_param& ftp, currency::transaction& tx, crypto::secret_key& tx_key, bool broadcast_tx, bool store_tx_secret_key /* = true */) - { - currency::finalized_tx result = AUTO_VAL_INIT(result); - result.tx = tx; - result.one_time_key = tx_key; - finalize_transaction(ftp, result, broadcast_tx, store_tx_secret_key); - tx = result.tx; - tx_key = result.one_time_key; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::finalize_transaction(currency::finalize_tx_param& ftp, currency::finalized_tx& result, bool broadcast_tx, bool store_tx_secret_key /* = true */) - { - // NOTE: if broadcast_tx == true callback rise_on_transfer2() may be called at the end of this function. - // That callback may call balance(), so it's important to have all used/spending transfers - // to be correctly marked with corresponding flags PRIOR to calling finalize_transaction() - - // broadcasting tx without secret key storing is forbidden to avoid lost key issues - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(!broadcast_tx || store_tx_secret_key, "finalize_tx is requested to broadcast a tx without storing the key"); - - THROW_IF_FALSE_WALLET_EX_MES(ftp.sources.size() <= CURRENCY_TX_MAX_ALLOWED_INPUTS, error::tx_too_big, "Too many inputs: " << ftp.sources.size() << ", maximum allowed is " << CURRENCY_TX_MAX_ALLOWED_INPUTS << "."); - - bool r = currency::construct_tx(m_account.get_keys(), - ftp, result); - //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); - uint64_t effective_fee = 0; - THROW_IF_FALSE_WALLET_CMN_ERR_EX(!get_tx_fee(result.tx, effective_fee) || effective_fee <= WALLET_TX_MAX_ALLOWED_FEE, "tx fee is WAY too big: " << print_money_brief(effective_fee) << ", maximum allowed is " << print_money_brief(WALLET_TX_MAX_ALLOWED_FEE) << "."); - - //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(result.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); - - size_t tx_blob_size = tx_to_blob(result.tx).size(); - THROW_IF_FALSE_WALLET_EX_MES(tx_blob_size < CURRENCY_MAX_TRANSACTION_BLOB_SIZE, error::tx_too_big, "Transaction size: " << tx_blob_size << " bytes, transaction size limit: " << CURRENCY_MAX_TRANSACTION_BLOB_SIZE << " bytes."); - - if (store_tx_secret_key) - m_tx_keys.insert(std::make_pair(get_transaction_hash(result.tx), result.one_time_key)); - - //TIME_MEASURE_START(send_transaction_to_network_time); - if (broadcast_tx) - send_transaction_to_network(result.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(result.tx, ftp.attachments, ftp.prepared_destinations, ftp.selected_transfers); - //TIME_MEASURE_FINISH(add_sent_tx_detailed_info_time); - - // not logging success here because it's the caller's responsibility - } - //---------------------------------------------------------------------------------------------------- - 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); - } - //---------------------------------------------------------------------------------------------------- - 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, - std::string* p_unsigned_filename_or_tx_blob_str) - { - //TIME_MEASURE_START(precalculation_time); - construct_tx_param ctp = AUTO_VAL_INIT(ctp); - ctp.attachments = attachments; - ctp.crypt_address = currency::get_crypt_address_from_destinations(m_account.get_keys(), dsts); - ctp.dsts = dsts; - ctp.dust_policy = dust_policy; - ctp.extra = extra; - ctp.fake_outputs_count = fake_outputs_count; - ctp.fee = fee; - ctp.flags = flags; - // ctp.mark_tx_as_complete - // ctp.multisig_id - ctp.shuffle = shuffle; - ctp.split_strategy_id = destination_split_strategy_id; - ctp.tx_outs_attr = tx_outs_attr; - ctp.unlock_time = unlock_time; - //TIME_MEASURE_FINISH(precalculation_time); - transfer(ctp, tx, send_to_network, p_unsigned_filename_or_tx_blob_str); - } - //---------------------------------------------------------------------------------------------------- - //---------------------------------------------------------------------------------------------------- - construct_tx_param wallet2::get_default_construct_tx_param_inital() - { - construct_tx_param ctp = AUTO_VAL_INIT(ctp); - - ctp.fee = m_core_runtime_config.tx_default_fee; - ctp.dust_policy = tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD); - ctp.split_strategy_id = get_current_split_strategy(); - ctp.tx_outs_attr = CURRENCY_TO_KEY_OUT_RELAXED; - ctp.shuffle = 0; - return ctp; - } - construct_tx_param wallet2::get_default_construct_tx_param() - { - return get_default_construct_tx_param_inital(); - } - //---------------------------------------------------------------------------------------------------- - bool wallet2::store_unsigned_tx_to_file_and_reserve_transfers(const currency::finalize_tx_param& ftp, const std::string& filename, std::string* p_unsigned_tx_blob_str /* = nullptr */) - { - TIME_MEASURE_START(store_unsigned_tx_time); - blobdata bl = t_serializable_object_to_blob(ftp); - crypto::chacha_crypt(bl, m_account.get_keys().view_secret_key); - - if (!filename.empty()) - { - bool r = epee::file_io_utils::save_string_to_file(filename, bl); - CHECK_AND_ASSERT_MES(r, false, "failed to store unsigned tx to " << filename); - LOG_PRINT_L0("Transaction stored to " << filename << ". You need to sign this tx using a full-access wallet."); - } - else - { - CHECK_AND_ASSERT_MES(p_unsigned_tx_blob_str != nullptr, false, "empty filename and p_unsigned_tx_blob_str == null"); - *p_unsigned_tx_blob_str = bl; - } - - TIME_MEASURE_FINISH(store_unsigned_tx_time); - - // reserve transfers at the very end - TIME_MEASURE_START(mark_transfers_as_spent_time); - mark_transfers_with_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_COLD_SIG_RESERVATION, std::string("cold sig reservation for money transfer"), true); - TIME_MEASURE_FINISH(mark_transfers_as_spent_time); - - WLT_LOG_GREEN("[wallet::store_unsigned_tx_to_file_and_reserve_transfers]" - << " store_unsigned_tx_time: " << print_fixed_decimal_point(store_unsigned_tx_time, 3) - << ", mark_transfers_as_spent_time: " << print_fixed_decimal_point(mark_transfers_as_spent_time, 3) - , LOG_LEVEL_1); - return true; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::check_and_throw_if_self_directed_tx_with_payment_id_requested(const construct_tx_param& ctp) - { - // If someone sends coins to his own address, all tx outputs will be detected as own outputs. - // It's totally okay unless payment id is used, because it would be impossible to distinguish - // between change outs and transfer outs. Thus, such tx with a payment id can't be correctly - // obtained via RPC by the given payment id. It could be a problem for an exchange or other - // service when a user, identifyied by payment id sends coins to another user on the same - // exchange/service. Coins will be received but RPCs like get_payments won't give the transfer. - // To avoid such issues we prohibit such txs with a soft rule on sender side. - - for (auto& d : ctp.dsts) - { - for (auto& addr : d.addr) - { - if (addr != m_account.get_public_address()) - return; // at least one destination address is not our address -- it's not self-directed tx - } - } - - // it's self-directed tx - payment_id_t pid; - bool has_payment_id = get_payment_id_from_decrypted_container(ctp.attachments, pid) && !pid.empty(); - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(!has_payment_id, "sending funds to yourself with payment id is not allowed"); - } - //---------------------------------------------------------------------------------------------------- - void wallet2::transfer(construct_tx_param& ctp, - currency::transaction& tx, - bool send_to_network, - std::string* p_unsigned_filename_or_tx_blob_str) - { - currency::finalized_tx result = AUTO_VAL_INIT(result); - transfer(ctp, result, send_to_network, p_unsigned_filename_or_tx_blob_str); - tx = result.tx; - } - //---------------------------------------------------------------------------------------------------- - void wallet2::transfer(construct_tx_param& ctp, - currency::finalized_tx& result, - bool send_to_network, - std::string* p_unsigned_filename_or_tx_blob_str) - { - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(!is_auditable() || !is_watch_only(), "You can't initiate coins transfer using an auditable watch-only wallet."); // btw, watch-only wallets can call transfer() within cold-signing process - - check_and_throw_if_self_directed_tx_with_payment_id_requested(ctp); - - bool asset_operation_requested = count_type_in_variant_container(ctp.extra) != 0; - bool dont_have_zero_asset_ids_in_destinations = std::count_if(ctp.dsts.begin(), ctp.dsts.end(), [](const tx_destination_entry& de) { return de.asset_id == null_pkey; }) == 0; - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(asset_operation_requested || dont_have_zero_asset_ids_in_destinations, "zero asset id is used errounesly (no asset operation was requested)"); - - if (ctp.crypt_address.spend_public_key == currency::null_pkey) - { - ctp.crypt_address = currency::get_crypt_address_from_destinations(m_account.get_keys(), ctp.dsts); - } - - TIME_MEASURE_START(prepare_transaction_time); - currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); - ftp.pevents_dispatcher = &m_debug_events_dispatcher; - ftp.tx_version = this->get_current_tx_version(); - if (!prepare_transaction(ctp, ftp)) - { - result.was_not_prepared = true; - return; - } - TIME_MEASURE_FINISH(prepare_transaction_time); - - if (m_watch_only) - { - bool r = store_unsigned_tx_to_file_and_reserve_transfers(ftp, (p_unsigned_filename_or_tx_blob_str != nullptr ? *p_unsigned_filename_or_tx_blob_str : "zano_tx_unsigned"), p_unsigned_filename_or_tx_blob_str); - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "failed to store unsigned tx"); - WLT_LOG_GREEN("[wallet::transfer]" << " prepare_transaction_time: " << print_fixed_decimal_point(prepare_transaction_time, 3), LOG_LEVEL_0); - return; - } - - 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(result.tx))); - TIME_MEASURE_FINISH(mark_transfers_as_spent_time); - - TIME_MEASURE_START(finalize_transaction_time); - try - { - finalize_transaction(ftp, result, send_to_network); - } - catch (...) - { - clear_transfers_from_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, std::string("exception on money transfer, tx: ") + epee::string_tools::pod_to_hex(get_transaction_hash(result.tx))); - throw; - } - TIME_MEASURE_FINISH(finalize_transaction_time); - - - WLT_LOG_GREEN("[wallet::transfer]" - //<< " precalculation_time: " << print_fixed_decimal_point(precalculation_time, 3) - << ", prepare_transaction_time: " << print_fixed_decimal_point(prepare_transaction_time, 3) - << ", finalize_transaction_time: " << print_fixed_decimal_point(finalize_transaction_time, 3) - << ", mark_transfers_as_spent_time: " << print_fixed_decimal_point(mark_transfers_as_spent_time, 3) - , LOG_LEVEL_0); - - print_tx_sent_message(result.tx, std::string() + "(transfer)", ctp.fee); + mark_transfers_with_flag(current_holds, WALLET_TRANSFER_DETAIL_FLAG_BLOCKED, "check_available_sources"); } - //---------------------------------------------------------------------------------------------------- - void wallet2::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 */) + for (auto& h: holds) { - bool r = false; - outs_total = 0; - amount_total = 0; - outs_swept = 0; - amount_swept = 0; + clear_transfers_from_flag(h, WALLET_TRANSFER_DETAIL_FLAG_BLOCKED, "check_available_sources"); + add_transfers_to_transfers_cache(h); + } - std::vector selected_transfers; - std::unordered_map fake_outs_for_selected_transfers; // tr index -> fake outs count - selected_transfers.reserve(m_transfers.size()); - fake_outs_for_selected_transfers.reserve(m_transfers.size()); + + WLT_LOG_MAGENTA("[CHECK_AVAILABLE_SOURCES]: " << amounts << " res: " << res << ENDL <<" holds: " << holds, LOG_LEVEL_0); + return res; + */ + return false; +} +//---------------------------------------------------------------------------------------------------- +std::string get_random_rext(size_t len) +{ + std::string buff(len / 2, 0); + crypto::generate_random_bytes(len / 2, (void*)buff.data()); + return string_tools::buff_to_hex_nodelimer(buff); +} +//---------------------------------------------------------------------------------------------------- + +// local_transfers_struct - structure to avoid copying the whole m_transfers +struct local_transfers_struct +{ + local_transfers_struct(transfer_container& tf) :l_transfers_ref(tf) + {} + + transfer_container& l_transfers_ref; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(l_transfers_ref) + END_KV_SERIALIZE_MAP() +}; + +void wallet2::dump_trunsfers(std::stringstream& ss, bool verbose) const +{ + if (verbose) + { + + ss << "{ \"transfers\": [" << ENDL; for (const auto& tr : m_transfers) { uint64_t i = tr.first; const transfer_details& td = tr.second; - size_t fake_outs_count_for_td = is_auditable() ? 0 : (td.is_zc() ? m_core_runtime_config.hf4_minimum_mixins : fake_outs_count); - uint64_t amount = td.amount(); - if (amount < threshold_amount && td.is_native_coin() && - is_transfer_ready_to_go(td, fake_outs_count_for_td)) - { - selected_transfers.push_back(i); - r = fake_outs_for_selected_transfers.insert(std::make_pair(i, fake_outs_count_for_td)).second; - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "unable to insert: " << i << ", " << fake_outs_count_for_td); - outs_total += 1; - amount_total += amount; - } + ss << "{ \"i\": " << i << "," << ENDL; + ss << "\"entry\": " << epee::serialization::store_t_to_json(td) << "}," << ENDL; } - - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(!selected_transfers.empty(), "No spendable outputs meet the criterion"); - - // sort by amount descending in order to spend bigger outputs first - std::sort(selected_transfers.begin(), selected_transfers.end(), [this](uint64_t a, uint64_t b) { return m_transfers.at(b).amount() < m_transfers.at(a).amount(); }); - - // limit RPC request with reasonable number of sources - if (selected_transfers.size() > CURRENCY_TX_MAX_ALLOWED_INPUTS) - selected_transfers.erase(selected_transfers.begin() + CURRENCY_TX_MAX_ALLOWED_INPUTS, selected_transfers.end()); - - prefetch_global_indicies_if_needed(selected_transfers); - - size_t max_fake_outs_count = 0; - for (auto tr_idx : selected_transfers) - if (max_fake_outs_count < fake_outs_for_selected_transfers[tr_idx]) - max_fake_outs_count = fake_outs_for_selected_transfers[tr_idx]; - - static const size_t estimated_bytes_per_input = 85; - const size_t estimated_max_inputs = static_cast(CURRENCY_MAX_TRANSACTION_BLOB_SIZE / (estimated_bytes_per_input * (max_fake_outs_count + 1.5))); // estimated number of maximum tx inputs under the tx size limit - - typedef COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry out_entry; - typedef currency::tx_source_entry::output_entry tx_output_entry; - - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response rpc_get_random_outs_resp = AUTO_VAL_INIT(rpc_get_random_outs_resp); - if (max_fake_outs_count > 0) + } + else + { + boost::io::ios_flags_saver ifs(ss); + ss << "index amount spent_h g_index block block_ts flg tx out# key image" << ENDL; + for (const auto& tr : m_transfers) { - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request req = AUTO_VAL_INIT(req); - req.height_upper_limit = m_last_pow_block_h; - req.use_forced_mix_outs = false; - req.decoys_count = max_fake_outs_count + 1; - for (uint64_t i : selected_transfers) - req.amounts.push_back(m_transfers.at(i).is_zc() ? 0 : m_transfers.at(i).m_amount); - - r = m_core_proxy->call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS(req, rpc_get_random_outs_resp); - - THROW_IF_FALSE_WALLET_EX(r, error::no_connection_to_daemon, "getrandom_outs1.bin"); - THROW_IF_FALSE_WALLET_EX(rpc_get_random_outs_resp.status != API_RETURN_CODE_BUSY, error::daemon_busy, "getrandom_outs1.bin"); - THROW_IF_FALSE_WALLET_EX(rpc_get_random_outs_resp.status == API_RETURN_CODE_OK, error::get_random_outs_error, rpc_get_random_outs_resp.status); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(rpc_get_random_outs_resp.outs.size() == selected_transfers.size(), - "daemon returned wrong number of amounts for getrandom_outs1.bin: " << rpc_get_random_outs_resp.outs.size() << ", requested: " << selected_transfers.size()); - - std::vector scanty_outs; - for (COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& amount_outs : rpc_get_random_outs_resp.outs) - { - if (amount_outs.outs.size() < max_fake_outs_count) - scanty_outs.push_back(amount_outs); - } - THROW_IF_FALSE_WALLET_EX(scanty_outs.empty(), error::not_enough_outs_to_mix, scanty_outs, max_fake_outs_count); + uint64_t i = tr.first; + const transfer_details& td = tr.second; + ss << std::right << + std::setw(5) << i << " " << + std::setw(21) << print_money(td.amount()) << " " << + std::setw(7) << td.m_spent_height << " " << + std::setw(7) << td.m_global_output_index << " " << + std::setw(6) << td.m_ptx_wallet_info->m_block_height << " " << + std::setw(10) << td.m_ptx_wallet_info->m_block_timestamp << " " << + std::setw(4) << td.m_flags << " " << + get_transaction_hash(td.m_ptx_wallet_info->m_tx) << " " << + std::setw(4) << td.m_internal_output_index << " " << + td.m_key_image << ENDL; } + } +} +//---------------------------------------------------------------------------------------------------- +std::string wallet2::dump_trunsfers(bool verbose) const +{ + std::stringstream ss; + dump_trunsfers(ss, verbose); + return ss.str(); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::dump_key_images(std::stringstream& ss) +{ + for (auto& ki : m_key_images) + { + ss << "[" << ki.first << "]: " << ki.second << ENDL; + } +} +void wallet2::get_multisig_transfers(multisig_transfer_container& ms_transfers) +{ + ms_transfers = m_multisig_transfers; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::get_contracts(escrow_contracts_container& contracts) +{ + contracts = m_contracts; + return true; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::build_escrow_release_templates(crypto::hash multisig_id, + uint64_t fee, + currency::transaction& tx_release_template, + currency::transaction& tx_burn_template, + const bc_services::contract_private_details& ecrow_details) +{ + construct_tx_param construct_params = AUTO_VAL_INIT(construct_params); + construct_params.fee = fee; + construct_params.multisig_id = multisig_id; + construct_params.split_strategy_id = get_current_split_strategy(); + construct_params.dsts.resize(2); + //0 - addr_a + //1 - addr_b + construct_params.dsts[0].addr.push_back(ecrow_details.a_addr); + construct_params.dsts[1].addr.push_back(ecrow_details.b_addr); + + //generate normal escrow release + construct_params.dsts[0].amount = ecrow_details.amount_a_pledge; + construct_params.dsts[1].amount = ecrow_details.amount_b_pledge + ecrow_details.amount_to_pay; + + //in case of ecrow_details.amount_a_pledge == 0 then exclude a + if (construct_params.dsts[0].amount == 0) + construct_params.dsts.erase(construct_params.dsts.begin()); + + + tx_service_attachment tsa = AUTO_VAL_INIT(tsa); + tsa.service_id = BC_ESCROW_SERVICE_ID; + tsa.instruction = BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_NORMAL; + construct_params.extra.push_back(tsa); + { currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); ftp.tx_version = this->get_current_tx_version(); - bool is_hf4 = this->is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM); - if (!payment_id.empty()) - set_payment_id_to_tx(ftp.attachments, payment_id, is_hf4); - // put encrypted payer info into the extra - ftp.crypt_address = destination_addr; + prepare_transaction(construct_params, ftp); + crypto::secret_key sk = AUTO_VAL_INIT(sk); + finalize_transaction(ftp, tx_release_template, sk, false); + } - currency::create_and_add_tx_payer_to_container_from_address(ftp.extra, m_account.get_public_address(), get_top_block_height(), m_core_runtime_config); + //generate burn escrow + construct_params.dsts.resize(1); + construct_params.dsts[0].addr.clear(); + construct_params.dsts[0].addr.push_back(null_pub_addr); + construct_params.dsts[0].amount = ecrow_details.amount_a_pledge + ecrow_details.amount_b_pledge + ecrow_details.amount_to_pay; - ftp.flags = 0; - // ftp.multisig_id -- not required - // ftp.prepared_destinations -- will be filled by prepare_tx_destinations - // ftp.selected_transfers -- needed only at stage of broadcasting or storing unsigned tx - ftp.shuffle = false; - // ftp.sources -- will be filled in try_construct_tx - ftp.spend_pub_key = m_account.get_public_address().spend_public_key; // needed for offline signing - ftp.tx_outs_attr = CURRENCY_TO_KEY_OUT_RELAXED; - ftp.unlock_time = 0; + construct_params.extra.clear(); + tsa.instruction = BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_BURN; + construct_params.extra.push_back(tsa); + { + currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); + ftp.tx_version = this->get_current_tx_version(); + prepare_transaction(construct_params, ftp); + crypto::secret_key sk = AUTO_VAL_INIT(sk); + finalize_transaction(ftp, tx_burn_template, sk, false); + } +} +//---------------------------------------------------------------------------------------------------- +void wallet2::build_escrow_cancel_template(crypto::hash multisig_id, + uint64_t expiration_period, + currency::transaction& tx_cancel_template, + const bc_services::contract_private_details& ecrow_details) +{ + auto it = m_multisig_transfers.find(multisig_id); + THROW_IF_FALSE_WALLET_EX(it != m_multisig_transfers.end(), error::wallet_internal_error, + "unable to find multisig id"); - enum try_construct_result_t { rc_ok = 0, rc_too_few_outputs = 1, rc_too_many_outputs = 2, rc_create_tx_failed = 3 }; - auto get_result_t_str = [](try_construct_result_t t) -> const char* - { return t == rc_ok ? "rc_ok" : t == rc_too_few_outputs ? "rc_too_few_outputs" : t == rc_too_many_outputs ? "rc_too_many_outputs" : t == rc_create_tx_failed ? "rc_create_tx_failed" : "unknown"; }; + THROW_IF_FALSE_WALLET_EX(it->second.amount() > (ecrow_details.amount_a_pledge + ecrow_details.amount_to_pay + ecrow_details.amount_b_pledge), error::wallet_internal_error, + "multisig id out amount no more than escrow total amount"); - auto try_construct_tx = [this, &selected_transfers, &rpc_get_random_outs_resp, &fake_outs_for_selected_transfers, &fee, &destination_addr] - (size_t st_index_upper_boundary, currency::finalize_tx_param& ftp, uint64_t& amount_swept) -> try_construct_result_t - { - amount_swept = 0; - ftp.gen_context = tx_generation_context{}; - ftp.sources.clear(); - ftp.prepared_destinations.clear(); + construct_tx_param construct_params = AUTO_VAL_INIT(construct_params); + currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); + ftp.tx_version = this->get_current_tx_version(); + 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 = get_current_split_strategy(); + construct_params.dsts.resize(2); + //0 - addr_a + //1 - addr_b + construct_params.dsts[0].addr.push_back(ecrow_details.a_addr); + construct_params.dsts[1].addr.push_back(ecrow_details.b_addr); - // prepare inputs - ftp.sources.resize(st_index_upper_boundary); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(st_index_upper_boundary <= selected_transfers.size(), "index_upper_boundary = " << st_index_upper_boundary << ", selected_transfers.size() = " << selected_transfers.size()); - for (size_t st_index = 0; st_index < st_index_upper_boundary; ++st_index) - { - currency::tx_source_entry& src = ftp.sources[st_index]; - uint64_t tr_index = selected_transfers[st_index]; - transfer_details& td = m_transfers.at(tr_index); - src.transfer_index = tr_index; - src.amount = td.amount(); - amount_swept += src.amount; - // populate src.outputs with mix-ins - if (rpc_get_random_outs_resp.outs.size()) - { - rpc_get_random_outs_resp.outs[st_index].outs.sort([](const out_entry& a, const out_entry& b) { return a.global_amount_index < b.global_amount_index; }); - for (out_entry& daemon_oe : rpc_get_random_outs_resp.outs[st_index].outs) - { - if (td.m_global_output_index == daemon_oe.global_amount_index) - continue; - src.outputs.emplace_back(daemon_oe.global_amount_index, daemon_oe.stealth_address, daemon_oe.concealing_point, daemon_oe.amount_commitment, daemon_oe.blinded_asset_id); - if (src.outputs.size() >= fake_outs_for_selected_transfers[tr_index]) - break; - } - } + //generate cancel escrow proposal + construct_params.dsts[0].amount = ecrow_details.amount_a_pledge + ecrow_details.amount_to_pay; + construct_params.dsts[1].amount = ecrow_details.amount_b_pledge; - // insert real output into src.outputs - // TODO: bad design, we need to get rid of code duplicates below -- sowle - auto it_to_insert = std::find_if(src.outputs.begin(), src.outputs.end(), [&](const tx_output_entry& a) - { - if (a.out_reference.type().hash_code() == typeid(uint64_t).hash_code()) - return static_cast(boost::get(a.out_reference) >= td.m_global_output_index); - return false; // TODO: implement deterministics real output placement in case there're ref_by_id outs - }); - tx_output_entry real_oe = AUTO_VAL_INIT(real_oe); - txout_ref_v out_reference = td.m_global_output_index; // TODO: use ref_by_id when neccessary - std::vector::iterator interted_it = src.outputs.end(); - VARIANT_SWITCH_BEGIN(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]); - VARIANT_CASE_CONST(tx_out_bare, o) - { - VARIANT_SWITCH_BEGIN(o.target); - VARIANT_CASE_CONST(txout_to_key, o) - interted_it = src.outputs.emplace(it_to_insert, out_reference, o.key); - VARIANT_CASE_CONST(txout_htlc, htlc) - interted_it = src.outputs.emplace(it_to_insert, out_reference, htlc.pkey_refund); - VARIANT_CASE_OTHER() - { - WLT_THROW_IF_FALSE_WITH_CODE(false, - "Internal error: unexpected type of target: " << o.target.type().name(), - API_RETURN_CODE_INTERNAL_ERROR); - } - VARIANT_SWITCH_END(); - } - VARIANT_CASE_CONST(tx_out_zarcanum, o); - interted_it = src.outputs.emplace(it_to_insert, out_reference, o.stealth_address, o.concealing_point, o.amount_commitment, o.blinded_asset_id); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.is_zc(), "transfer #" << tr_index << ", amount: " << print_money_brief(td.amount()) << " is not a ZC"); - src.real_out_amount_blinding_mask = td.m_zc_info_ptr->amount_blinding_mask; - src.real_out_asset_id_blinding_mask = td.m_zc_info_ptr->asset_id_blinding_mask; - src.asset_id = td.m_zc_info_ptr->asset_id; - VARIANT_SWITCH_END(); - src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_ptx_wallet_info->m_tx); - src.real_output = interted_it - src.outputs.begin(); - src.real_output_in_tx_index = td.m_internal_output_index; - } + if (construct_params.dsts[0].amount == 0) + construct_params.dsts.erase(construct_params.dsts.begin()); + else if (construct_params.dsts[1].amount == 0) + construct_params.dsts.erase(construct_params.dsts.begin() + 1); - if (amount_swept <= fee) - return rc_too_few_outputs; + tx_service_attachment tsa = AUTO_VAL_INIT(tsa); + tsa.service_id = BC_ESCROW_SERVICE_ID; + tsa.instruction = BC_ESCROW_SERVICE_INSTRUCTION_RELEASE_CANCEL; + construct_params.extra.push_back(tsa); + currency::etc_tx_details_expiration_time expir = AUTO_VAL_INIT(expir); + expir.v = m_core_runtime_config.get_core_time() + expiration_period; + construct_params.extra.push_back(expir); - // try to construct a transaction + prepare_transaction(construct_params, ftp); + crypto::secret_key sk = AUTO_VAL_INIT(sk); + finalize_transaction(ftp, tx_cancel_template, sk, false); +} - assets_selection_context needed_money_map; - needed_money_map[currency::native_coin_asset_id] = {}; - const std::vector dsts({ tx_destination_entry(amount_swept - fee, destination_addr) }); - prepare_tx_destinations(needed_money_map, get_current_split_strategy(), tools::tx_dust_policy(), dsts, ftp.flags, ftp.prepared_destinations); +//---------------------------------------------------------------------------------------------------- +void wallet2::build_escrow_template(const bc_services::contract_private_details& ecrow_details, + size_t fake_outputs_count, + uint64_t unlock_time, + uint64_t expiration_time, + uint64_t b_release_fee, + const std::string& payment_id, + currency::transaction& tx, + std::vector& selected_transfers, + 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 = get_current_split_strategy(); + ctp.tx_outs_attr = CURRENCY_TO_KEY_OUT_RELAXED; + ctp.unlock_time = unlock_time; - currency::transaction tx = AUTO_VAL_INIT(tx); - crypto::secret_key tx_key = AUTO_VAL_INIT(tx_key); - try - { - finalize_transaction(ftp, tx, tx_key, false, false); - } - catch (error::tx_too_big&) - { - return rc_too_many_outputs; - } - catch (...) - { - return rc_create_tx_failed; - } + etc_tx_details_expiration_time t = AUTO_VAL_INIT(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; + ctp.extra.push_back(att); - return rc_ok; - }; + 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; + ctp.attachments.push_back(att); + } - size_t st_index_upper_boundary = std::min(selected_transfers.size(), estimated_max_inputs); - try_construct_result_t res = try_construct_tx(st_index_upper_boundary, ftp, amount_swept); + currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); + ftp.tx_version = this->get_current_tx_version(); + prepare_transaction(ctp, ftp); - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(res != rc_too_few_outputs, st_index_upper_boundary << " biggest unspent outputs have total amount of " << print_money_brief(amount_swept) - << " which is less than required fee: " << print_money_brief(fee) << ", transaction cannot be constructed"); + selected_transfers = ftp.selected_transfers; - if (res == rc_too_many_outputs) + finalize_transaction(ftp, tx, one_time_key, false); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::add_transfers_to_expiration_list(const std::vector& selected_transfers, const std::vector& received, uint64_t expiration, const crypto::hash& related_tx_id) +{ + // check all elements in selected_transfers for being already mentioned in m_money_expirations + std::vector selected_transfers_local; + for (auto transfer_index : selected_transfers) + { + bool found = false; + for (auto it = m_money_expirations.begin(); !found && it != m_money_expirations.end(); ++it) { - WLT_LOG_L1("sweep_below: first try of try_construct_tx(" << st_index_upper_boundary << ") returned " << get_result_t_str(res)); - size_t low_bound = 0; - size_t high_bound = st_index_upper_boundary; - currency::finalize_tx_param ftp_ok = ftp; - for (;;) + auto& st = it->selected_transfers; + found = std::find(st.begin(), st.end(), transfer_index) != st.end(); + } + if (!found) + selected_transfers_local.push_back(transfer_index); // populate selected_transfers_local only with indices, not containing in m_money_expirations + } + + if (selected_transfers_local.empty()) + return; + + m_money_expirations.push_back(AUTO_VAL_INIT(expiration_entry_info())); + m_money_expirations.back().expiration_time = expiration; + m_money_expirations.back().selected_transfers = selected_transfers_local; + m_money_expirations.back().related_tx_id = related_tx_id; + m_money_expirations.back().receved = received; + + std::stringstream ss; + for (auto tr_ind : m_money_expirations.back().selected_transfers) + { + //THROW_IF_FALSE_WALLET_INT_ERR_EX(tr_ind < m_transfers.size(), "invalid transfer index"); + uint32_t flags_before = m_transfers.at(tr_ind).m_flags; + m_transfers.at(tr_ind).m_flags |= WALLET_TRANSFER_DETAIL_FLAG_BLOCKED; + m_transfers.at(tr_ind).m_flags |= WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION; + ss << " " << std::right << std::setw(4) << tr_ind << " " << std::setw(21) << print_money(m_transfers.at(tr_ind).amount()) << " " + << std::setw(2) << std::left << flags_before << " -> " << std::setw(2) << std::left << m_transfers.at(tr_ind).m_flags << " " + << get_transaction_hash(m_transfers.at(tr_ind).m_ptx_wallet_info->m_tx) << std::endl; + } + WLT_LOG_GREEN(m_money_expirations.back().selected_transfers.size() << " transfer(s) added to expiration list:" << ENDL << + "index amount flags tx hash" << ENDL << + ss.str() << ", expire(s) at: " << expiration, LOG_LEVEL_0); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::remove_transfer_from_expiration_list(uint64_t transfer_index) +{ + //THROW_IF_FALSE_WALLET_INT_ERR_EX(transfer_index < m_transfers.size(), "invalid transfer index"); + auto& tr_entry = m_transfers.at(transfer_index); + for (auto it = m_money_expirations.begin(); it != m_money_expirations.end(); /* nothing */) + { + auto& st = it->selected_transfers; + auto jt = std::find(st.begin(), st.end(), transfer_index); + if (jt != st.end()) + { + WLT_LOG_GREEN("Transfer [" << transfer_index << "], amount: " << print_money(tr_entry.amount()) << ", tx: " << get_transaction_hash(tr_entry.m_ptx_wallet_info->m_tx) << + " was removed from the expiration list", LOG_LEVEL_0); + st.erase(jt); + if (st.empty()) { - if (low_bound + 1 >= high_bound) - { - st_index_upper_boundary = low_bound; - res = rc_ok; - ftp = ftp_ok; - break; - } - st_index_upper_boundary = (low_bound + high_bound) / 2; - try_construct_result_t res = try_construct_tx(st_index_upper_boundary, ftp, amount_swept); - WLT_LOG_L1("sweep_below: try_construct_tx(" << st_index_upper_boundary << ") returned " << get_result_t_str(res)); - if (res == rc_ok) - { - low_bound = st_index_upper_boundary; - ftp_ok = ftp; - } - else if (res == rc_too_many_outputs) - { - high_bound = st_index_upper_boundary; - } - else - break; + it = m_money_expirations.erase(it); + continue; } } + ++it; + } + // clear proposal reservation flag and blocked flag + uint32_t flags_before = tr_entry.m_flags; + tr_entry.m_flags &= ~WALLET_TRANSFER_DETAIL_FLAG_BLOCKED; + tr_entry.m_flags &= ~WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION; + if (flags_before != tr_entry.m_flags) + { + WLT_LOG_BLUE("Transfer [" << transfer_index << "] was cleared from escrow proposal reservation, flags: " << flags_before << " -> " << tr_entry.m_flags << ", reason: intentional removing from expiration list", LOG_LEVEL_0); + } - if (res != rc_ok) + // (don't change m_spent flag, because transfer status is unclear - the caller should take care of it) +} +//---------------------------------------------------------------------------------------------------- +void wallet2::send_escrow_proposal(const wallet_public::create_proposal_param& wp, + currency::transaction& proposal_tx, + currency::transaction& escrow_template_tx) +{ + return send_escrow_proposal(wp.details, wp.fake_outputs_count, wp.unlock_time, wp.expiration_period, wp.fee, wp.b_fee, wp.payment_id, proposal_tx, escrow_template_tx); +} +//---------------------------------------------------------------------------------------------------- +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, + uint64_t fee, + uint64_t b_release_fee, + const std::string& payment_id, + currency::transaction& tx, + currency::transaction& template_tx) +{ + if (!is_connected_to_net()) + { + THROW_IF_TRUE_WALLET_EX(true, error::wallet_internal_error, + "Transfer attempt while daemon offline"); + } + + 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; + 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, get_multisig_out_index(template_tx.vout)); + + 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, contract: " + epee::string_tools::pod_to_hex(ms_id)); + + 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; + 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); + + 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 = get_current_split_strategy(); + ctp.tx_outs_attr = CURRENCY_TO_KEY_OUT_RELAXED; + ctp.unlock_time = unlock_time; + + currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); + ftp.tx_version = this->get_current_tx_version(); + try + { + prepare_transaction(ctp, ftp); + crypto::secret_key sk = AUTO_VAL_INIT(sk); + finalize_transaction(ftp, tx, sk, false); + } + catch (...) + { + clear_transfers_from_flag(selected_transfers_for_template, mask_to_mark_escrow_template_locked_transfers, "undo prepared escrow template tx"); // don't forget to unlock template transfers if smth went wrong + add_transfers_to_transfers_cache(selected_transfers_for_template); + throw; + } + + send_transaction_to_network(tx); + + 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.attachments, ftp.prepared_destinations, ftp.selected_transfers); + + print_tx_sent_message(tx, "(from multisig)", fee); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::create_htlc_proposal(uint64_t amount, const currency::account_public_address& addr, uint64_t lock_blocks_count, currency::transaction& tx, const crypto::hash& htlc_hash, std::string& origin) +{ + construct_tx_param ctp = get_default_construct_tx_param(); + ctp.fee = TX_DEFAULT_FEE; + ctp.dsts.resize(1); + ctp.dsts.back().addr.push_back(addr); + ctp.dsts.back().amount = amount; + destination_option_htlc_out& htlc_option = ctp.dsts.back().htlc_options; + htlc_option.expiration = lock_blocks_count; //about 12 hours + htlc_option.htlc_hash = htlc_hash; + + currency::create_and_add_tx_payer_to_container_from_address(ctp.extra, + get_account().get_keys().account_address, get_top_block_height(), get_core_runtime_config()); + + finalized_tx ft = AUTO_VAL_INIT(ft); + this->transfer(ctp, ft, true, nullptr); + origin = ft.htlc_origin; + tx = ft.tx; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::get_list_of_active_htlc(std::list& htlcs, bool only_redeem_txs) +{ + for (auto htlc_entry : m_active_htlcs_txid) + { + //auto it = m_transfers.find(htlc_entry.second); + //if (it == m_transfers.end()) + // continue; + //const transfer_details& td = it->second; + const transfer_details& td = m_transfers.at(htlc_entry.second); + if (only_redeem_txs && !(td.m_flags & WALLET_TRANSFER_DETAIL_FLAG_HTLC_REDEEM)) { - uint64_t amount_min = UINT64_MAX, amount_max = 0, amount_sum = 0; - for (auto& i : selected_transfers) - { - uint64_t amount = m_transfers.at(i).amount(); - amount_min = std::min(amount_min, amount); - amount_max = std::max(amount_max, amount); - amount_sum += amount; - } - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(false, "try_construct_tx failed with result: " << get_result_t_str(res) << " (" << res << ")" << - ", selected_transfers stats:\n" << - " outs: " << selected_transfers.size() << ENDL << - " amount min: " << print_money(amount_min) << ENDL << - " amount max: " << print_money(amount_max) << ENDL << - " amount avg: " << (selected_transfers.empty() ? std::string("n/a") : print_money(amount_sum / selected_transfers.size()))); + continue; + } + wallet_public::htlc_entry_info entry = AUTO_VAL_INIT(entry); + entry.tx_id = htlc_entry.first; + if (td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].type() != typeid(tx_out_bare)) + { + //@#@ + LOG_ERROR("Unexpected output type in get_list_of_active_htlc:" << td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].type().name()); + continue; + } + const tx_out_bare out_b = boost::get(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]); + entry.amount = out_b.amount; + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(out_b.target.type() == typeid(txout_htlc), + "[get_list_of_active_htlc]Internal error: unexpected type of out"); + const txout_htlc& htlc = boost::get(out_b.target); + entry.sha256_hash = htlc.htlc_hash; + + currency::tx_payer payer = AUTO_VAL_INIT(payer); + if (currency::get_type_in_variant_container(td.varian_options, payer)) + entry.counterparty_address = payer.acc_addr; + + entry.is_redeem = td.m_flags & WALLET_TRANSFER_DETAIL_FLAG_HTLC_REDEEM ? true : false; + htlcs.push_back(entry); + } +} +//---------------------------------------------------------------------------------------------------- +void wallet2::redeem_htlc(const crypto::hash& htlc_tx_id, const std::string& origin) +{ + currency::transaction result_tx = AUTO_VAL_INIT(result_tx); + return redeem_htlc(htlc_tx_id, origin, result_tx); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::redeem_htlc(const crypto::hash& htlc_tx_id, const std::string& origin, currency::transaction& result_tx) +{ + + construct_tx_param ctp = get_default_construct_tx_param(); + ctp.fee = TX_DEFAULT_FEE; + ctp.htlc_tx_id = htlc_tx_id; + ctp.htlc_origin = origin; + ctp.dsts.resize(1); + ctp.dsts.back().addr.push_back(m_account.get_keys().account_address); + + auto it = m_active_htlcs_txid.find(htlc_tx_id); + WLT_THROW_IF_FALSE_WITH_CODE(it != m_active_htlcs_txid.end(), + "htlc not found with tx_id = " << htlc_tx_id, API_RETURN_CODE_NOT_FOUND); + + ctp.dsts.back().amount = m_transfers.at(it->second).amount() - ctp.fee; + this->transfer(ctp, result_tx, true, nullptr); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::check_htlc_redeemed(const crypto::hash& htlc_tx_id, std::string& origin, crypto::hash& redeem_tx_id) +{ + auto it = m_active_htlcs_txid.find(htlc_tx_id); + + WLT_THROW_IF_FALSE_WITH_CODE(it != m_active_htlcs_txid.end(), + "htlc not found with tx_id = " << htlc_tx_id, API_RETURN_CODE_NOT_FOUND); + + transfer_details_extra_option_htlc_info htlc_options = AUTO_VAL_INIT(htlc_options); + if (!currency::get_type_in_variant_container(m_transfers.at(it->second).varian_options, htlc_options)) + { + return false; + } + if (htlc_options.origin.size()) + { + origin = htlc_options.origin; + redeem_tx_id = htlc_options.redeem_tx_id; + return true; + } + return false; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::create_ionic_swap_proposal(const wallet_public::ionic_swap_proposal_info& proposal_details, const currency::account_public_address& destination_addr, wallet_public::ionic_swap_proposal& proposal) +{ + std::vector selected_transfers_for_template; + + return build_ionic_swap_template(proposal_details, destination_addr, proposal, selected_transfers_for_template); + + //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 ionic_swap"); + //return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::build_ionic_swap_template(const wallet_public::ionic_swap_proposal_info& proposal_detais, const currency::account_public_address& destination_addr, + wallet_public::ionic_swap_proposal& proposal, + std::vector& selected_transfers) +{ + WLT_THROW_IF_FALSE_WITH_CODE(proposal_detais.fee_paid_by_a >= get_current_minimum_network_fee(), "Error at build_ionic_swap_template, ", API_RETURN_CODE_WALLET_FEE_TOO_LOW); + + construct_tx_param ctp = get_default_construct_tx_param(); + + //ctp.fake_outputs_count = proposal_detais.mixins; + ctp.fee = proposal_detais.fee_paid_by_a; + ctp.flags = TX_FLAG_SIGNATURE_MODE_SEPARATE; + ctp.mark_tx_as_complete = false; + ctp.crypt_address = destination_addr; + + ctp.dsts.resize(proposal_detais.to_finalizer.size() + proposal_detais.to_initiator.size()); + size_t i = 0; + // Here is an proposed for exchange funds + for (; i != proposal_detais.to_finalizer.size(); i++) + { + ctp.dsts[i].amount = proposal_detais.to_finalizer[i].amount; + ctp.dsts[i].amount_to_provide = proposal_detais.to_finalizer[i].amount; + ctp.dsts[i].flags |= tx_destination_entry_flags::tdef_explicit_amount_to_provide; + ctp.dsts[i].addr.push_back(destination_addr); + ctp.dsts[i].asset_id = proposal_detais.to_finalizer[i].asset_id; + } + // Here is an expected in return funds + std::vector for_expiration_list; + for (size_t j = 0; j != proposal_detais.to_initiator.size(); j++, i++) + { + ctp.dsts[i].amount = proposal_detais.to_initiator[j].amount; + ctp.dsts[i].amount_to_provide = 0; + ctp.dsts[i].flags |= tx_destination_entry_flags::tdef_explicit_amount_to_provide; + ctp.dsts[i].addr.push_back(m_account.get_public_address()); + ctp.dsts[i].asset_id = proposal_detais.to_initiator[j].asset_id; + for_expiration_list.push_back(payment_details_subtransfer{ ctp.dsts[i].asset_id, ctp.dsts[i].amount }); + } + + currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); + ftp.mode_separate_fee = ctp.fee; + ftp.tx_version = this->get_current_tx_version(); + prepare_transaction(ctp, ftp); + + selected_transfers = ftp.selected_transfers; + currency::finalized_tx finalize_result = AUTO_VAL_INIT(finalize_result); + finalize_transaction(ftp, finalize_result, false); + for (uint64_t i : selected_transfers) + m_transfers.at(i).m_flags &= ~WALLET_TRANSFER_DETAIL_FLAG_BLOCKED; + + //add_transfers_to_expiration_list(selected_transfers, for_expiration_list, this->get_core_runtime_config().get_core_time() + proposal_detais.expiration_time, currency::null_hash); + + //wrap it all + proposal.tx_template = finalize_result.tx; + wallet_public::ionic_swap_proposal_context ispc = AUTO_VAL_INIT(ispc); + ispc.gen_context = finalize_result.ftp.gen_context; + //ispc.one_time_skey = finalize_result.one_time_key; + std::string proposal_context_blob = t_serializable_object_to_blob(ispc); + proposal.encrypted_context = crypto::chacha_crypt(static_cast(proposal_context_blob), finalize_result.derivation); + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::get_ionic_swap_proposal_info(const std::string& raw_proposal, wallet_public::ionic_swap_proposal_info& proposal_info) const +{ + wallet_public::ionic_swap_proposal proposal = AUTO_VAL_INIT(proposal); + bool r = t_unserializable_object_from_blob(proposal, raw_proposal); + THROW_IF_TRUE_WALLET_EX(!r, error::wallet_internal_error, "Failed to parse proposal"); + return get_ionic_swap_proposal_info(proposal, proposal_info); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::get_ionic_swap_proposal_info(const wallet_public::ionic_swap_proposal& proposal, wallet_public::ionic_swap_proposal_info& proposal_info) const +{ + wallet_public::ionic_swap_proposal_context ionic_context = AUTO_VAL_INIT(ionic_context); + return get_ionic_swap_proposal_info(proposal, proposal_info, ionic_context); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::get_ionic_swap_proposal_info(const wallet_public::ionic_swap_proposal& proposal, wallet_public::ionic_swap_proposal_info& proposal_info, wallet_public::ionic_swap_proposal_context& ionic_context) const +{ + const transaction& tx = proposal.tx_template; + crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); + std::vector outs; + bool r = lookup_acc_outs(m_account.get_keys(), tx, outs, derivation); + THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "Failed to lookup_acc_outs for tx: " << get_transaction_hash(tx)); + + if (!outs.size()) + { + return false; + } + + //decrypt context + std::string decrypted_raw_context = crypto::chacha_crypt(proposal.encrypted_context, derivation); + r = t_unserializable_object_from_blob(ionic_context, decrypted_raw_context); + THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "Failed to unserialize decrypted ionic_context"); + + r = validate_tx_details_against_tx_generation_context(tx, ionic_context.gen_context); + THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "validate_tx_details_against_tx_generation_context failed"); + + std::unordered_map amounts_provided_by_a; + + std::unordered_map ammounts_to_a; //amounts to Alice (the one who created proposal), should be NOT funded + std::unordered_map ammounts_to_b; //amounts to Bob (the one who received proposal), should BE funded + std::vector bob_outs; + bob_outs.resize(proposal.tx_template.vout.size()); + + for (const auto& o : outs) + { + THROW_IF_FALSE_WALLET_INT_ERR_EX(ionic_context.gen_context.asset_ids.size() > o.index, "Tx gen context has mismatch with tx(asset_ids) "); + THROW_IF_FALSE_WALLET_INT_ERR_EX(ionic_context.gen_context.asset_ids[o.index].to_public_key() == o.asset_id, "Tx gen context has mismatch with tx(asset_id != asset_id) "); + THROW_IF_FALSE_WALLET_INT_ERR_EX(ionic_context.gen_context.amounts[o.index].m_u64[0] == o.amount, "Tx gen context has mismatch with tx(amount != amount)"); + + ammounts_to_b[o.asset_id] += o.amount; + bob_outs[o.index] = true; + } + size_t i = 0; + //validate outputs against decrypted tx generation context + for (i = 0; i != tx.vout.size(); i++) + { + + if (bob_outs[i]) + { + continue; } - // populate ftp.selected_transfers from ftp.sources - ftp.selected_transfers.clear(); - for (size_t i = 0; i < ftp.sources.size(); ++i) - ftp.selected_transfers.push_back(ftp.sources[i].transfer_index); + crypto::public_key asset_id = ionic_context.gen_context.asset_ids[i].to_public_key(); + uint64_t amount = ionic_context.gen_context.amounts[i].m_u64[0]; + ammounts_to_a[asset_id] += amount; + } - outs_swept = ftp.sources.size(); - - - if (m_watch_only) + //read amounts already provided by third party + size_t zc_current_index = 0; //some inputs might be old ones, so it's asset id assumed as native and there is no entry for it in real_zc_ins_asset_ids + //THROW_IF_FALSE_WALLET_INT_ERR_EX(ionic_context.gen_context.input_amounts.size() == tx.vin.size(), "Tx gen context has mismatch with tx(amount != amount)"); + for (i = 0; i != tx.vin.size(); i++) + { + //size_t mx = 0; + uint64_t amount = 0; + crypto::public_key in_asset_id = currency::native_coin_asset_id; + if (tx.vin[i].type() == typeid(txin_zc_input)) { - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(p_filename_or_unsigned_tx_blob_str != nullptr, "p_filename_or_unsigned_tx_blob_str is null"); - bool r = store_unsigned_tx_to_file_and_reserve_transfers(ftp, *p_filename_or_unsigned_tx_blob_str, p_filename_or_unsigned_tx_blob_str); - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "failed to store unsigned tx"); - return; + in_asset_id = ionic_context.gen_context.real_zc_ins_asset_ids[zc_current_index].to_public_key(); + amount = ionic_context.gen_context.zc_input_amounts[zc_current_index]; + zc_current_index++; + //mx = boost::get(tx.vin[i]).key_offsets.size() - 1; } - - mark_transfers_as_spent(ftp.selected_transfers, "sweep_below"); - - transaction local_tx; - transaction* p_tx = p_result_tx != nullptr ? p_result_tx : &local_tx; - *p_tx = transaction{}; - try + else if (tx.vin[i].type() == typeid(txin_to_key)) { - crypto::secret_key sk{}; - finalize_transaction(ftp, *p_tx, sk, true); - print_tx_sent_message(*p_tx, "(sweep_below)", get_tx_fee(*p_tx)); + amount = boost::get(tx.vin[i]).amount; + //mx = boost::get(tx.vin[i]).key_offsets.size() - 1; } - catch (...) + else { - clear_transfers_from_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, std::string("exception on sweep_below, tx id (might be wrong): ") + epee::string_tools::pod_to_hex(get_transaction_hash(*p_tx))); - throw; + WLT_LOG_RED("Unexpected type of input in ionic_swap tx: " << tx.vin[i].type().name(), LOG_LEVEL_0); + return false; } + amounts_provided_by_a[in_asset_id] += amount; + //if (proposal_info.mixins == 0 || proposal_info.mixins > mx) + //{ + // proposal_info.mixins = mx; + //} } + //this might be 0, if Alice don't want to pay fee herself + proposal_info.fee_paid_by_a = currency::get_tx_fee(tx); + if (proposal_info.fee_paid_by_a) + { + THROW_IF_FALSE_WALLET_INT_ERR_EX(amounts_provided_by_a[currency::native_coin_asset_id] >= proposal_info.fee_paid_by_a, "Fee mentioned as specified but not provided by A"); + amounts_provided_by_a[currency::native_coin_asset_id] -= proposal_info.fee_paid_by_a; + } + + //proposal_info.fee = currency::get_tx_fee(tx); + //need to make sure that funds for Bob properly funded + for (const auto& a : ammounts_to_b) + { + uint64_t amount_sent_back_to_initiator = ammounts_to_a[a.first]; + + if (amounts_provided_by_a[a.first] < (a.second + amount_sent_back_to_initiator)) + { + WLT_LOG_RED("Amount[" << a.first << "] provided by Alice(" << amounts_provided_by_a[a.first] << ") is less then transfered to Bob(" << a.second << ")", LOG_LEVEL_0); + return false; + } + amounts_provided_by_a[a.first] -= (amount_sent_back_to_initiator + a.second); + proposal_info.to_finalizer.push_back(view::asset_funds{ a.first, a.second }); + //clean accounted assets + ammounts_to_a.erase(ammounts_to_a.find(a.first)); + if (amounts_provided_by_a[a.first] > 0) + { + WLT_LOG_RED("Amount[" << a.first << "] provided by Alice has unused leftovers: " << amounts_provided_by_a[a.first], LOG_LEVEL_0); + return false; + } + } + + //need to see what Alice actually expect in return + for (const auto& a : ammounts_to_a) + { + //now amount provided by A should be less or equal to what we have in a.second + if (amounts_provided_by_a[a.first] > a.second) + { + //could be fee + WLT_LOG_RED("Amount[" << a.first << "] provided by Alice has unused leftovers: " << amounts_provided_by_a[a.first], LOG_LEVEL_0); + return false; + } + + proposal_info.to_initiator.push_back(view::asset_funds{ a.first, a.second - amounts_provided_by_a[a.first] }); + } + + return true; +} + +//---------------------------------------------------------------------------------------------------- +bool wallet2::accept_ionic_swap_proposal(const std::string& raw_proposal, currency::transaction& result_tx) +{ + wallet_public::ionic_swap_proposal proposal = AUTO_VAL_INIT(proposal); + bool r = t_unserializable_object_from_blob(proposal, raw_proposal); + THROW_IF_TRUE_WALLET_EX(!r, error::wallet_internal_error, "Failed to parse proposal info"); + + return accept_ionic_swap_proposal(proposal, result_tx); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::accept_ionic_swap_proposal(const wallet_public::ionic_swap_proposal& proposal, currency::transaction& result_tx) +{ + mode_separate_context msc = AUTO_VAL_INIT(msc); + msc.tx_for_mode_separate = proposal.tx_template; + result_tx = msc.tx_for_mode_separate; + + wallet_public::ionic_swap_proposal_context ionic_context = AUTO_VAL_INIT(ionic_context); + bool r = get_ionic_swap_proposal_info(proposal, msc.proposal_info, ionic_context); + THROW_IF_TRUE_WALLET_EX(!r, error::wallet_internal_error, "Failed to get info from proposal"); + + std::unordered_map balances; + uint64_t mined = 0; + this->balance(balances, mined); + //validate balances needed + uint64_t native_amount_required = 0; + for (const auto& item : msc.proposal_info.to_initiator) + { + if (balances[item.asset_id].unlocked < item.amount) + { + THROW_IF_FALSE_WALLET_EX(false, error::not_enough_money, balances[item.asset_id].unlocked, item.amount, 0 /*fee*/, item.asset_id, get_asset_decimal_point(item.asset_id)); + } + if (item.asset_id == currency::native_coin_asset_id) + { + native_amount_required = item.amount; + } + } + + // balances is ok, check if fee is added to tx + uint64_t additional_fee = 0; + if (msc.proposal_info.fee_paid_by_a < m_core_runtime_config.tx_default_fee) + { + additional_fee = m_core_runtime_config.tx_default_fee - msc.proposal_info.fee_paid_by_a; + if (balances[currency::native_coin_asset_id].unlocked < additional_fee + native_amount_required) + { + THROW_IF_FALSE_WALLET_EX(false, error::not_enough_money, balances[currency::native_coin_asset_id].unlocked, native_amount_required, additional_fee, currency::native_coin_asset_id); + } + } + + //everything is seemed to be ok + + construct_tx_param construct_param = get_default_construct_tx_param(); + construct_param.fee = additional_fee; + + crypto::secret_key one_time_key = ionic_context.gen_context.tx_key.sec; // TODO: figure out this mess with tx sec key -- sowle + 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.need_at_least_1_zc = true; + + //build transaction + currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); + ftp.tx_version = this->get_current_tx_version(); + ftp.gen_context = ionic_context.gen_context; + prepare_transaction(construct_param, ftp, msc); + + + + try + { + finalize_transaction(ftp, result_tx, one_time_key, true); + } + catch (...) + { + clear_transfers_from_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, std::string("exception in finalize_transaction, tx: ") + epee::string_tools::pod_to_hex(get_transaction_hash(result_tx))); + throw; + } + mark_transfers_as_spent(ftp.selected_transfers, std::string("Proposal has been accepted with tx <" + epee::string_tools::pod_to_hex(get_transaction_hash(result_tx))) + ">"); + return true; +} +//---------------------------------------------------------------------------------------------------- + +// Signing and auth +bool wallet2::sign_buffer(const std::string& buff, crypto::signature& sig) +{ + crypto::hash h = crypto::cn_fast_hash(buff.data(), buff.size()); + crypto::generate_signature(h, m_account.get_public_address().spend_public_key, m_account.get_keys().spend_secret_key, sig); + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::validate_sign(const std::string& buff, const crypto::signature& sig, const crypto::public_key& pkey) +{ + crypto::hash h = crypto::cn_fast_hash(buff.data(), buff.size()); + return crypto::check_signature(h, pkey, sig); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::encrypt_buffer(const std::string& buff, std::string& res_buff) +{ + res_buff = buff; + crypto::chacha_crypt(res_buff, m_account.get_keys().view_secret_key); + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::decrypt_buffer(const std::string& buff, std::string& res_buff) +{ + res_buff = buff; + crypto::chacha_crypt(res_buff, m_account.get_keys().view_secret_key); + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::prepare_tx_sources_for_defragmentation_tx(std::vector& sources, std::vector& selected_indicies, uint64_t& found_money) +{ + if (!m_defragmentation_tx_enabled) + return false; + + std::stringstream ss; + if (epee::log_space::log_singletone::get_log_detalisation_level() >= LOG_LEVEL_2) + ss << "preparing sources for utxo defragmentation tx:"; + for (const auto& tr : m_transfers)//size_t i = 0, size = m_transfers .size(); i < size && selected_indicies.size() < m_max_utxo_count_for_defragmentation_tx; ++i) + { + if (selected_indicies.size() >= m_max_utxo_count_for_defragmentation_tx) + break; + uint64_t i = tr.first; + const auto& td = tr.second; + if (!td.is_native_coin() || td.m_amount > m_max_allowed_output_amount_for_defragmentation_tx) + continue; + + uint64_t fake_outs_count_for_td = m_decoys_count_for_defragmentation_tx == SIZE_MAX ? (td.is_zc() ? m_core_runtime_config.hf4_minimum_mixins : CURRENCY_DEFAULT_DECOY_SET_SIZE) : m_decoys_count_for_defragmentation_tx; + if (is_transfer_ready_to_go(td, fake_outs_count_for_td)) + { + found_money += td.m_amount; + selected_indicies.push_back(i); + if (epee::log_space::log_singletone::get_log_detalisation_level() >= LOG_LEVEL_2) + ss << " selected transfer #" << i << ", amount: " << print_money_brief(td.m_amount) << ", height: " << td.m_ptx_wallet_info->m_block_height << ", " << (td.is_zc() ? "ZC" : " "); + } + } + + if (selected_indicies.size() < m_min_utxo_count_for_defragmentation_tx || found_money <= TX_MINIMUM_FEE) + { + // too few outputs were found, hence don't create a defragmentation tx + selected_indicies.clear(); + found_money = 0; + return false; + } + + WLT_LOG(ss.str(), LOG_LEVEL_2); + + return prepare_tx_sources(m_decoys_count_for_defragmentation_tx == SIZE_MAX ? CURRENCY_DEFAULT_DECOY_SET_SIZE : m_decoys_count_for_defragmentation_tx, sources, selected_indicies); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::prepare_tx_sources(assets_selection_context& needed_money_map, size_t fake_outputs_count, uint64_t dust_threshold, std::vector& sources, std::vector& selected_indicies) +{ + try + { + select_transfers(needed_money_map, fake_outputs_count, dust_threshold, selected_indicies); // always returns true, TODO consider refactoring -- sowle + return prepare_tx_sources(fake_outputs_count, sources, selected_indicies); + } + catch (...) + { + // if smth went wrong -- invalidate transfers cache to trigger its regeneration on the next use + // it is necessary because it may be in invalid state (some items might be erased within select_indices_for_transfer() or expand_selection_with_zc_input()) + m_found_free_amounts.clear(); + throw; + } +} +//---------------------------------------------------------------------------------------------------- +void wallet2::prefetch_global_indicies_if_needed(const std::vector& selected_indicies) +{ + //std::list> txs; + //std::list indices_that_requested_global_indicies; + for (uint64_t i : selected_indicies) + { + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(m_transfers.at(i).m_global_output_index != WALLET_GLOBAL_OUTPUT_INDEX_UNDEFINED, + "m_transfers.at(" << i << ").m_global_output_index is WALLET_GLOBAL_OUTPUT_INDEX_UNDEFINED"); + //indices_that_requested_global_indicies.push_back(i); + //txs.push_back(m_transfers.at(i).m_ptx_wallet_info->m_tx); + //} + } + + /* + std::vector > outputs_for_all_txs; + fetch_tx_global_indixes(txs, outputs_for_all_txs); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(txs.size() == outputs_for_all_txs.size(), "missmatch sizes txs.size() == outputs_for_all_txs.size()"); + auto it_indices = indices_that_requested_global_indicies.begin(); + auto it_ooutputs = outputs_for_all_txs.begin(); + for (; it_ooutputs != outputs_for_all_txs.end();) + { + transfer_details& td = m_transfers.at(*it_indices); + td.m_global_output_index = (*it_ooutputs)[td.m_internal_output_index]; + it_ooutputs++; it_indices++; + }*/ +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::prepare_tx_sources(size_t fake_outputs_count, std::vector& sources, const std::vector& selected_indicies) +{ + return prepare_tx_sources(fake_outputs_count, false, sources, selected_indicies); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::prepare_tx_sources(size_t fake_outputs_count_, bool use_all_decoys_if_found_less_than_required, std::vector& sources, const std::vector& selected_indicies) +{ + typedef COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry out_entry; + typedef currency::tx_source_entry::output_entry tx_output_entry; + + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response daemon_resp = AUTO_VAL_INIT(daemon_resp); + //we should request even of fake_outputs_count == 0, since for for postzarcanum this era this param is redefined + //todo: remove if(true) block later if this code will be settled + if (true) + { + size_t fake_outputs_count = fake_outputs_count_; + uint64_t zarcanum_start_from = m_core_runtime_config.hard_forks.m_height_the_hardfork_n_active_after[ZANO_HARDFORK_04_ZARCANUM]; + uint64_t current_size = m_chain.get_blockchain_current_size(); + + bool need_to_request = fake_outputs_count != 0; + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS3::request req = AUTO_VAL_INIT(req); + req.height_upper_limit = m_last_pow_block_h; // request decoys to be either older than, or the same age as stake output's height + req.use_forced_mix_outs = false; // TODO: add this feature to UI later + //req.decoys_count = fake_outputs_count + 1; // one more to be able to skip a decoy in case it hits the real output + for (uint64_t i : selected_indicies) + { + req.amounts.push_back(COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS3::offsets_distribution()); + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS3::offsets_distribution& rdisttib = req.amounts.back(); + + auto it = m_transfers.find(i); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it != m_transfers.end(), + "internal error: index in m_tranfers " << i << " not found"); + + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it->second.m_ptx_wallet_info->m_tx.vout.size() > it->second.m_internal_output_index, + "m_internal_output_index = " << it->second.m_internal_output_index << + " is greater or equal to outputs count = " << it->second.m_ptx_wallet_info->m_tx.vout.size()); + + //rdisttib.own_global_index = it->m_global_output_index; + //check if we have Zarcanum era output of pre-Zarcanum + if (it->second.is_zc()) + { + if (this->is_auditable()) + continue; + //Zarcanum era + rdisttib.amount = 0; + //generate distribution in Zarcanum hardfork + build_distribution_for_input(rdisttib.global_offsets, it->second.m_global_output_index); + need_to_request = true; + } + else + { + //for prezarcanum era use flat distribution + rdisttib.amount = it->second.m_amount; + rdisttib.global_offsets.resize(fake_outputs_count + 1, 0); + } + } + if (need_to_request) + { + size_t attempt_count = 0; + while (true) + { + daemon_resp = COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response(); + bool r = m_core_proxy->call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS3(req, daemon_resp); + THROW_IF_FALSE_WALLET_EX(r, error::no_connection_to_daemon, "getrandom_outs3.bin"); + if (daemon_resp.status == API_RETURN_CODE_FAIL) + { + if (attempt_count < 10) + { + attempt_count++; + continue; + } + else + { + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(daemon_resp.outs.size() == selected_indicies.size(), + "unable to exacute getrandom_outs2.bin after 10 attempts with code API_RETURN_CODE_FAIL, there must be problems with mixins"); + } + } + THROW_IF_FALSE_WALLET_EX(daemon_resp.status != API_RETURN_CODE_BUSY, error::daemon_busy, "getrandom_outs.bin"); + THROW_IF_FALSE_WALLET_EX(daemon_resp.status == API_RETURN_CODE_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_outs2.bin, wrong amounts count = " << daemon_resp.outs.size() << ", expected: " << selected_indicies.size()); + break; + } + + std::vector scanty_outs; + THROW_IF_FALSE_WALLET_EX(daemon_resp.outs.size() == req.amounts.size(), error::not_enough_outs_to_mix, scanty_outs, fake_outputs_count); + + if (!use_all_decoys_if_found_less_than_required) + { + // make sure we have received the requested number of decoys + for (size_t i = 0; i != daemon_resp.outs.size(); i++) + if (req.amounts[i].amount != 0 && daemon_resp.outs[i].outs.size() != req.amounts[i].global_offsets.size()) + scanty_outs.push_back(daemon_resp.outs[i]); + THROW_IF_FALSE_WALLET_EX(scanty_outs.empty(), error::not_enough_outs_to_mix, scanty_outs, fake_outputs_count); + } + } + } + + //lets prefetch m_global_output_index for selected_indicies + //this days doesn't prefetch, only validated that prefetch is not needed + prefetch_global_indicies_if_needed(selected_indicies); + + //prepare inputs + size_t i = 0; + for (uint64_t J : selected_indicies) + { + auto it = m_transfers.find(J); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it != m_transfers.end(), "internal error: J " << J << " not found in m_transfers"); + + + sources.push_back(AUTO_VAL_INIT(currency::tx_source_entry())); + currency::tx_source_entry& src = sources.back(); + transfer_details& td = it->second; + src.transfer_index = J; + src.amount = td.amount(); + src.asset_id = td.get_asset_id(); + size_t fake_outputs_count = fake_outputs_count_; + //redefine for hardfork + if (td.is_zc() && !this->is_auditable()) + fake_outputs_count = m_core_runtime_config.hf4_minimum_mixins; + + + //paste mixin transaction + if (daemon_resp.outs.size()) + { + if (td.is_zc()) + { + //get rid of unneeded + select_decoys(daemon_resp.outs[i], td.m_global_output_index); + } + else + { + //TODO: make sure we have exact count needed + } + + daemon_resp.outs[i].outs.sort([](const out_entry& a, const out_entry& b) {return a.global_amount_index < b.global_amount_index; }); + for (out_entry& daemon_oe : daemon_resp.outs[i].outs) + { + if (td.m_global_output_index == daemon_oe.global_amount_index) + continue; + tx_output_entry oe = AUTO_VAL_INIT(oe); + oe.amount_commitment = daemon_oe.amount_commitment; + oe.concealing_point = daemon_oe.concealing_point; + oe.out_reference = daemon_oe.global_amount_index; + oe.stealth_address = daemon_oe.stealth_address; + oe.blinded_asset_id = daemon_oe.blinded_asset_id; // TODO @#@# BAD DESIGN, consider refactoring -- sowle + src.outputs.push_back(oe); + if (src.outputs.size() >= fake_outputs_count) + break; + } + } + + //paste real transaction to the random index + auto it_to_insert = std::find_if(src.outputs.begin(), src.outputs.end(), [&](const tx_output_entry& a) + { + if (a.out_reference.type().hash_code() == typeid(uint64_t).hash_code()) + return static_cast(boost::get(a.out_reference) >= td.m_global_output_index); + return false; // TODO: implement deterministics real output placement in case there're ref_by_id outs + }); + //size_t real_index = src.outputs.size() ? (rand() % src.outputs.size() ):0; + tx_output_entry real_oe = AUTO_VAL_INIT(real_oe); + real_oe.out_reference = td.m_global_output_index; // TODO: use ref_by_id when neccessary + VARIANT_SWITCH_BEGIN(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]); + VARIANT_CASE_CONST(tx_out_bare, o) + { + VARIANT_SWITCH_BEGIN(o.target); + VARIANT_CASE_CONST(txout_to_key, o) + real_oe.stealth_address = o.key; + VARIANT_CASE_CONST(txout_htlc, htlc) + real_oe.stealth_address = htlc.pkey_refund; + VARIANT_CASE_OTHER() + { + WLT_THROW_IF_FALSE_WITH_CODE(false, + "Internal error: unexpected type of target: " << o.target.type().name(), + API_RETURN_CODE_INTERNAL_ERROR); + } + VARIANT_SWITCH_END(); + } + VARIANT_CASE_CONST(tx_out_zarcanum, o); + real_oe.amount_commitment = o.amount_commitment; // TODO @#@# consider using shorter code like in sweep_below() (or better reuse it) + real_oe.concealing_point = o.concealing_point; + real_oe.stealth_address = o.stealth_address; + real_oe.blinded_asset_id = o.blinded_asset_id; + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.is_zc(), "transfer #" << J << ", amount: " << print_money_brief(td.amount()) << " is not a ZC"); + src.real_out_amount_blinding_mask = td.m_zc_info_ptr->amount_blinding_mask; + src.real_out_asset_id_blinding_mask = td.m_zc_info_ptr->asset_id_blinding_mask; + src.asset_id = td.m_zc_info_ptr->asset_id; +#ifndef NDEBUG + WLT_CHECK_AND_ASSERT_MES(crypto::point_t(src.asset_id) + src.real_out_asset_id_blinding_mask * crypto::c_point_X == crypto::point_t(real_oe.blinded_asset_id).modify_mul8(), false, "real_out_asset_id_blinding_mask doesn't match real_oe.blinded_asset_id"); + WLT_CHECK_AND_ASSERT_MES(td.m_amount * crypto::point_t(real_oe.blinded_asset_id).modify_mul8() + src.real_out_amount_blinding_mask * crypto::c_point_G == crypto::point_t(real_oe.amount_commitment).modify_mul8(), false, "real_out_amount_blinding_mask doesn't match real_oe.amount_commitment"); +#endif + VARIANT_SWITCH_END(); + + auto interted_it = src.outputs.insert(it_to_insert, real_oe); + src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_ptx_wallet_info->m_tx); + src.real_output = interted_it - src.outputs.begin(); + src.real_output_in_tx_index = td.m_internal_output_index; + + if (epee::log_space::get_set_log_detalisation_level() >= LOG_LEVEL_1) + { + std::stringstream ss; + ss << "source entry [" << i << "], td_idx: " << J << ", "; + print_source_entry(ss, src); + WLT_LOG_L1(ss.str()); + } + + ++i; + } + return true; +} + + +//---------------------------------------------------------------------------------------------------------------- +template +typename t_obj_container::value_type extract_random_from_container(t_obj_container& container) +{ + auto it = container.begin(); + std::advance(it, (crypto::rand() % container.size())); + typename t_obj_container::value_type obj = *it; + container.erase(it); + return obj; +} +//---------------------------------------------------------------------------------------------------------------- +void wallet2::select_decoys(currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& amount_entry, uint64_t own_g_index) +{ + THROW_IF_FALSE_WALLET_INT_ERR_EX(amount_entry.amount == 0, "Amount is not 0 in zc decoys entry"); + typedef currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry out_entry; + + //TODO: This strategy would be a subject for continuous refactoring + + //first take all real transactions if ther are some + std::list local_outs; + std::list coinbases; + + while (amount_entry.outs.size() && local_outs.size() != m_core_runtime_config.hf4_minimum_mixins) + { + out_entry entry = extract_random_from_container(amount_entry.outs); + + // + if (entry.global_amount_index == own_g_index) + { + continue; + } + + //skip auditable + if ((entry.flags & (RANDOM_OUTPUTS_FOR_AMOUNTS_FLAGS_NOT_ALLOWED))) + { + continue; + } + if (entry.flags & (RANDOM_OUTPUTS_FOR_AMOUNTS_FLAGS_COINBASE)) + { + coinbases.push_back(entry); + continue; + } + + + local_outs.push_back(entry); + } + + //extend with coin base outs if needed + while (coinbases.size() && local_outs.size() != m_core_runtime_config.hf4_minimum_mixins) + { + out_entry entry = extract_random_from_container(coinbases); + local_outs.push_back(entry); + } + + THROW_IF_FALSE_WALLET_INT_ERR_EX(local_outs.size() == m_core_runtime_config.hf4_minimum_mixins, "Amount is not 0 in zc decoys entry"); + amount_entry.outs = local_outs; +} +//---------------------------------------------------------------------------------------------------------------- +void wallet2::build_distribution_for_input(std::vector& offsets, uint64_t own_index) +{ + decoy_selection_generator zarcanum_decoy_set_generator; + zarcanum_decoy_set_generator.init(get_actual_zc_global_index()); + + THROW_IF_FALSE_WALLET_INT_ERR_EX(zarcanum_decoy_set_generator.is_initialized(), "zarcanum_decoy_set_generator are not initialized"); + if (m_core_runtime_config.hf4_minimum_mixins) + { + uint64_t actual_zc_index = get_actual_zc_global_index(); + offsets = zarcanum_decoy_set_generator.generate_unique_reversed_distribution(actual_zc_index - 1 > WALLET_FETCH_RANDOM_OUTS_SIZE ? WALLET_FETCH_RANDOM_OUTS_SIZE : actual_zc_index - 1, own_index); + } +} +//---------------------------------------------------------------------------------------------------------------- +bool wallet2::prepare_tx_sources(crypto::hash multisig_id, std::vector& sources, uint64_t& found_money) +{ + 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: " + epee::string_tools::pod_to_hex(multisig_id)); + THROW_IF_FALSE_WALLET_INT_ERR_EX(!it->second.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)); + + 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()"); + //@#@ + THROW_IF_FALSE_WALLET_INT_ERR_EX(it->second.m_ptx_wallet_info->m_tx.vout[it->second.m_internal_output_index].type() == typeid(tx_out_bare), "Unknown type id in prepare_tx_sources: " << it->second.m_ptx_wallet_info->m_tx.vout[it->second.m_internal_output_index].type().name()); + const tx_out_bare& out = boost::get(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(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); + src.multisig_id = multisig_id; + src.ms_sigs_count = ms_out.minimum_sigs; + src.ms_keys_count = ms_out.keys.size(); + return true; +} +//---------------------------------------------------------------------------------------------------------------- +bool wallet2::prepare_tx_sources_htlc(crypto::hash htlc_tx_id, const std::string& origin, std::vector& sources, uint64_t& found_money) +{ + typedef currency::tx_source_entry::output_entry tx_output_entry; + //lets figure out, if we have active htlc for this htlc + auto it = m_active_htlcs_txid.find(htlc_tx_id); + if (it == m_active_htlcs_txid.end()) + { + WLT_THROW_IF_FALSE_WITH_CODE(false, + "htlc not found with tx_id = " << htlc_tx_id, API_RETURN_CODE_NOT_FOUND); + } + + //WLT_THROW_IF_FALSE_WITH_CODE(m_transfers.size() > it->second, + // "Internal error: index in m_active_htlcs_txid <" << it->second << "> is bigger then size of m_transfers <" << m_transfers.size() << ">", API_RETURN_CODE_INTERNAL_ERROR); + + const transfer_details& td = m_transfers.at(it->second); + //@#@ + WLT_THROW_IF_FALSE_WITH_CODE(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].type() == typeid(tx_out_bare), + "Unexpected out type in prepare_tx_sources_htlc:" << td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].type().name(), API_RETURN_CODE_INTERNAL_ERROR); + + const tx_out_bare& out_bare = boost::get(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]); + WLT_THROW_IF_FALSE_WITH_CODE(out_bare.target.type() == typeid(txout_htlc), + "Unexpected type in active htlc", API_RETURN_CODE_INTERNAL_ERROR); + + const txout_htlc& htlc_out = boost::get(out_bare.target); + bool use_sha256 = !(htlc_out.flags & CURRENCY_TXOUT_HTLC_FLAGS_HASH_TYPE_MASK); + + //check origin + WLT_THROW_IF_FALSE_WITH_CODE(origin.size() != 0, + "Origin for htlc is empty", API_RETURN_CODE_BAD_ARG); + + crypto::hash htlc_calculated_hash = currency::null_hash; + if (use_sha256) + { + htlc_calculated_hash = crypto::sha256_hash(origin.data(), origin.size()); + } + else + { + htlc_calculated_hash = crypto::RIPEMD160_hash_256(origin.data(), origin.size()); + } + WLT_THROW_IF_FALSE_WITH_CODE(htlc_calculated_hash == htlc_out.htlc_hash, + "Origin hash is missmatched with txout_htlc", API_RETURN_CODE_HTLC_ORIGIN_HASH_MISSMATCHED); + + sources.push_back(AUTO_VAL_INIT(currency::tx_source_entry())); + currency::tx_source_entry& src = sources.back(); + tx_output_entry real_oe = AUTO_VAL_INIT(real_oe); + real_oe.out_reference = td.m_global_output_index; // TODO: use ref_by_id when necessary + real_oe.stealth_address = htlc_out.pkey_redeem; + src.outputs.push_back(real_oe); //m_global_output_index should be prefetched + src.amount = found_money = td.amount(); + src.real_output_in_tx_index = td.m_internal_output_index; + src.real_output = 0;//no mixins supposed to be in htlc + src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_ptx_wallet_info->m_tx); + src.htlc_origin = origin; + return true; + +} +//---------------------------------------------------------------------------------------------------------------- +assets_selection_context wallet2::get_needed_money(uint64_t fee, const std::vector& dsts) +{ + assets_selection_context amounts_map; + amounts_map[currency::native_coin_asset_id].needed_amount = fee; + for (auto& dt : dsts) + { + if (dt.asset_id == currency::null_pkey) + continue; //this destination for emmition only + + THROW_IF_TRUE_WALLET_EX(0 == dt.amount, error::zero_destination); + uint64_t money_to_add = dt.amount; + if (dt.amount_to_provide || dt.flags & tx_destination_entry_flags::tdef_explicit_amount_to_provide) + money_to_add = dt.amount_to_provide; + + amounts_map[dt.asset_id].needed_amount += money_to_add; + THROW_IF_TRUE_WALLET_EX(amounts_map[dt.asset_id].needed_amount < money_to_add, error::tx_sum_overflow, dsts, fee); + //clean up empty entries + if (amounts_map[dt.asset_id].needed_amount == 0) + { + amounts_map.erase(amounts_map.find(dt.asset_id)); + } + } + return amounts_map; +} +//---------------------------------------------------------------------------------------------------------------- +void wallet2::set_disable_tor_relay(bool disable) +{ + m_disable_tor_relay = disable; +} +//---------------------------------------------------------------------------------------------------------------- +void wallet2::notify_state_change(const std::string& state_code, const std::string& details) +{ + m_wcallback->on_tor_status_change(state_code); +} +//---------------------------------------------------------------------------------------------------------------- +void wallet2::send_transaction_to_network(const transaction& tx) +{ +#ifndef DISABLE_TOR + if (!m_disable_tor_relay) + { + //TODO check that core synchronized + //epee::net_utils::levin_client2 p2p_client; + + //make few attempts + tools::levin_over_tor_client p2p_client; + p2p_client.get_transport().set_notifier(this); + bool succeseful_sent = false; + for (size_t i = 0; i != 3; i++) + { + if (!p2p_client.connect("144.76.183.143", 2121, 10000)) + { + continue;//THROW_IF_FALSE_WALLET_EX(false, error::no_connection_to_daemon, "Failed to connect to TOR node"); + } + + + currency::NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request p2p_req = AUTO_VAL_INIT(p2p_req); + currency::NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::response p2p_rsp = AUTO_VAL_INIT(p2p_rsp); + p2p_req.txs.push_back(t_serializable_object_to_blob(tx)); + this->notify_state_change(WALLET_LIB_STATE_SENDING); + epee::net_utils::invoke_remote_command2(NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::ID, p2p_req, p2p_rsp, p2p_client); + p2p_client.disconnect(); + if (p2p_rsp.code == API_RETURN_CODE_OK) + { + this->notify_state_change(WALLET_LIB_SENT_SUCCESS); + succeseful_sent = true; + break; + } + this->notify_state_change(WALLET_LIB_SEND_FAILED); + //checking if transaction got relayed to other nodes and + //return; + } + if (!succeseful_sent) + { + this->notify_state_change(WALLET_LIB_SEND_FAILED); + THROW_IF_FALSE_WALLET_EX(succeseful_sent, error::no_connection_to_daemon, "Faile to build TOR stream"); + } + } + else +#endif // + { + COMMAND_RPC_SEND_RAW_TX::request req; + req.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(tx)); + COMMAND_RPC_SEND_RAW_TX::response daemon_send_resp; + bool r = m_core_proxy->call_COMMAND_RPC_SEND_RAW_TX(req, daemon_send_resp); + THROW_IF_TRUE_WALLET_EX(!r, error::no_connection_to_daemon, "sendrawtransaction"); + THROW_IF_TRUE_WALLET_EX(daemon_send_resp.status == API_RETURN_CODE_BUSY, error::daemon_busy, "sendrawtransaction"); + THROW_IF_TRUE_WALLET_EX(daemon_send_resp.status == API_RETURN_CODE_DISCONNECTED, error::no_connection_to_daemon, "Transfer attempt while daemon offline"); + THROW_IF_TRUE_WALLET_EX(daemon_send_resp.status != API_RETURN_CODE_OK, error::tx_rejected, tx, daemon_send_resp.status); + + WLT_LOG_L2("transaction " << get_transaction_hash(tx) << " generated ok and sent to daemon:" << ENDL << currency::obj_to_json_str(tx)); + } + +} +//---------------------------------------------------------------------------------------------------------------- +void wallet2::add_sent_tx_detailed_info(const transaction& tx, const std::vector& decrypted_att, + const std::vector& destinations, + const std::vector& selected_transfers) +{ + payment_id_t payment_id; + get_payment_id_from_decrypted_container(decrypted_att, payment_id); + + std::vector recipients; + std::unordered_set used_addresses; + for (const auto& d : destinations) + { + for (const auto& addr : d.addr) + { + if (used_addresses.insert(addr).second && addr != m_account.get_public_address()) + recipients.push_back(payment_id.empty() ? get_account_address_as_str(addr) : get_account_address_and_payment_id_as_str(addr, payment_id)); + } + } + if (!recipients.size()) + { + //transaction send to ourself + recipients.push_back(payment_id.empty() ? get_account_address_as_str(m_account.get_public_address()) : get_account_address_and_payment_id_as_str(m_account.get_public_address(), payment_id)); + } + + add_sent_unconfirmed_tx(tx, recipients, selected_transfers, destinations); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::mark_transfers_with_flag(const std::vector& selected_transfers, uint32_t flag, const std::string& reason /* = empty_string */, bool throw_if_flag_already_set /* = false */) +{ + // check all selected transfers prior to flag change + for (uint64_t i : selected_transfers) + { + //WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(i < m_transfers.size(), "invalid transfer index given: " << i << ", m_transfers.size() == " << m_transfers.size()); + if (throw_if_flag_already_set) + { + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX((m_transfers.at(i).m_flags & flag) == 0, "transfer #" << i << " already has flag " << flag << ": " << m_transfers.at(i).m_flags << ", transfer info:" << ENDL << epee::serialization::store_t_to_json(m_transfers.at(i))); + } + } + + for (uint64_t i : selected_transfers) + { + uint32_t flags_before = m_transfers.at(i).m_flags; + m_transfers.at(i).m_flags |= flag; + WLT_LOG_L1("marking transfer #" << std::setfill('0') << std::right << std::setw(3) << i << " with flag " << flag << " : " << flags_before << " -> " << m_transfers.at(i).m_flags << + (reason.empty() ? "" : ", reason: ") << reason); + } +} +//---------------------------------------------------------------------------------------------------- +void wallet2::clear_transfers_from_flag(const std::vector& selected_transfers, uint32_t flag, const std::string& reason /* = empty_string */) noexcept +{ + TRY_ENTRY(); + for (uint64_t i : selected_transfers) + { + //if (i >= m_transfers.size()) + //{ + // WLT_LOG_ERROR("INTERNAL ERROR: i: " << i << " >= m_transfers.size() : " << m_transfers.size()); + // continue; + //} + auto& tr_entry = m_transfers.at(i); + uint32_t flags_before = tr_entry.m_flags; + tr_entry.m_flags &= ~flag; + WLT_LOG_L1("clearing transfer #" << std::setfill('0') << std::right << std::setw(3) << i << " from flag " << flag << " : " << flags_before << " -> " << tr_entry.m_flags << + (reason.empty() ? "" : ", reason: ") << reason); + } + CATCH_ENTRY_NO_RETURN(); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::exception_handler() +{ + m_found_free_amounts.clear(); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::exception_handler() const +{ + // do nothing + // TODO: is it correct? +} +//---------------------------------------------------------------------------------------------------- +void wallet2::mark_transfers_as_spent(const std::vector& selected_transfers, const std::string& reason /* = empty_string */) +{ + // TODO: design a safe undo for this operation + mark_transfers_with_flag(selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, reason); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::extract_offers_from_transfer_entry(size_t i, std::unordered_map& offers_local) +{ + //TODO: this code supports only one market(offer) instruction per transaction + load_wallet_transfer_info_flags(m_transfer_history[i]); + switch (m_transfer_history[i].tx_type) + { + case GUI_TX_TYPE_PUSH_OFFER: + { + bc_services::offer_details od; + if (!get_type_in_variant_container(m_transfer_history[i].marketplace_entries, od)) + { + WLT_LOG_ERROR("Transaction history entry " << i << " market as type " << m_transfer_history[i].tx_type << " but get_type_in_variant_container returned false for bc_services::offer_details"); + break; + } + crypto::hash h = null_hash; + h = m_transfer_history[i].tx_hash; + bc_services::offer_details_ex& ode = offers_local[h]; + ode = AUTO_VAL_INIT(bc_services::offer_details_ex()); + static_cast(ode) = od; + //fill extra fields + ode.tx_hash = m_transfer_history[i].tx_hash; + ode.index_in_tx = 0; // TODO: handle multiple offers in tx, now only one per tx is supported + ode.timestamp = m_transfer_history[i].timestamp; + ode.fee = m_transfer_history[i].fee; + ode.stopped = false; + break; + } + case GUI_TX_TYPE_UPDATE_OFFER: + { + bc_services::update_offer uo; + if (!get_type_in_variant_container(m_transfer_history[i].marketplace_entries, uo)) + { + WLT_LOG_ERROR("Transaction history entry " << i << " market as type " << m_transfer_history[i].tx_type << " but get_type_in_variant_container returned false for update_offer"); + break; + } + crypto::hash h = null_hash; + h = m_transfer_history[i].tx_hash; + bc_services::offer_details_ex& ode = offers_local[h]; + ode = AUTO_VAL_INIT(bc_services::offer_details_ex()); + static_cast(ode) = uo.of; + //fill extra fields + ode.tx_hash = m_transfer_history[i].tx_hash; + ode.index_in_tx = 0; + ode.fee = m_transfer_history[i].fee; + ode.stopped = false; + ode.tx_original_hash = uo.tx_id; + //remove old transaction + crypto::hash h_old = uo.tx_id; + auto it = offers_local.find(h_old); + if (it == offers_local.end()) + { + WLT_LOG_L3("Unable to find original tx record " << h_old << " in update offer " << h); + break; + } + //keep original timestamp + ode.timestamp = it->second.timestamp; + offers_local.erase(it); + break; + } + case GUI_TX_TYPE_CANCEL_OFFER: + { + bc_services::cancel_offer co; + if (!get_type_in_variant_container(m_transfer_history[i].marketplace_entries, co)) + { + WLT_LOG_ERROR("Transaction history entry " << i << " market as type " << m_transfer_history[i].tx_type << " but get_type_in_variant_container returned false for cancel_offer"); + break; + } + crypto::hash h = co.tx_id; + auto it = offers_local.find(h); + if (it == offers_local.end()) + { + WLT_LOG_L3("Unable to find original tx record " << h << " in cancel offer " << h); + break; + } + offers_local.erase(it); + + } + default: + ; + } + + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::select_my_offers(std::list& offers) +{ + std::unordered_map offers_local; + + if (!m_transfer_history.size()) + return true; + + uint64_t stop_timestamp = m_core_runtime_config.get_core_time() - OFFER_MAXIMUM_LIFE_TIME; + + size_t i = m_transfer_history.size() - 1; + for (; i != 0; i--) + { + if (m_transfer_history[i].timestamp < stop_timestamp) + { + i++; + break; + } + } + if (i == 0 && m_transfer_history[0].timestamp < stop_timestamp) + i++; + if (i >= m_transfer_history.size()) + return true; + + for (; i != m_transfer_history.size(); i++) + { + extract_offers_from_transfer_entry(i, offers_local); + } + for (const auto& o : offers_local) + offers.push_back(o.second); + + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::get_actual_offers(std::list& offers) +{ + select_my_offers(offers); + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::expand_selection_with_zc_input(assets_selection_context& needed_money_map, uint64_t fake_outputs_count, std::vector& selected_indexes) +{ + free_amounts_cache_type& found_free_amounts = m_found_free_amounts[currency::native_coin_asset_id]; + auto& asset_needed_money_item = needed_money_map[currency::native_coin_asset_id]; + //need to add ZC input + for (auto it = found_free_amounts.begin(); it != found_free_amounts.end(); it++) + { + for (auto it_in_amount = it->second.begin(); it_in_amount != it->second.end(); it_in_amount++) + { + if (!m_transfers.at(*it_in_amount).is_zc()) + { + continue; + } + + if (is_transfer_ready_to_go(m_transfers.at(*it->second.begin()), fake_outputs_count)) + { + asset_needed_money_item.found_amount += it->first; + selected_indexes.push_back(*it_in_amount); + it->second.erase(it_in_amount); + if (!it->second.size()) + { + found_free_amounts.erase(it); + } + return true; + } + } + } + WLT_THROW_IF_FALSE_WALLET_EX_MES(false, error::no_zc_inputs, "At least one ZC is required for the operation, but none were found"); + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::select_indices_for_transfer(assets_selection_context& needed_money_map, uint64_t fake_outputs_count, std::vector& selected_indexes) +{ + for (auto& item : needed_money_map) + { + if (item.second.needed_amount == 0) + continue; + + const crypto::public_key asset_id = item.first; + asset_descriptor_base asset_info{}; + uint32_t asset_flags = 0; + if (!get_asset_info(asset_id, asset_info, asset_flags)) + WLT_LOG_L1("select_indices_for_transfer: unknown asset id: " << asset_id); + + auto asset_cache_it = m_found_free_amounts.find(asset_id); + THROW_IF_FALSE_WALLET_EX(asset_cache_it != m_found_free_amounts.end(), error::not_enough_money, item.second.found_amount, item.second.needed_amount, (uint64_t)0, asset_id, asset_info.decimal_point); + item.second.found_amount = select_indices_for_transfer(selected_indexes, asset_cache_it->second, item.second.needed_amount, fake_outputs_count, asset_id, asset_info.decimal_point); + THROW_IF_FALSE_WALLET_EX(item.second.found_amount >= item.second.needed_amount, error::not_enough_money, item.second.found_amount, item.second.needed_amount, (uint64_t)0, asset_id, asset_info.decimal_point); + } + if (m_current_context.pconstruct_tx_param && m_current_context.pconstruct_tx_param->need_at_least_1_zc) + { + bool found_zc_input = false; + for (auto i : selected_indexes) + { + if (m_transfers.at(i).is_zc()) + { + found_zc_input = true; + break; + } + } + if (!found_zc_input) + { + expand_selection_with_zc_input(needed_money_map, fake_outputs_count, selected_indexes); + } + } + + return true; +} +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::select_indices_for_transfer(std::vector& selected_indexes, free_amounts_cache_type& found_free_amounts, uint64_t needed_money, uint64_t fake_outputs_count_, + const crypto::public_key& asset_id, size_t decimal_point) +{ + WLT_LOG_GREEN("Selecting indices for transfer of " << print_money_brief(needed_money, decimal_point) << " with " << fake_outputs_count_ << " fake outs, found_free_amounts.size()=" << found_free_amounts.size() << + (asset_id == native_coin_asset_id ? std::string() : std::string(", asset_id: ") + crypto::pod_to_hex(asset_id)) << "...", LOG_LEVEL_0); + uint64_t found_money = 0; + size_t outputs_found = 0; + std::string selected_amounts_str; + while (found_money < needed_money && found_free_amounts.size()) + { + auto it = found_free_amounts.lower_bound(needed_money - found_money); + if (!(it != found_free_amounts.end() && it->second.size())) + { + it = --found_free_amounts.end(); + WLT_CHECK_AND_ASSERT_MES(it->second.size(), 0, "internal error: empty found_free_amounts map"); + } + uint64_t fake_outputs_count = fake_outputs_count_; + if (!this->is_auditable() && m_transfers.at(*it->second.begin()).is_zc()) + { + fake_outputs_count = m_core_runtime_config.hf4_minimum_mixins; + } + if (is_transfer_ready_to_go(m_transfers.at(*it->second.begin()), fake_outputs_count)) + { + found_money += it->first; + selected_indexes.push_back(*it->second.begin()); + WLT_LOG_L2("Selected index: " << *it->second.begin() << ", transfer_details: " << ENDL << epee::serialization::store_t_to_json(m_transfers.at(*it->second.begin()))); + selected_amounts_str += (selected_amounts_str.empty() ? "" : "+") + print_money_brief(it->first, decimal_point); + ++outputs_found; + } + it->second.erase(it->second.begin()); + if (!it->second.size()) + found_free_amounts.erase(it); + + } + + WLT_LOG_GREEN("Found " << print_money_brief(found_money, decimal_point) << " as " << outputs_found << " out(s): " << selected_amounts_str << ", found_free_amounts.size()=" << found_free_amounts.size() << + (asset_id == native_coin_asset_id ? std::string() : std::string(", asset_id: ") + crypto::pod_to_hex(asset_id)), LOG_LEVEL_0); + return found_money; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::is_transfer_ready_to_go(const transfer_details& td, uint64_t fake_outputs_count) const +{ + if (is_transfer_able_to_go(td, fake_outputs_count) && is_transfer_unlocked(td)) + { + return true; + } + return false; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::is_transfer_able_to_go(const transfer_details& td, uint64_t fake_outputs_count) const +{ + if (!td.is_spendable()) + return false; + + const tx_out_v& out_v = td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]; + + uint8_t mix_attr = CURRENCY_TO_KEY_OUT_RELAXED; + if (get_mix_attr_from_tx_out_v(out_v, mix_attr)) + { + if (!currency::is_mixattr_applicable_for_fake_outs_counter(td.m_ptx_wallet_info->m_tx.version, mix_attr, fake_outputs_count, m_core_runtime_config)) + return false; + } + + VARIANT_SWITCH_BEGIN(out_v); + VARIANT_CASE_CONST(tx_out_bare, o); + if (o.target.type() == typeid(txout_htlc)) + { + if (fake_outputs_count != 0) + return false; + } + VARIANT_SWITCH_END(); + + return true; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::prepare_free_transfers_cache(uint64_t fake_outputs_count) +{ + WLT_LOG_L2("Preparing transfers_cache..."); + uint64_t count = 0; + if (!m_found_free_amounts.size() || fake_outputs_count != m_fake_outputs_count) + { + m_found_free_amounts.clear(); + for (const auto& tr : m_transfers) + { + uint64_t i = tr.first; + const transfer_details& td = tr.second; + uint64_t fake_outputs_count_local = fake_outputs_count; + if (td.m_zc_info_ptr) + { + //zarcanum out, redefine fake_outputs_count + fake_outputs_count_local = this->is_auditable() ? 0 : m_core_runtime_config.hf4_minimum_mixins; + } + if (is_transfer_able_to_go(td, fake_outputs_count_local)) + { + //@#@ + m_found_free_amounts[td.get_asset_id()][td.amount()].insert(i); + count++; + } + } + m_fake_outputs_count = fake_outputs_count; + } + + WLT_LOG_L2("Transfers_cache prepared. " << count << " items cached for " << m_found_free_amounts.size() << " amounts"); + return true; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::add_transfers_to_transfers_cache(const std::vector& indexs) +{ + //@#@ + for (auto i : indexs) + add_transfer_to_transfers_cache(m_transfers.at(i).amount(), i, m_transfers.at(i).get_asset_id()); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::add_transfer_to_transfers_cache(uint64_t amount, uint64_t index, const crypto::public_key& asset_id /* = currency::native_coin_asset_id */) +{ + m_found_free_amounts[asset_id][amount].insert(index); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::select_transfers(assets_selection_context& needed_money_map, size_t fake_outputs_count, uint64_t /*dust_threshold*/, std::vector& selected_indicies) +{ + prepare_free_transfers_cache(fake_outputs_count); + return select_indices_for_transfer(needed_money_map, fake_outputs_count, selected_indicies); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::add_sent_unconfirmed_tx(const currency::transaction& tx, + const std::vector& recipients, + const std::vector& selected_indicies, + const std::vector& splitted_dsts) +{ + PROFILE_FUNC("wallet2::add_sent_unconfirmed_tx"); + process_transaction_context ptc(tx); + ptc.recipients = recipients; + for (auto addr : recipients) + ptc.remote_aliases.push_back(get_alias_for_address(addr)); + + handle_unconfirmed_tx(ptc); + wallet_public::wallet_transfer_info& unconfirmed_wti = misc_utils::get_or_insert_value_initialized(m_unconfirmed_txs, currency::get_transaction_hash(tx)); + //override some info that might be missing + unconfirmed_wti.selected_indicies = selected_indicies; +} +//---------------------------------------------------------------------------------------------------- +std::string wallet2::get_alias_for_address(const std::string& addr) +{ + std::vector aliases = get_aliases_for_address(addr); + if (aliases.size()) + return aliases.front(); + return ""; +} +//---------------------------------------------------------------------------------------------------- +std::vector wallet2::get_aliases_for_address(const std::string& addr) +{ + PROFILE_FUNC("wallet2::get_alias_for_address"); + currency::COMMAND_RPC_GET_ALIASES_BY_ADDRESS::request req = addr; + currency::COMMAND_RPC_GET_ALIASES_BY_ADDRESS::response res = AUTO_VAL_INIT(res); + std::vector aliases; + if (!m_core_proxy->call_COMMAND_RPC_GET_ALIASES_BY_ADDRESS(req, res)) + { + WLT_LOG_L0("Failed to COMMAND_RPC_GET_ALIASES_BY_ADDRESS"); + return aliases; + } + for (auto& e : res.alias_info_list) + { + aliases.push_back(e.alias); + } + return aliases; +} +//---------------------------------------------------------------------------------------------------- +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, + currency::transaction& tx) +{ + transfer(dsts, fake_outputs_count, unlock_time, fee, extra, attachments, get_current_split_strategy(), 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) +{ + currency::transaction tx; + transfer(dsts, fake_outputs_count, unlock_time, fee, extra, attachments, tx); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::is_connected_to_net() +{ + currency::COMMAND_RPC_GET_INFO::request req = AUTO_VAL_INIT(req); + currency::COMMAND_RPC_GET_INFO::response res = AUTO_VAL_INIT(res); + if (!m_core_proxy->call_COMMAND_RPC_GET_INFO(req, res)) + { + WLT_LOG_L0("Failed to COMMAND_RPC_GET_INFO"); + return false; + } + return (res.synchronized_connections_count) ? true : false; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::process_genesis_if_needed(const currency::block& genesis, const std::vector* pglobal_indexes) +{ + if (!m_transfers.empty() || !m_key_images.empty()) + return; + + THROW_IF_TRUE_WALLET_EX(get_blockchain_current_size() > 1, error::wallet_internal_error, "Can't change wallet genesis block once the blockchain has been populated"); + + crypto::hash genesis_hash = get_block_hash(genesis); + if (get_blockchain_current_size() == 1 && m_chain.get_genesis() != genesis_hash) + WLT_LOG_L0("Changing genesis block for wallet " << m_account.get_public_address_str() << ":" << ENDL << " " << m_chain.get_genesis() << " -> " << genesis_hash); + + //m_blockchain.clear(); + + //m_blockchain.push_back(genesis_hash); + m_chain.set_genesis(genesis_hash); + m_last_bc_timestamp = genesis.timestamp; + + WLT_LOG_L2("Processing genesis block: " << genesis_hash); + process_new_transaction(genesis.miner_tx, 0, genesis, pglobal_indexes); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::set_genesis(const crypto::hash& genesis_hash) +{ + THROW_IF_TRUE_WALLET_EX(get_blockchain_current_size() != 1, error::wallet_internal_error, "Can't change wallet genesis hash once the blockchain has been populated"); + WLT_LOG_L0("Changing genesis hash for wallet " << m_account.get_public_address_str() << ":" << ENDL << " " << m_chain.get_genesis() << " -> " << genesis_hash); + m_chain.set_genesis(genesis_hash); +} +//---------------------------------------------------------------------------------------------------- +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 + << 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.", + LOG_LEVEL_0); +} +//---------------------------------------------------------------------------------------------------- +uint64_t wallet2::get_tx_expiration_median() const +{ + currency::COMMAND_RPC_GET_CURRENT_CORE_TX_EXPIRATION_MEDIAN::request req = AUTO_VAL_INIT(req); + currency::COMMAND_RPC_GET_CURRENT_CORE_TX_EXPIRATION_MEDIAN::response res = AUTO_VAL_INIT(res); + m_core_proxy->call_COMMAND_RPC_GET_CURRENT_CORE_TX_EXPIRATION_MEDIAN(req, res); + + if (res.status != API_RETURN_CODE_OK) + { + WLT_LOG_ERROR("COMMAND_RPC_GET_CURRENT_CORE_TX_EXPIRATION_MEDIAN failed, status: " << res.status); + return 0; + } + + return res.expiration_median; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::print_source_entry(std::stringstream& output, const currency::tx_source_entry& src) const +{ + std::stringstream ss; + for (auto& el : src.outputs) + ss << el.out_reference << " "; + + output << "amount: " << print_money_brief(src.amount, get_asset_decimal_point(src.asset_id)) << (src.is_zc() ? "" : " (bare)"); + + if (src.asset_id != currency::native_coin_asset_id) + output << " (" << print16(src.asset_id) << ")"; + + output << ", real_output: " << src.real_output + << ", real_output_in_tx_index: " << src.real_output_in_tx_index + << ", indexes: " << ss.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; +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::is_need_to_split_outputs() +{ + return !is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::prepare_tx_destinations(const assets_selection_context& needed_money_map, + detail::split_strategy_id_t destination_split_strategy_id, + const tx_dust_policy& dust_policy, + const std::vector& dsts, + uint8_t tx_flags, + std::vector& final_destinations) +{ + + /* + let's account all processes assets, so if there are some destinations + that haven't been present in needed_money_map we can add it to final destinations + (could be in ionic swaps for example) + */ + std::unordered_set processed_assets; + for (auto& el : needed_money_map) + { + prepare_tx_destinations(el.second.needed_amount, el.second.found_amount, destination_split_strategy_id, dust_policy, dsts, el.first, final_destinations); + processed_assets.insert(el.first); + } + + if (is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM)) + { + // special case for asset minting destinations + for (auto& dst : dsts) + if (dst.asset_id == currency::null_pkey || processed_assets.count(dst.asset_id) == 0) + final_destinations.emplace_back(dst.amount, dst.addr, dst.asset_id); + + //exclude destinations that supposed to be burned (for ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN) + for (size_t i = 0; i < final_destinations.size(); ) + { + if (final_destinations[i].addr.size() == 0) + { + final_destinations.erase(final_destinations.begin() + i); + } + else + { + i++; + } + } + + if (!(tx_flags & TX_FLAG_SIGNATURE_MODE_SEPARATE)) + { + if (final_destinations.empty()) + { + // if there's no destinations -- make CURRENCY_TX_MIN_ALLOWED_OUTS empty destinations + for (size_t i = 0; i < CURRENCY_TX_MIN_ALLOWED_OUTS; ++i) + final_destinations.emplace_back(0, m_account.get_public_address()); + } + else if (final_destinations.size() < CURRENCY_TX_MIN_ALLOWED_OUTS) + { + // if there's not ehough destinations items (i.e. outputs), split the last one + tx_destination_entry de = final_destinations.back(); + final_destinations.pop_back(); + size_t items_to_be_added = CURRENCY_TX_MIN_ALLOWED_OUTS - final_destinations.size(); + // TODO: consider allowing to set them somewhere + size_t num_digits_to_keep = CURRENCY_TX_OUTS_RND_SPLIT_DIGITS_TO_KEEP; + decompose_amount_randomly(de.amount, [&](uint64_t amount) { de.amount = amount; final_destinations.push_back(de); }, items_to_be_added, num_digits_to_keep); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(final_destinations.size() == CURRENCY_TX_MIN_ALLOWED_OUTS, + "can't get necessary number of outputs using decompose_amount_randomly(), got " << final_destinations.size() << " while mininum is " << CURRENCY_TX_MIN_ALLOWED_OUTS); + } + } + } +} +//---------------------------------------------------------------------------------------------------- +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, + const crypto::public_key& asset_id, + std::vector& final_destinations) +{ + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(found_money >= needed_money, "found_money = " << print_money_brief(found_money) << " is less than needed_money = " << print_money_brief(needed_money) << ", assed_id: " << asset_id); + + if (is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM)) + { + for (auto& dst : dsts) + { + if (dst.asset_id == asset_id) + final_destinations.emplace_back(dst); + } + if (found_money > needed_money) + final_destinations.emplace_back(found_money - needed_money, m_account.get_public_address(), asset_id); // returning back the change + } + else + { + // pre-HF4 + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(asset_id == currency::native_coin_asset_id, "assets are not allowed prior to HF4"); + 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().account_address); + change_dts.amount = found_money - needed_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_destinations, 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); + + if (0 != dust && !dust_policy.add_to_fee) + { + final_destinations.emplace_back(dust, dust_policy.addr_for_dust); + } + } +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::prepare_transaction(construct_tx_param& ctp, currency::finalize_tx_param& ftp, const mode_separate_context& msc) +{ + + SET_CONTEXT_OBJ_FOR_SCOPE(pconstruct_tx_param, ctp); + SET_CONTEXT_OBJ_FOR_SCOPE(pfinalize_tx_param, ftp); + SET_CONTEXT_OBJ_FOR_SCOPE(pmode_separate_context, msc); + + TIME_MEASURE_START_MS(get_needed_money_time); + + const currency::transaction& tx_for_mode_separate = msc.tx_for_mode_separate; + assets_selection_context needed_money_map = get_needed_money(ctp.fee, ctp.dsts); + if (this->is_auditable() && ctp.fake_outputs_count > 0) + { + WLT_THROW_IF_FALSE_WITH_CODE(false, "WALLET_RPC_ERROR_CODE_WRONG_MIXINS_FOR_AUDITABLE_WALLET", "WALLET_RPC_ERROR_CODE_WRONG_MIXINS_FOR_AUDITABLE_WALLET"); + } + ftp.ado_current_asset_owner = ctp.ado_current_asset_owner; + ftp.pthirdparty_sign_handler = ctp.pthirdparty_sign_handler; + // + // TODO @#@# need to do refactoring over this part to support hidden amounts and asset_id + // + 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"); + if (ftp.tx_version > TRANSACTION_VERSION_PRE_HF4) + { + for (const auto& el : msc.proposal_info.to_initiator) + needed_money_map[el.asset_id].needed_amount += el.amount; + } + + if (msc.escrow) + needed_money_map[currency::native_coin_asset_id].needed_amount += (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.create_utxo_defragmentation_tx) + { + try + { + if (!prepare_tx_sources_for_defragmentation_tx(ftp.sources, ftp.selected_transfers, needed_money_map[currency::native_coin_asset_id].found_amount)) + return false; + } + catch (const error::not_enough_outs_to_mix&) { return false; } // if there's not enough decoys, return false to indicate minor non-fatal error + } + else if (ctp.htlc_tx_id != currency::null_hash) + { + //htlc + //@#@ need to do refactoring over this part to support hidden amounts and asset_id + prepare_tx_sources_htlc(ctp.htlc_tx_id, ctp.htlc_origin, ftp.sources, needed_money_map[currency::native_coin_asset_id].found_amount); + WLT_THROW_IF_FALSE_WITH_CODE(ctp.dsts.size() == 1, + "htlc: unexpected ctp.dsts.size() =" << ctp.dsts.size(), API_RETURN_CODE_INTERNAL_ERROR); + + WLT_THROW_IF_FALSE_WITH_CODE(needed_money_map[currency::native_coin_asset_id].found_amount > ctp.fee, + "htlc: found money less then fee", API_RETURN_CODE_INTERNAL_ERROR); + + //fill amount + ctp.dsts.begin()->amount = needed_money_map[currency::native_coin_asset_id].found_amount - ctp.fee; + + } + else if (ctp.multisig_id != currency::null_hash) + { + //multisig + //@#@ need to do refactoring over this part to support hidden amounts and asset_id + prepare_tx_sources(ctp.multisig_id, ftp.sources, needed_money_map[currency::native_coin_asset_id].found_amount); + } + else + { + //regular tx + prepare_tx_sources(needed_money_map, ctp.fake_outputs_count, ctp.dust_policy.dust_threshold, ftp.sources, ftp.selected_transfers); + } + TIME_MEASURE_FINISH_MS(prepare_tx_sources_time); + + TIME_MEASURE_START_MS(prepare_tx_destinations_time); + prepare_tx_destinations(needed_money_map, static_cast(ctp.split_strategy_id), ctp.dust_policy, ctp.dsts, ctp.flags, 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; + ftp.spend_pub_key = m_account.get_public_address().spend_public_key; + + /* 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);*/ + return true; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::finalize_transaction(currency::finalize_tx_param& ftp, currency::transaction& tx, crypto::secret_key& tx_key, bool broadcast_tx, bool store_tx_secret_key /* = true */) +{ + currency::finalized_tx result = AUTO_VAL_INIT(result); + result.tx = tx; + result.one_time_key = tx_key; + finalize_transaction(ftp, result, broadcast_tx, store_tx_secret_key); + tx = result.tx; + tx_key = result.one_time_key; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::finalize_transaction(currency::finalize_tx_param& ftp, currency::finalized_tx& result, bool broadcast_tx, bool store_tx_secret_key /* = true */) +{ + // NOTE: if broadcast_tx == true callback rise_on_transfer2() may be called at the end of this function. + // That callback may call balance(), so it's important to have all used/spending transfers + // to be correctly marked with corresponding flags PRIOR to calling finalize_transaction() + + // broadcasting tx without secret key storing is forbidden to avoid lost key issues + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(!broadcast_tx || store_tx_secret_key, "finalize_tx is requested to broadcast a tx without storing the key"); + + THROW_IF_FALSE_WALLET_EX_MES(ftp.sources.size() <= CURRENCY_TX_MAX_ALLOWED_INPUTS, error::tx_too_big, "Too many inputs: " << ftp.sources.size() << ", maximum allowed is " << CURRENCY_TX_MAX_ALLOWED_INPUTS << "."); + + bool r = currency::construct_tx(m_account.get_keys(), + ftp, result); + //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); + uint64_t effective_fee = 0; + THROW_IF_FALSE_WALLET_CMN_ERR_EX(!get_tx_fee(result.tx, effective_fee) || effective_fee <= WALLET_TX_MAX_ALLOWED_FEE, "tx fee is WAY too big: " << print_money_brief(effective_fee) << ", maximum allowed is " << print_money_brief(WALLET_TX_MAX_ALLOWED_FEE) << "."); + + //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(result.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); + + size_t tx_blob_size = tx_to_blob(result.tx).size(); + THROW_IF_FALSE_WALLET_EX_MES(tx_blob_size < CURRENCY_MAX_TRANSACTION_BLOB_SIZE, error::tx_too_big, "Transaction size: " << tx_blob_size << " bytes, transaction size limit: " << CURRENCY_MAX_TRANSACTION_BLOB_SIZE << " bytes."); + + if (store_tx_secret_key) + m_tx_keys.insert(std::make_pair(get_transaction_hash(result.tx), result.one_time_key)); + + //TIME_MEASURE_START(send_transaction_to_network_time); + if (broadcast_tx) + send_transaction_to_network(result.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(result.tx, ftp.attachments, ftp.prepared_destinations, ftp.selected_transfers); + //TIME_MEASURE_FINISH(add_sent_tx_detailed_info_time); + + // not logging success here because it's the caller's responsibility +} +//---------------------------------------------------------------------------------------------------- +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); +} +//---------------------------------------------------------------------------------------------------- +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, + std::string* p_unsigned_filename_or_tx_blob_str) +{ + //TIME_MEASURE_START(precalculation_time); + construct_tx_param ctp = AUTO_VAL_INIT(ctp); + ctp.attachments = attachments; + ctp.crypt_address = currency::get_crypt_address_from_destinations(m_account.get_keys(), dsts); + ctp.dsts = dsts; + ctp.dust_policy = dust_policy; + ctp.extra = extra; + ctp.fake_outputs_count = fake_outputs_count; + ctp.fee = fee; + ctp.flags = flags; + // ctp.mark_tx_as_complete + // ctp.multisig_id + ctp.shuffle = shuffle; + ctp.split_strategy_id = destination_split_strategy_id; + ctp.tx_outs_attr = tx_outs_attr; + ctp.unlock_time = unlock_time; + //TIME_MEASURE_FINISH(precalculation_time); + transfer(ctp, tx, send_to_network, p_unsigned_filename_or_tx_blob_str); +} +//---------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------- +construct_tx_param wallet2::get_default_construct_tx_param_inital() +{ + construct_tx_param ctp = AUTO_VAL_INIT(ctp); + + ctp.fee = m_core_runtime_config.tx_default_fee; + ctp.dust_policy = tools::tx_dust_policy(DEFAULT_DUST_THRESHOLD); + ctp.split_strategy_id = get_current_split_strategy(); + ctp.tx_outs_attr = CURRENCY_TO_KEY_OUT_RELAXED; + ctp.shuffle = 0; + return ctp; +} +construct_tx_param wallet2::get_default_construct_tx_param() +{ + return get_default_construct_tx_param_inital(); +} +//---------------------------------------------------------------------------------------------------- +bool wallet2::store_unsigned_tx_to_file_and_reserve_transfers(const currency::finalize_tx_param& ftp, const std::string& filename, std::string* p_unsigned_tx_blob_str /* = nullptr */) +{ + TIME_MEASURE_START(store_unsigned_tx_time); + blobdata bl = t_serializable_object_to_blob(ftp); + crypto::chacha_crypt(bl, m_account.get_keys().view_secret_key); + + if (!filename.empty()) + { + bool r = epee::file_io_utils::save_string_to_file(filename, bl); + CHECK_AND_ASSERT_MES(r, false, "failed to store unsigned tx to " << filename); + LOG_PRINT_L0("Transaction stored to " << filename << ". You need to sign this tx using a full-access wallet."); + } + else + { + CHECK_AND_ASSERT_MES(p_unsigned_tx_blob_str != nullptr, false, "empty filename and p_unsigned_tx_blob_str == null"); + *p_unsigned_tx_blob_str = bl; + } + + TIME_MEASURE_FINISH(store_unsigned_tx_time); + + // reserve transfers at the very end + TIME_MEASURE_START(mark_transfers_as_spent_time); + mark_transfers_with_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_COLD_SIG_RESERVATION, std::string("cold sig reservation for money transfer"), true); + TIME_MEASURE_FINISH(mark_transfers_as_spent_time); + + WLT_LOG_GREEN("[wallet::store_unsigned_tx_to_file_and_reserve_transfers]" + << " store_unsigned_tx_time: " << print_fixed_decimal_point(store_unsigned_tx_time, 3) + << ", mark_transfers_as_spent_time: " << print_fixed_decimal_point(mark_transfers_as_spent_time, 3) + , LOG_LEVEL_1); + return true; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::check_and_throw_if_self_directed_tx_with_payment_id_requested(const construct_tx_param& ctp) +{ + // If someone sends coins to his own address, all tx outputs will be detected as own outputs. + // It's totally okay unless payment id is used, because it would be impossible to distinguish + // between change outs and transfer outs. Thus, such tx with a payment id can't be correctly + // obtained via RPC by the given payment id. It could be a problem for an exchange or other + // service when a user, identifyied by payment id sends coins to another user on the same + // exchange/service. Coins will be received but RPCs like get_payments won't give the transfer. + // To avoid such issues we prohibit such txs with a soft rule on sender side. + + for (auto& d : ctp.dsts) + { + for (auto& addr : d.addr) + { + if (addr != m_account.get_public_address()) + return; // at least one destination address is not our address -- it's not self-directed tx + } + } + + // it's self-directed tx + payment_id_t pid; + bool has_payment_id = get_payment_id_from_decrypted_container(ctp.attachments, pid) && !pid.empty(); + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(!has_payment_id, "sending funds to yourself with payment id is not allowed"); +} +//---------------------------------------------------------------------------------------------------- +void wallet2::transfer(construct_tx_param& ctp, + currency::transaction& tx, + bool send_to_network, + std::string* p_unsigned_filename_or_tx_blob_str) +{ + currency::finalized_tx result = AUTO_VAL_INIT(result); + transfer(ctp, result, send_to_network, p_unsigned_filename_or_tx_blob_str); + tx = result.tx; +} +//---------------------------------------------------------------------------------------------------- +void wallet2::transfer(construct_tx_param& ctp, + currency::finalized_tx& result, + bool send_to_network, + std::string* p_unsigned_filename_or_tx_blob_str) +{ + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(!is_auditable() || !is_watch_only(), "You can't initiate coins transfer using an auditable watch-only wallet."); // btw, watch-only wallets can call transfer() within cold-signing process + + check_and_throw_if_self_directed_tx_with_payment_id_requested(ctp); + + bool asset_operation_requested = count_type_in_variant_container(ctp.extra) != 0; + bool dont_have_zero_asset_ids_in_destinations = std::count_if(ctp.dsts.begin(), ctp.dsts.end(), [](const tx_destination_entry& de) { return de.asset_id == null_pkey; }) == 0; + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(asset_operation_requested || dont_have_zero_asset_ids_in_destinations, "zero asset id is used errounesly (no asset operation was requested)"); + + if (ctp.crypt_address.spend_public_key == currency::null_pkey) + { + ctp.crypt_address = currency::get_crypt_address_from_destinations(m_account.get_keys(), ctp.dsts); + } + + TIME_MEASURE_START(prepare_transaction_time); + currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); + ftp.pevents_dispatcher = &m_debug_events_dispatcher; + ftp.tx_version = this->get_current_tx_version(); + if (!prepare_transaction(ctp, ftp)) + { + result.was_not_prepared = true; + return; + } + TIME_MEASURE_FINISH(prepare_transaction_time); + + if (m_watch_only) + { + bool r = store_unsigned_tx_to_file_and_reserve_transfers(ftp, (p_unsigned_filename_or_tx_blob_str != nullptr ? *p_unsigned_filename_or_tx_blob_str : "zano_tx_unsigned"), p_unsigned_filename_or_tx_blob_str); + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "failed to store unsigned tx"); + WLT_LOG_GREEN("[wallet::transfer]" << " prepare_transaction_time: " << print_fixed_decimal_point(prepare_transaction_time, 3), LOG_LEVEL_0); + return; + } + + 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(result.tx))); + TIME_MEASURE_FINISH(mark_transfers_as_spent_time); + + TIME_MEASURE_START(finalize_transaction_time); + try + { + finalize_transaction(ftp, result, send_to_network); + } + catch (...) + { + clear_transfers_from_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, std::string("exception on money transfer, tx: ") + epee::string_tools::pod_to_hex(get_transaction_hash(result.tx))); + throw; + } + TIME_MEASURE_FINISH(finalize_transaction_time); + + + WLT_LOG_GREEN("[wallet::transfer]" + //<< " precalculation_time: " << print_fixed_decimal_point(precalculation_time, 3) + << ", prepare_transaction_time: " << print_fixed_decimal_point(prepare_transaction_time, 3) + << ", finalize_transaction_time: " << print_fixed_decimal_point(finalize_transaction_time, 3) + << ", mark_transfers_as_spent_time: " << print_fixed_decimal_point(mark_transfers_as_spent_time, 3) + , LOG_LEVEL_0); + + print_tx_sent_message(result.tx, std::string() + "(transfer)", ctp.fee); +} + +//---------------------------------------------------------------------------------------------------- +void wallet2::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 */) +{ + bool r = false; + outs_total = 0; + amount_total = 0; + outs_swept = 0; + amount_swept = 0; + + std::vector selected_transfers; + std::unordered_map fake_outs_for_selected_transfers; // tr index -> fake outs count + selected_transfers.reserve(m_transfers.size()); + fake_outs_for_selected_transfers.reserve(m_transfers.size()); + for (const auto& tr : m_transfers) + { + uint64_t i = tr.first; + const transfer_details& td = tr.second; + size_t fake_outs_count_for_td = is_auditable() ? 0 : (td.is_zc() ? m_core_runtime_config.hf4_minimum_mixins : fake_outs_count); + uint64_t amount = td.amount(); + if (amount < threshold_amount && td.is_native_coin() && + is_transfer_ready_to_go(td, fake_outs_count_for_td)) + { + selected_transfers.push_back(i); + r = fake_outs_for_selected_transfers.insert(std::make_pair(i, fake_outs_count_for_td)).second; + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(r, "unable to insert: " << i << ", " << fake_outs_count_for_td); + outs_total += 1; + amount_total += amount; + } + } + + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(!selected_transfers.empty(), "No spendable outputs meet the criterion"); + + // sort by amount descending in order to spend bigger outputs first + std::sort(selected_transfers.begin(), selected_transfers.end(), [this](uint64_t a, uint64_t b) { return m_transfers.at(b).amount() < m_transfers.at(a).amount(); }); + + // limit RPC request with reasonable number of sources + if (selected_transfers.size() > CURRENCY_TX_MAX_ALLOWED_INPUTS) + selected_transfers.erase(selected_transfers.begin() + CURRENCY_TX_MAX_ALLOWED_INPUTS, selected_transfers.end()); + + prefetch_global_indicies_if_needed(selected_transfers); + + size_t max_fake_outs_count = 0; + for (auto tr_idx : selected_transfers) + if (max_fake_outs_count < fake_outs_for_selected_transfers[tr_idx]) + max_fake_outs_count = fake_outs_for_selected_transfers[tr_idx]; + + static const size_t estimated_bytes_per_input = 85; + const size_t estimated_max_inputs = static_cast(CURRENCY_MAX_TRANSACTION_BLOB_SIZE / (estimated_bytes_per_input * (max_fake_outs_count + 1.5))); // estimated number of maximum tx inputs under the tx size limit + + typedef COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry out_entry; + typedef currency::tx_source_entry::output_entry tx_output_entry; + + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response rpc_get_random_outs_resp = AUTO_VAL_INIT(rpc_get_random_outs_resp); + if (max_fake_outs_count > 0) + { + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request req = AUTO_VAL_INIT(req); + req.height_upper_limit = m_last_pow_block_h; + req.use_forced_mix_outs = false; + req.decoys_count = max_fake_outs_count + 1; + for (uint64_t i : selected_transfers) + req.amounts.push_back(m_transfers.at(i).is_zc() ? 0 : m_transfers.at(i).m_amount); + + r = m_core_proxy->call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS(req, rpc_get_random_outs_resp); + + THROW_IF_FALSE_WALLET_EX(r, error::no_connection_to_daemon, "getrandom_outs1.bin"); + THROW_IF_FALSE_WALLET_EX(rpc_get_random_outs_resp.status != API_RETURN_CODE_BUSY, error::daemon_busy, "getrandom_outs1.bin"); + THROW_IF_FALSE_WALLET_EX(rpc_get_random_outs_resp.status == API_RETURN_CODE_OK, error::get_random_outs_error, rpc_get_random_outs_resp.status); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(rpc_get_random_outs_resp.outs.size() == selected_transfers.size(), + "daemon returned wrong number of amounts for getrandom_outs1.bin: " << rpc_get_random_outs_resp.outs.size() << ", requested: " << selected_transfers.size()); + + std::vector scanty_outs; + for (COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& amount_outs : rpc_get_random_outs_resp.outs) + { + if (amount_outs.outs.size() < max_fake_outs_count) + scanty_outs.push_back(amount_outs); + } + THROW_IF_FALSE_WALLET_EX(scanty_outs.empty(), error::not_enough_outs_to_mix, scanty_outs, max_fake_outs_count); + } + + currency::finalize_tx_param ftp = AUTO_VAL_INIT(ftp); + ftp.tx_version = this->get_current_tx_version(); + bool is_hf4 = this->is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM); + if (!payment_id.empty()) + set_payment_id_to_tx(ftp.attachments, payment_id, is_hf4); + // put encrypted payer info into the extra + ftp.crypt_address = destination_addr; + + currency::create_and_add_tx_payer_to_container_from_address(ftp.extra, m_account.get_public_address(), get_top_block_height(), m_core_runtime_config); + + ftp.flags = 0; + // ftp.multisig_id -- not required + // ftp.prepared_destinations -- will be filled by prepare_tx_destinations + // ftp.selected_transfers -- needed only at stage of broadcasting or storing unsigned tx + ftp.shuffle = false; + // ftp.sources -- will be filled in try_construct_tx + ftp.spend_pub_key = m_account.get_public_address().spend_public_key; // needed for offline signing + ftp.tx_outs_attr = CURRENCY_TO_KEY_OUT_RELAXED; + ftp.unlock_time = 0; + + enum try_construct_result_t { rc_ok = 0, rc_too_few_outputs = 1, rc_too_many_outputs = 2, rc_create_tx_failed = 3 }; + auto get_result_t_str = [](try_construct_result_t t) -> const char* + { return t == rc_ok ? "rc_ok" : t == rc_too_few_outputs ? "rc_too_few_outputs" : t == rc_too_many_outputs ? "rc_too_many_outputs" : t == rc_create_tx_failed ? "rc_create_tx_failed" : "unknown"; }; + + auto try_construct_tx = [this, &selected_transfers, &rpc_get_random_outs_resp, &fake_outs_for_selected_transfers, &fee, &destination_addr] + (size_t st_index_upper_boundary, currency::finalize_tx_param& ftp, uint64_t& amount_swept) -> try_construct_result_t + { + amount_swept = 0; + ftp.gen_context = tx_generation_context{}; + ftp.sources.clear(); + ftp.prepared_destinations.clear(); + + // prepare inputs + ftp.sources.resize(st_index_upper_boundary); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(st_index_upper_boundary <= selected_transfers.size(), "index_upper_boundary = " << st_index_upper_boundary << ", selected_transfers.size() = " << selected_transfers.size()); + for (size_t st_index = 0; st_index < st_index_upper_boundary; ++st_index) + { + currency::tx_source_entry& src = ftp.sources[st_index]; + uint64_t tr_index = selected_transfers[st_index]; + transfer_details& td = m_transfers.at(tr_index); + src.transfer_index = tr_index; + src.amount = td.amount(); + amount_swept += src.amount; + + // populate src.outputs with mix-ins + if (rpc_get_random_outs_resp.outs.size()) + { + rpc_get_random_outs_resp.outs[st_index].outs.sort([](const out_entry& a, const out_entry& b) { return a.global_amount_index < b.global_amount_index; }); + for (out_entry& daemon_oe : rpc_get_random_outs_resp.outs[st_index].outs) + { + if (td.m_global_output_index == daemon_oe.global_amount_index) + continue; + src.outputs.emplace_back(daemon_oe.global_amount_index, daemon_oe.stealth_address, daemon_oe.concealing_point, daemon_oe.amount_commitment, daemon_oe.blinded_asset_id); + if (src.outputs.size() >= fake_outs_for_selected_transfers[tr_index]) + break; + } + } + + // insert real output into src.outputs + // TODO: bad design, we need to get rid of code duplicates below -- sowle + auto it_to_insert = std::find_if(src.outputs.begin(), src.outputs.end(), [&](const tx_output_entry& a) + { + if (a.out_reference.type().hash_code() == typeid(uint64_t).hash_code()) + return static_cast(boost::get(a.out_reference) >= td.m_global_output_index); + return false; // TODO: implement deterministics real output placement in case there're ref_by_id outs + }); + tx_output_entry real_oe = AUTO_VAL_INIT(real_oe); + txout_ref_v out_reference = td.m_global_output_index; // TODO: use ref_by_id when neccessary + std::vector::iterator interted_it = src.outputs.end(); + VARIANT_SWITCH_BEGIN(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]); + VARIANT_CASE_CONST(tx_out_bare, o) + { + VARIANT_SWITCH_BEGIN(o.target); + VARIANT_CASE_CONST(txout_to_key, o) + interted_it = src.outputs.emplace(it_to_insert, out_reference, o.key); + VARIANT_CASE_CONST(txout_htlc, htlc) + interted_it = src.outputs.emplace(it_to_insert, out_reference, htlc.pkey_refund); + VARIANT_CASE_OTHER() + { + WLT_THROW_IF_FALSE_WITH_CODE(false, + "Internal error: unexpected type of target: " << o.target.type().name(), + API_RETURN_CODE_INTERNAL_ERROR); + } + VARIANT_SWITCH_END(); + } + VARIANT_CASE_CONST(tx_out_zarcanum, o); + interted_it = src.outputs.emplace(it_to_insert, out_reference, o.stealth_address, o.concealing_point, o.amount_commitment, o.blinded_asset_id); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.is_zc(), "transfer #" << tr_index << ", amount: " << print_money_brief(td.amount()) << " is not a ZC"); + src.real_out_amount_blinding_mask = td.m_zc_info_ptr->amount_blinding_mask; + src.real_out_asset_id_blinding_mask = td.m_zc_info_ptr->asset_id_blinding_mask; + src.asset_id = td.m_zc_info_ptr->asset_id; + VARIANT_SWITCH_END(); + src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_ptx_wallet_info->m_tx); + src.real_output = interted_it - src.outputs.begin(); + src.real_output_in_tx_index = td.m_internal_output_index; + } + + if (amount_swept <= fee) + return rc_too_few_outputs; + + // try to construct a transaction + + assets_selection_context needed_money_map; + needed_money_map[currency::native_coin_asset_id] = {}; + const std::vector dsts({ tx_destination_entry(amount_swept - fee, destination_addr) }); + prepare_tx_destinations(needed_money_map, get_current_split_strategy(), tools::tx_dust_policy(), dsts, ftp.flags, ftp.prepared_destinations); + + currency::transaction tx = AUTO_VAL_INIT(tx); + crypto::secret_key tx_key = AUTO_VAL_INIT(tx_key); + try + { + finalize_transaction(ftp, tx, tx_key, false, false); + } + catch (error::tx_too_big&) + { + return rc_too_many_outputs; + } + catch (...) + { + return rc_create_tx_failed; + } + + return rc_ok; + }; + + size_t st_index_upper_boundary = std::min(selected_transfers.size(), estimated_max_inputs); + try_construct_result_t res = try_construct_tx(st_index_upper_boundary, ftp, amount_swept); + + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(res != rc_too_few_outputs, st_index_upper_boundary << " biggest unspent outputs have total amount of " << print_money_brief(amount_swept) + << " which is less than required fee: " << print_money_brief(fee) << ", transaction cannot be constructed"); + + if (res == rc_too_many_outputs) + { + WLT_LOG_L1("sweep_below: first try of try_construct_tx(" << st_index_upper_boundary << ") returned " << get_result_t_str(res)); + size_t low_bound = 0; + size_t high_bound = st_index_upper_boundary; + currency::finalize_tx_param ftp_ok = ftp; + for (;;) + { + if (low_bound + 1 >= high_bound) + { + st_index_upper_boundary = low_bound; + res = rc_ok; + ftp = ftp_ok; + break; + } + st_index_upper_boundary = (low_bound + high_bound) / 2; + try_construct_result_t res = try_construct_tx(st_index_upper_boundary, ftp, amount_swept); + WLT_LOG_L1("sweep_below: try_construct_tx(" << st_index_upper_boundary << ") returned " << get_result_t_str(res)); + if (res == rc_ok) + { + low_bound = st_index_upper_boundary; + ftp_ok = ftp; + } + else if (res == rc_too_many_outputs) + { + high_bound = st_index_upper_boundary; + } + else + break; + } + } + + if (res != rc_ok) + { + uint64_t amount_min = UINT64_MAX, amount_max = 0, amount_sum = 0; + for (auto& i : selected_transfers) + { + uint64_t amount = m_transfers.at(i).amount(); + amount_min = std::min(amount_min, amount); + amount_max = std::max(amount_max, amount); + amount_sum += amount; + } + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(false, "try_construct_tx failed with result: " << get_result_t_str(res) << " (" << res << ")" << + ", selected_transfers stats:\n" << + " outs: " << selected_transfers.size() << ENDL << + " amount min: " << print_money(amount_min) << ENDL << + " amount max: " << print_money(amount_max) << ENDL << + " amount avg: " << (selected_transfers.empty() ? std::string("n/a") : print_money(amount_sum / selected_transfers.size()))); + } + + // populate ftp.selected_transfers from ftp.sources + ftp.selected_transfers.clear(); + for (size_t i = 0; i < ftp.sources.size(); ++i) + ftp.selected_transfers.push_back(ftp.sources[i].transfer_index); + + outs_swept = ftp.sources.size(); + + + if (m_watch_only) + { + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(p_filename_or_unsigned_tx_blob_str != nullptr, "p_filename_or_unsigned_tx_blob_str is null"); + bool r = store_unsigned_tx_to_file_and_reserve_transfers(ftp, *p_filename_or_unsigned_tx_blob_str, p_filename_or_unsigned_tx_blob_str); + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "failed to store unsigned tx"); + return; + } + + mark_transfers_as_spent(ftp.selected_transfers, "sweep_below"); + + transaction local_tx; + transaction* p_tx = p_result_tx != nullptr ? p_result_tx : &local_tx; + *p_tx = transaction{}; + try + { + crypto::secret_key sk{}; + finalize_transaction(ftp, *p_tx, sk, true); + print_tx_sent_message(*p_tx, "(sweep_below)", get_tx_fee(*p_tx)); + } + catch (...) + { + clear_transfers_from_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, std::string("exception on sweep_below, tx id (might be wrong): ") + epee::string_tools::pod_to_hex(get_transaction_hash(*p_tx))); + throw; + } + + +} + } // namespace tools From 93fdf0e677c302a6d3da75318adc27cabbeeb80b Mon Sep 17 00:00:00 2001 From: sowle Date: Wed, 9 Oct 2024 17:48:41 +0200 Subject: [PATCH 008/106] functional tests: transactions flow test fixed --- tests/functional_tests/transactions_flow_test.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/functional_tests/transactions_flow_test.cpp b/tests/functional_tests/transactions_flow_test.cpp index 95646b63..07707a2d 100644 --- a/tests/functional_tests/transactions_flow_test.cpp +++ b/tests/functional_tests/transactions_flow_test.cpp @@ -197,9 +197,9 @@ uint64_t got_money_in_first_transfers(const tools::transfer_container& incoming_ { uint64_t summ = 0; size_t count = 0; - BOOST_FOREACH(const tools::transfer_details& td, incoming_transfers) + for(auto& tr : incoming_transfers) { - summ += boost::get(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]).amount; + summ += boost::get(tr.second.m_ptx_wallet_info->m_tx.vout[tr.second.m_internal_output_index]).amount; if(++count >= n_transfers) return summ; } @@ -245,9 +245,9 @@ std::string get_incoming_transfers_str(tools::wallet2& w) uint64_t spent_count = 0; uint64_t unspent_count = 0; - for (const auto& td : transfers) + for (const auto& tr : transfers) { - if (td.m_flags&WALLET_TRANSFER_DETAIL_FLAG_SPENT) + if (tr.second.m_flags&WALLET_TRANSFER_DETAIL_FLAG_SPENT) { ++spent_count; } @@ -469,8 +469,9 @@ bool transactions_flow_test( //lets go! size_t count = 0; prepared_transfers = 0; - BOOST_FOREACH(tools::transfer_details& td, incoming_transfers) + for(const auto& tr : incoming_transfers) { + const tools::transfer_details& td = tr.second; if (td.is_spent()) continue; From 02aeaa20788cb1e5ef50369942af3e7b84f5fb21 Mon Sep 17 00:00:00 2001 From: sowle Date: Wed, 9 Oct 2024 18:10:17 +0200 Subject: [PATCH 009/106] wallet2 : transfer_flags_to_str updated to support recently added flags --- src/wallet/wallet2.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 497e170f..f4b203ad 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -129,7 +129,7 @@ namespace tools //---------------------------------------------------------------------------------------------------- std::string wallet2::transfer_flags_to_str(uint32_t flags) { - std::string result(7, ' '); + std::string result(8, ' '); if (flags & WALLET_TRANSFER_DETAIL_FLAG_SPENT) result[0] = 's'; if (flags & WALLET_TRANSFER_DETAIL_FLAG_BLOCKED) @@ -142,8 +142,11 @@ std::string wallet2::transfer_flags_to_str(uint32_t flags) result[4] = 'c'; if (flags & WALLET_TRANSFER_DETAIL_FLAG_HTLC_REDEEM) result[5] = 'h'; + if (flags & WALLET_TRANSFER_DETAIL_CONCISE_MODE_PRESERVE) + result[6] = 'p'; if (flags & WALLET_TRANSFER_DETAIL_FLAG_ASSET_OP_RESERVATION) - result[6] = 'a'; + result[7] = 'a'; + return result; } //---------------------------------------------------------------------------------------------------- @@ -4112,7 +4115,7 @@ bool wallet2::generate_utxo_defragmentation_transaction_if_needed(currency::tran //---------------------------------------------------------------------------------------------------- std::string wallet2::get_transfers_str(bool include_spent /*= true*/, bool include_unspent /*= true*/, bool show_only_unknown /*= false*/, const std::string& filter_asset_ticker /*= std::string{}*/) const { - static const char* header = " index amount ticker g_index flags block tx out# asset id"; + static const char* header = " index amount ticker g_index flags block tx out# asset id"; std::stringstream ss; ss << header << ENDL; size_t count = 0; @@ -4145,7 +4148,7 @@ std::string wallet2::get_transfers_str(bool include_spent /*= true*/, bool inclu std::setw(6) << std::left << (native_coin ? std::string(" ") : adb.ticker) << " " << std::right << std::setw(7) << td.m_global_output_index << " " << std::setw(2) << std::setfill('0') << td.m_flags << std::setfill(' ') << ":" << - std::setw(7) << transfer_flags_to_str(td.m_flags) << " " << + std::setw(8) << transfer_flags_to_str(td.m_flags) << " " << std::setw(7) << td.m_ptx_wallet_info->m_block_height << " " << get_transaction_hash(td.m_ptx_wallet_info->m_tx) << " " << std::setw(4) << td.m_internal_output_index << " "; From 986f7dc7d30fc64405110b8b03b7cdb267f90847 Mon Sep 17 00:00:00 2001 From: sowle Date: Thu, 10 Oct 2024 15:20:42 +0200 Subject: [PATCH 010/106] minor improvements --- src/wallet/wallet_errors.h | 7 +++++-- tests/core_tests/wallet_tests.cpp | 7 ++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index 636f7bf6..33b0be51 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -321,10 +321,13 @@ namespace tools const currency::transaction m_tx; }; //---------------------------------------------------------------------------------------------------- - struct wallet_error_resync_needed + struct wallet_error_resync_needed : public std::exception { + wallet_error_resync_needed() + : std::exception("wallet_error_resync_needed") + {} }; - + //---------------------------------------------------------------------------------------------------- struct tx_parse_error : public refresh_error { explicit tx_parse_error(std::string&& loc, const currency::blobdata& tx_blob) diff --git a/tests/core_tests/wallet_tests.cpp b/tests/core_tests/wallet_tests.cpp index bb822de1..79eb631d 100644 --- a/tests/core_tests/wallet_tests.cpp +++ b/tests/core_tests/wallet_tests.cpp @@ -3822,6 +3822,7 @@ bool wallet_and_sweep_below::c1(currency::core& c, size_t ev_index, const std::v //------------------------------------------------------------------------------ + block_template_blacklist_test::block_template_blacklist_test() { REGISTER_CALLBACK_METHOD(block_template_blacklist_test, c1); @@ -3912,12 +3913,13 @@ bool block_template_blacklist_test::c1(currency::core& c, size_t ev_index, const return true; } +//------------------------------------------------------------------------------ + wallet_reorganize_and_trim_test::wallet_reorganize_and_trim_test() { REGISTER_CALLBACK_METHOD(wallet_reorganize_and_trim_test, c1); } - bool wallet_reorganize_and_trim_test::generate(std::vector& events) const { uint64_t ts = test_core_time::get_time(); @@ -3937,6 +3939,7 @@ bool wallet_reorganize_and_trim_test::generate(std::vector& ev return true; } + bool wallet_reorganize_and_trim_test::c1(currency::core& c, size_t ev_index, const std::vector& events) { std::shared_ptr miner_wlt = init_playtime_test_wallet(events, c, MINER_ACC_IDX); @@ -3970,5 +3973,3 @@ bool wallet_reorganize_and_trim_test::c1(currency::core& c, size_t ev_index, con } return true; } - - From bfa68e4b8876fd47b397c6d51ac9f79594987762 Mon Sep 17 00:00:00 2001 From: sowle Date: Thu, 10 Oct 2024 20:45:43 +0200 Subject: [PATCH 011/106] an attempt to fix gcc compilation --- src/wallet/wallet_errors.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index 33b0be51..7479159b 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -323,9 +323,7 @@ namespace tools //---------------------------------------------------------------------------------------------------- struct wallet_error_resync_needed : public std::exception { - wallet_error_resync_needed() - : std::exception("wallet_error_resync_needed") - {} + virtual const char* what() const noexcept override { return "wallet_error_resync_needed"; } }; //---------------------------------------------------------------------------------------------------- struct tx_parse_error : public refresh_error From 52bd48dbc394147bf1a92d3a6a60d2e00e04a320 Mon Sep 17 00:00:00 2001 From: zano build machine Date: Thu, 10 Oct 2024 23:47:36 +0300 Subject: [PATCH 012/106] === build number: 349 -> 350 === --- src/version.h.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h.in b/src/version.h.in index 9455b660..cdd68cbb 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -8,6 +8,6 @@ #define PROJECT_REVISION "1" #define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION -#define PROJECT_VERSION_BUILD_NO 349 +#define PROJECT_VERSION_BUILD_NO 350 #define PROJECT_VERSION_BUILD_NO_STR STRINGIFY_EXPAND(PROJECT_VERSION_BUILD_NO) #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO_STR "[" BUILD_COMMIT_ID "]" From b1c70ac2bb8f52944281cddd2b37bb06604ec2a6 Mon Sep 17 00:00:00 2001 From: sowle Date: Fri, 11 Oct 2024 05:41:36 +0200 Subject: [PATCH 013/106] === version: 2.0.1 -> 2.0.2 === --- src/version.h.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h.in b/src/version.h.in index cdd68cbb..f2864e10 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -5,7 +5,7 @@ #define PROJECT_MAJOR_VERSION "2" #define PROJECT_MINOR_VERSION "0" -#define PROJECT_REVISION "1" +#define PROJECT_REVISION "2" #define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION #define PROJECT_VERSION_BUILD_NO 350 From 5d15ed1c154696745848ed2892cc831b293fb5a5 Mon Sep 17 00:00:00 2001 From: sowle Date: Sat, 12 Oct 2024 03:01:33 +0200 Subject: [PATCH 014/106] point_t::operator==() fixed and optimized + various tests --- src/crypto/crypto-sugar.h | 21 +--- tests/functional_tests/crypto_tests.cpp | 101 +++++++++++++++-- .../crypto_tests_performance.h | 107 +++++++++++++++++- 3 files changed, 197 insertions(+), 32 deletions(-) diff --git a/src/crypto/crypto-sugar.h b/src/crypto/crypto-sugar.h index b74edc4f..8fe7890a 100644 --- a/src/crypto/crypto-sugar.h +++ b/src/crypto/crypto-sugar.h @@ -812,26 +812,7 @@ namespace crypto friend bool operator==(const point_t& lhs, const point_t& rhs) { - // TODO: @#@# (performance) consider checking (lhs - rhs).is_zero() instead - - // convert to xy form, then compare components (because (x, y, z, t) representation is not unique) - fe lrecip, lx, ly; - fe rrecip, rx, ry; - - fe_invert(lrecip, lhs.m_p3.Z); - fe_invert(rrecip, rhs.m_p3.Z); - - fe_mul(lx, lhs.m_p3.X, lrecip); - fe_mul(rx, rhs.m_p3.X, rrecip); - if (memcmp(&lx, &rx, sizeof lx) != 0) - return false; - - fe_mul(ly, lhs.m_p3.Y, lrecip); - fe_mul(ry, rhs.m_p3.Y, rrecip); - if (memcmp(&ly, &ry, sizeof ly) != 0) - return false; - - return true; + return (lhs - rhs).is_zero(); } friend bool operator!=(const point_t& lhs, const point_t& rhs) diff --git a/tests/functional_tests/crypto_tests.cpp b/tests/functional_tests/crypto_tests.cpp index d4217d47..28a9b18c 100644 --- a/tests/functional_tests/crypto_tests.cpp +++ b/tests/functional_tests/crypto_tests.cpp @@ -317,7 +317,7 @@ inline std::ostream& operator<<(std::ostream& ss, const fe &f) { constexpr size_t fe_index_max = (sizeof f / sizeof f[0]) - 1; ss << "{"; - for (size_t i = 0; i <= fe_index_max; ++i) + for (size_t i = 0; i < fe_index_max; ++i) ss << f[i] << ", "; return ss << f[fe_index_max] << "}"; } @@ -490,14 +490,6 @@ struct test_keeper_t }; -//////////////////////////////////////////////////////////////////////////////// -// #include "crypto_tests_ml2s.h" -#include "crypto_tests_range_proofs.h" -#include "crypto_tests_clsag.h" -#include "crypto_tests_one_out_of_many_proofs.h" -//////////////////////////////////////////////////////////////////////////////// - - // // Tests @@ -609,8 +601,6 @@ TEST(crypto, constants) -#include "crypto_tests_performance.h" - TEST(crypto, ge_scalarmult_vartime_p3) { // make sure that my ge_scalarmult_vartime_p3 gives the same result as ge_scalarmul_p3 @@ -1501,6 +1491,86 @@ TEST(crypto, point_is_zero) return true; } +// pairs of the same points but with different fe representation (that caused old eq op to fail) +std::vector> twin_point_pairs { + { + point_t{{-21128868, 3837998, 33062696, -4394645, -14632370, -4032942, -13494326, 9949403, -22877702, 12347080, -23842113, 10741520, -31317130, 13501783, -30346713, 9537971, -4230538, 5133263, 19202715, 304530, 11783766, -14713954, 31928743, -15802260, -23524982, -12132264, -2221079, -13294882, 15351986, -12208940, -25429666, -10630821, 10586935, 3680531, -5196293, 15100068, -4109273, 2588076, -22807834, 9178747}}, + point_t{{-9713365, -9829929, 12195149, -3403928, -26261133, -16229257, -31293898, -3007350, 14656870, -441189, -21842901, -10684017, -14933612, -15577968, 6355936, -12616303, 17881365, 2613091, -29791631, 8160616, -6564960, 3860717, -32409761, 10253039, -32570848, -3563771, -7755841, 14547843, -12480489, 13372339, 21802122, 15976373, -16158840, -2718080, -16676669, 14276439, -7192951, 11049430, 27384949, 10563297}} + }, + { + point_t{{3695219, -9621154, -17709499, 2939106, 3744267, -14575097, -23519506, -10091102, -2604099, 14269408, -3229226, 6334758, -32391005, -2351680, -8798530, -2266840, 32921382, -2519414, -18691041, 3881653, -15053990, 9136740, -3226527, -4942926, 1506694, -9001375, 27935490, -14623776, -3948473, 4210796, 18939736, -15596865, -13293120, 1337568, -18317440, -5143258, 16723454, 14691110, 9769827, -3118966}}, + point_t{{-11397382, -16642076, -15110371, -3127178, -20841265, -8333469, -8416820, -11641211, -24444275, -1309821, -3258975, -14590075, 2704495, -11408556, -1158440, -13417600, -17702918, 15792391, -17674166, 7544500, -30233403, -6317281, -29573467, 13768629, -1805743, 10305936, -5964981, 2553131, 25279025, 5554228, 24015913, 3005849, 25526408, -4040648, 14468366, 14790756, 25816013, 15853930, 18508209, -16582046}} + }, + { + point_t{{3469318, -85183, 29056087, 13528494, -23149977, 16623454, 3373881, 7763807, 24097266, 11526397, 8089099, -2103871, -32120521, 1046529, 28087844, 6858112, -27963932, -3359170, 21430085, -5312246, 21764518, -13443543, -12702889, -12398634, 10157405, 4858225, 26174527, -11743221, -30269431, -10050502, 25862179, 4744514, 32296603, 6270322, -28198957, 1471820, -13801749, -3295909, 17760181, -12065527}}, + point_t{{-28763218, -1114036, 4671232, -1599732, -14823984, -9622687, 15430161, 12840558, 30741788, -11449341, -28556261, 3623611, 25689145, -14680141, -7832784, 14819387, 2481189, -13167197, -28732414, -14312280, -32965162, -7714625, 33268896, -2408169, -10811971, -4363847, -26138329, 5165910, -18544903, -7502120, 10360740, 6034706, 28458994, -5675091, -28036374, -702149, -8660662, -13290682, 9067253, 4344694}} + }, + { + point_t{{-5841866, -15636514, 12367718, 16116867, -26645766, 5140632, 33476033, -3893277, -6807986, -6188111, -18150893, -7313585, -22559821, 14208716, -5344811, -581281, 24672694, -6638035, -10808134, -3046, -7472033, -8901879, 15120908, -7133930, 15015498, 12440181, -27737720, 396004, 3105271, 11399924, 12739797, -10781030, -27655617, 10547004, -1517603, -14788397, -16728565, 5538806, 27633091, 5635300}}, + point_t{{29738502, -9099722, 18969328, -5041586, -17865763, 3799509, -8535672, 15345696, 760867, -3647512, 21227601, 2877796, 21366975, -5870266, 14750680, 10962526, 10279514, 12394109, -15626344, -10182797, 3199874, -24046, -14148768, -14198371, -29493512, -16221096, -3714752, -8636183, 5751940, -7670476, 1204040, 8561908, -16290929, 13125810, -24274070, -7039328, -21149730, -9157888, -16370207, -4817989}} + }, + { + point_t{{5583931, 3584727, 352648, -3282339, -25634902, 4982415, 6570289, -7123343, 28056356, -14753772, 15464638, -10030089, 13934473, 10182538, -32633085, -3344322, 10345546, -6010634, -11737756, 7740657, 12017410, -3538127, -21949796, -16720911, 18521392, -7746707, -25163322, 16438097, -14488853, -11100914, 11664826, -4058927, 13298397, 10849774, 22800849, -8941814, 31033013, 362090, -20721545, 10379502}}, + point_t{{-15413065, 10389318, 28684750, 9129051, 13176029, -15599624, 16333382, -15196190, 5299760, 5379067, -11298666, -10943555, 24033630, -10331364, -6271762, 15703052, -6168555, -10342077, -17144019, -3303678, 32460403, 15294706, 19144257, 16248865, 22506488, 5505084, -401821, 9347950, -6513204, 7226066, -26715856, 11994906, -13302883, -8556677, 15797496, -3086516, 23027165, 1637560, -29381930, 12262558}} + }, + { + point_t{{-26548919, 11392979, 28773327, 197314, 13888676, 5289903, 14156094, -3215467, 12204802, 11098576, -15494111, 10812748, -11869915, -343850, -20456404, -12577384, -4979562, 15251598, -487043, 11571657, -16066121, -2338179, -16228507, 5602197, 25353942, -7225080, 22718433, -11309476, -10403076, 11326705, -296493, -4563931, 10628858, 16672320, 24967340, 210989, -11332864, -13045242, -32517393, 9220843}}, + point_t{{-8809414, 1276711, 5724117, -10893002, 16836911, -10562554, 7928860, 4905731, -30890761, -16272783, 15099435, 7209265, -17062927, -15647371, -26068760, -10022538, 15199805, 11039218, -17427534, -3334404, 9597778, 5993687, -14091491, -6035380, -18428597, -4056089, -14203111, 349297, 20316350, 1399705, 2972501, -1228750, -23459865, -88574, 9071309, 11300857, 9425425, 596628, 3627198, -1613531}} + }, + { + point_t{{-15051466, -12717864, -1094059, 9523511, -9851421, 6693669, -6491711, -14360212, -10025337, 5146942, -15213537, -14748468, -16189919, 6643542, -22303813, -6712277, 12722420, 3747576, -11411745, -5170831, 6906842, -3442874, 13923053, 5056698, 14272344, 2297302, 24147707, 1505741, 23445765, 7170090, -14014582, 6539085, -25379082, 15881906, 9955130, -10830876, -6331971, -9745690, 19301861, -3693027}}, + point_t{{-23568458, 9628844, -26909663, -2205696, 16765570, 12703337, -19733659, -1311798, 6240000, -3186465, -12848561, -9029739, -21391036, 5644922, 29142361, -8070730, 15491500, 5159583, -15039215, -3782415, 17864864, 13111010, -24101940, 9297110, -23296716, 9763494, -33210081, 12312114, -30144102, 10080914, 31682399, 7452145, 19314287, -7581029, -24620989, -3233342, -25926522, -14260964, 24233736, 13681782}} + }, + { + point_t{{19211265, 2757185, 22841428, 4384196, -5344236, -6665958, 13817937, -15401311, 11324591, 13548371, 5920675, -7720845, -26190512, 80906, 11282325, -16150822, 18149158, -15519773, 2721716, -11983296, -14418387, -11153557, 13957160, -3743225, 22506862, -3245854, 4494361, 13510745, -5656988, 5588269, 28354819, -13495704, -13208982, -495328, 17363458, -10857316, -5302126, -7875123, 26008688, -13624830}}, + point_t{{9615635, 11456756, -23929642, 1059944, 1030437, -11895019, 12717341, 14376320, 18760933, -13214228, 28877065, 8204034, -13488237, -4234088, -16433228, 12293567, 22065763, 1258393, 20249733, -7792923, -28493355, -10099755, 9817133, 10497183, 7163689, 3742107, -24288334, 7485474, 17332829, 1167562, -7414283, -2724061, 32933754, 3732809, 18401256, 10890894, -28075941, -4870953, 21081310, 10942194}} + }, + { + point_t{{15697801, -6534008, 10450035, -1554623, -18762845, 11804480, 30164267, 4203906, 28533676, 10647395, 30633476, 9271811, 8721644, -7564181, 5372849, -11987056, 21870223, -770830, -23302500, 5628035, -12397032, 14456892, 14966767, -4384854, 6238573, 5721532, -3482707, 93803, 17582081, -8642913, 10405075, -7178396, -5588624, 15751945, 21474071, 14634321, 2195436, -9319762, -31171521, 3257219}}, + point_t{{-9159868, 13485045, -1816502, 9498441, 12599869, -9143471, -3235081, -7400456, -11042456, 10726811, 30758369, 2034330, -10446636, -12818157, 15789290, 11476501, 12893075, -11738407, 5385991, -11602328, -12578961, -5727994, -3242915, -14123261, 18142422, 9302871, -27881894, 1412251, 13445358, 3794224, 30076600, 12194158, 27928330, -11549668, -21526967, 908741, 7889436, 11312182, 479283, 3079906}} + } +}; + +TEST(crypto, point_eq_op) +{ + for(auto& pp : twin_point_pairs) + { + ASSERT_TRUE((pp.first - pp.second).is_zero()); + ASSERT_TRUE((pp.second - pp.first).is_zero()); + + scalar_t r = scalar_t::random(); + ASSERT_TRUE((r * pp.first - r * pp.second).is_zero()); + ASSERT_TRUE((r * pp.second - r * pp.first).is_zero()); + + ASSERT_TRUE (pp.first == pp.second); + ASSERT_FALSE(pp.first != pp.second); + ASSERT_TRUE (pp.second == pp.first); + ASSERT_FALSE(pp.second != pp.first); + } + + + //// twin points generator + //size_t N = 10000000; + //size_t hits = 0; + //scalar_t r = scalar_t::random(); + //point_t R = r * c_point_G; + //for(size_t i = 0; i < N; ++i) + //{ + // scalar_t s = scalar_t::random(); + // point_t X = (r + s) * c_point_G; + // point_t Y = R + s * c_point_G; + // if (!(X == Y)) + // { + // ++hits; + // std::cout << hits << ENDL + // << " X: " << X << " {" << X.to_comma_separated_int32_str() << "}" << ENDL + // << " Y: " << Y << " {" << Y.to_comma_separated_int32_str() << "}" << ENDL; + // } + //} + //std::cout << ENDL << hits << " hits of " << N << ENDL; + + return true; +} TEST(crypto, sc_get_bit) { @@ -1985,6 +2055,15 @@ TEST(crypto, eth_signature_basics) +//////////////////////////////////////////////////////////////////////////////// +// #include "crypto_tests_ml2s.h" +#include "crypto_tests_range_proofs.h" +#include "crypto_tests_clsag.h" +#include "crypto_tests_one_out_of_many_proofs.h" +#include "crypto_tests_performance.h" +//////////////////////////////////////////////////////////////////////////////// + + // // test's runner diff --git a/tests/functional_tests/crypto_tests_performance.h b/tests/functional_tests/crypto_tests_performance.h index ce539480..0996add2 100644 --- a/tests/functional_tests/crypto_tests_performance.h +++ b/tests/functional_tests/crypto_tests_performance.h @@ -1,6 +1,8 @@ -// Copyright (c) 2021 Zano Project +// Copyright (c) 2021-2024 Zano Project +// Copyright (c) 2021-2024 sowle (val@zano.org, crypto.sowle@gmail.com) // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +// #pragma once #include @@ -1233,3 +1235,106 @@ TEST(perf, generators) return true; } + +bool old_point_eq_operator_bugous(const point_t& lhs, const point_t& rhs) +{ + // convert to xy form, then compare components (because (x, y, z, t) representation is not unique) + fe lrecip, lx, ly; + fe rrecip, rx, ry; + + fe_invert(lrecip, lhs.m_p3.Z); + fe_invert(rrecip, rhs.m_p3.Z); + + fe_mul(lx, lhs.m_p3.X, lrecip); + fe_mul(rx, rhs.m_p3.X, rrecip); + if (memcmp(&lx, &rx, sizeof lx) != 0) + return false; + + fe_mul(ly, lhs.m_p3.Y, lrecip); + fe_mul(ry, rhs.m_p3.Y, rrecip); + if (memcmp(&ly, &ry, sizeof ly) != 0) + return false; + + return true; +} + +bool old_point_eq_operator(const point_t& lhs, const point_t& rhs) +{ + // convert to xy form, then compare components (because (x, y, z, t) representation is not unique) + fe lrecip, lx, ly, dx; + fe rrecip, rx, ry, dy; + + fe_invert(lrecip, lhs.m_p3.Z); + fe_invert(rrecip, rhs.m_p3.Z); + + fe_mul(lx, lhs.m_p3.X, lrecip); + fe_mul(rx, rhs.m_p3.X, rrecip); + fe_sub(dx, lx, rx); + if (fe_isnonzero(dx) != 0) + return false; + + fe_mul(ly, lhs.m_p3.Y, lrecip); + fe_mul(ry, rhs.m_p3.Y, rrecip); + fe_sub(dy, ly, ry); + if (fe_isnonzero(dy) != 0) + return false; + + return true; +} + + +TEST(perf, point_eq_vs_iszero) +{ + const size_t warmup_rounds = 20; + const size_t rounds = 200; + const size_t inner_rounds = 64; + std::vector timings1, timings2; + + size_t N = inner_rounds - twin_point_pairs.size() * 2; // number of random points + scalar_vec_t scalars; + scalars.resize_and_make_random(N); + std::vector points(N); + std::transform(scalars.cbegin(), scalars.cend(), points.begin(), [](const scalar_t& s){ return s * c_point_G; }); + + // add twin points + for(auto& p : twin_point_pairs) + points.push_back(p.first), points.push_back(p.second); + + ASSERT_EQ(points.size(), inner_rounds); + + // and shuffle + std::shuffle(points.begin(), points.end(), crypto::uniform_random_bit_generator{}); + + for(size_t i = 0; i < rounds + warmup_rounds; ++i) + { + std::vector results1(inner_rounds), results2(inner_rounds); + + TIME_MEASURE_START(t1); + for(size_t j = 0; j < inner_rounds; ++j) + for(size_t k = j + 1; k < inner_rounds; ++k) + results1[j] ^= uint8_t(old_point_eq_operator(points[j], points[k]) ? j : k); + TIME_MEASURE_FINISH(t1); + uint64_t h1 = hash_64(results1.data(), results1.size() * sizeof(results1[0])); + + TIME_MEASURE_START(t2); + for(size_t j = 0; j < inner_rounds; ++j) + for(size_t k = j + 1; k < inner_rounds; ++k) + results2[j] ^= uint8_t((points[j] - points[k]).is_zero() ? j : k); + TIME_MEASURE_FINISH(t2); + uint64_t h2 = hash_64(results2.data(), results2.size() * sizeof(results2[0])); + + ASSERT_EQ(h1, h2); + + if (i >= warmup_rounds) + { + timings1.push_back(t1); + timings2.push_back(t2); + } + } + + std::cout << "After " << rounds << " rounds:" << ENDL << + "point_t operator== : " << epee::misc_utils::median(timings1) << " mcs" << ENDL << + "point_t is_zero() : " << epee::misc_utils::median(timings2) << " mcs" << ENDL; + + return true; +} From d06ba3112527dbe0b0c870c87e9ca5486503be67 Mon Sep 17 00:00:00 2001 From: zano build machine Date: Sat, 12 Oct 2024 04:02:03 +0300 Subject: [PATCH 015/106] === build number: 350 -> 351 === --- src/version.h.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h.in b/src/version.h.in index f2864e10..703cc37b 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -8,6 +8,6 @@ #define PROJECT_REVISION "2" #define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION -#define PROJECT_VERSION_BUILD_NO 350 +#define PROJECT_VERSION_BUILD_NO 351 #define PROJECT_VERSION_BUILD_NO_STR STRINGIFY_EXPAND(PROJECT_VERSION_BUILD_NO) #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO_STR "[" BUILD_COMMIT_ID "]" From c344739de9e9320da17fe6a9be1347afc8eeb830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=D1=91pa=20Dolgorukov?= Date: Mon, 14 Oct 2024 23:41:50 +0500 Subject: [PATCH 016/106] unit_tests: implement the "p2p_client_version.test0" (#466) --- tests/unit_tests/p2p_client_version.cpp | 186 ++++++++++++++++++++++-- 1 file changed, 177 insertions(+), 9 deletions(-) diff --git a/tests/unit_tests/p2p_client_version.cpp b/tests/unit_tests/p2p_client_version.cpp index e89817b0..398cb623 100644 --- a/tests/unit_tests/p2p_client_version.cpp +++ b/tests/unit_tests/p2p_client_version.cpp @@ -5,21 +5,189 @@ #include "gtest/gtest.h" #include "common/util.h" -bool check_parse_client_version(const std::string& str, int expected_major, int expected_minor, int expected_revision, int expected_build_number, const std::string& expected_commit_id, bool expected_dirty) +enum class reponse_check_parse_client_version : uint8_t { - int major = -1, minor = -1, revision = -1, build_number = -1; - std::string commit_id; - bool dirty = false; - if (!tools::parse_client_version(str, major, minor, revision, build_number, commit_id, dirty)) - return false; + parsed, + not_parsed, + parsed_unexpect +}; - return major == expected_major && minor == expected_minor && revision == expected_revision && build_number == expected_build_number && commit_id == expected_commit_id && dirty == expected_dirty; +reponse_check_parse_client_version check_parse_client_version(const std::string& str, const std::optional& expected_major, const std::optional& expected_minor, + const std::optional& expected_revision, const std::optional& expected_build_number, + const std::optional& expected_commit_id, const std::optional& expected_dirty) +{ + enum class version_integer_component : uint8_t { major, minor, revision, build_number }; + // 3 not in {0; 1} and low-order bit not equsl to 0. + constexpr uint8_t out_of_logicals_value{3}; + std::array out_of_int32_bounds_values{}; + + { + // (1 ** 32) > INT32_MAX + constexpr auto out_of_int32_bounds_value{static_cast(1) << 32}; + + if (expected_major.has_value() && expected_major.value() == 0) + { + ++out_of_int32_bounds_values.at(static_cast(version_integer_component::major)); + } + + if (expected_minor.has_value() && expected_minor.value() == 0) + { + ++out_of_int32_bounds_values.at(static_cast(version_integer_component::minor)); + } + + if (expected_revision.has_value() && expected_revision.value() == 0) + { + ++out_of_int32_bounds_values.at(static_cast(version_integer_component::revision)); + } + + if (expected_build_number.has_value() && expected_build_number.value() == 0) + { + ++out_of_int32_bounds_values.at(static_cast(version_integer_component::build_number)); + } + } + + int64_t major_pass{out_of_int32_bounds_values.at(static_cast(version_integer_component::major))}, + minor_pass{out_of_int32_bounds_values.at(static_cast(version_integer_component::minor))}, + revision_pass{out_of_int32_bounds_values.at(static_cast(version_integer_component::revision))}, + build_number_pass{out_of_int32_bounds_values.at(static_cast(version_integer_component::build_number))}; + std::string commit_id{}; + uint8_t dirty_pass{out_of_logicals_value}; + + if (!tools::parse_client_version(str, reinterpret_cast(major_pass), reinterpret_cast(minor_pass), reinterpret_cast(revision_pass), + reinterpret_cast(build_number_pass), commit_id, reinterpret_cast(dirty_pass))) + { + return reponse_check_parse_client_version::not_parsed; + } + + constexpr uint64_t mask_to_fit_value_int32{0x00000000FFFFFFFF}; + const auto major{static_cast(major_pass & mask_to_fit_value_int32)}; + const auto minor{static_cast(minor_pass & mask_to_fit_value_int32)}; + const auto revision{static_cast(revision_pass & mask_to_fit_value_int32)}; + const auto build_number{static_cast(build_number_pass & mask_to_fit_value_int32)}; + const bool dirty{dirty_pass != 2 && dirty_pass != out_of_logicals_value}; + + if (expected_major.has_value()) + { + if (major_pass == out_of_int32_bounds_values.at(static_cast(version_integer_component::major)) || major != expected_major.value()) + { + return reponse_check_parse_client_version::parsed_unexpect; + } + } + + else + { + if (major_pass != out_of_int32_bounds_values.at(static_cast(version_integer_component::major))) + { + return reponse_check_parse_client_version::parsed_unexpect; + } + } + + if (expected_minor.has_value()) + { + if (minor_pass == out_of_int32_bounds_values.at(static_cast(version_integer_component::minor)) || minor != expected_minor.value()) + { + return reponse_check_parse_client_version::parsed_unexpect; + } + } + + else + { + if (minor_pass != out_of_int32_bounds_values.at(static_cast(version_integer_component::minor))) + { + return reponse_check_parse_client_version::parsed_unexpect; + } + } + + if (expected_revision.has_value()) + { + if (revision_pass == out_of_int32_bounds_values.at(static_cast(version_integer_component::revision)) || revision != expected_revision.value()) + { + return reponse_check_parse_client_version::parsed_unexpect; + } + } + + else + { + if (revision_pass != out_of_int32_bounds_values.at(static_cast(version_integer_component::revision))) + { + return reponse_check_parse_client_version::parsed_unexpect; + } + } + + if (expected_commit_id.has_value()) + { + if (commit_id != expected_commit_id.value()) + { + return reponse_check_parse_client_version::parsed_unexpect; + } + } + + else + { + if (!commit_id.empty()) + { + return reponse_check_parse_client_version::parsed_unexpect; + } + } + + if (expected_dirty.has_value()) + { + if (dirty != expected_dirty.value()) + { + return reponse_check_parse_client_version::parsed_unexpect; + } + } + + else + { + if (dirty_pass != out_of_logicals_value) + { + return reponse_check_parse_client_version::parsed_unexpect; + } + } + + return reponse_check_parse_client_version::parsed; } - TEST(p2p_client_version, test_0) { - ASSERT_TRUE(check_parse_client_version("10.101.999.28391[deadbeef31337-dirty]", 10, 101, 999, 28391, "deadbeef31337", true)); + ASSERT_EQ(check_parse_client_version("10.101.999.28391[deadbeef31337-dirty]", 10, 101, 999, 28391, "deadbeef31337", true), reponse_check_parse_client_version::parsed); + ASSERT_EQ(check_parse_client_version("+67.+43.+50.+83", 67, 43, 50, 83, "", false), reponse_check_parse_client_version::parsed); + ASSERT_EQ(check_parse_client_version("-12.-90.17.-95", -12, -90, 17, -95, "", false), reponse_check_parse_client_version::parsed); + ASSERT_EQ(check_parse_client_version("54.-100.-76.21[]", 54, -100, -76, 21, "", false), reponse_check_parse_client_version::parsed); + ASSERT_EQ(check_parse_client_version("-93.8.-81.75[-dirty]", -93, 8, -81, 75, "", true), reponse_check_parse_client_version::parsed); + ASSERT_EQ(check_parse_client_version("-8.-85.79.24[--dirty]", -8, -85, 79, 24, "-", true), reponse_check_parse_client_version::parsed); + ASSERT_EQ(check_parse_client_version("-62.53.79.80[\\]", -62, 53, 79, 80, "\\", false), reponse_check_parse_client_version::parsed); + ASSERT_EQ(check_parse_client_version("-27.91.-12.34[-]", -27, 91, -12, 34, "-", false), reponse_check_parse_client_version::parsed); + ASSERT_EQ(check_parse_client_version("-51.-66.-10.58\0[--dirty]", -51, -66, -10, 58, "", false), reponse_check_parse_client_version::parsed); + ASSERT_EQ(check_parse_client_version("-24.27.-81.79[" "\0" "-dirty]", {}, {}, {}, {}, {}, {}), reponse_check_parse_client_version::not_parsed); + ASSERT_EQ(check_parse_client_version("0.0.0.0", 0, 0, 0, 0, "", false), reponse_check_parse_client_version::parsed); + ASSERT_EQ(check_parse_client_version("27 . 33 . -59 . 47", 27, 33, -59, 47, "", false), reponse_check_parse_client_version::parsed); + ASSERT_EQ(check_parse_client_version("-2147483648.-2147483648.-2147483648.-2147483648", INT32_MIN, INT32_MIN, INT32_MIN, INT32_MIN, "", false), reponse_check_parse_client_version::parsed); + ASSERT_EQ(check_parse_client_version("2147483647.2147483647.2147483647.2147483647", INT32_MAX, INT32_MAX, INT32_MAX, INT32_MAX, "", false), reponse_check_parse_client_version::parsed); + ASSERT_EQ(check_parse_client_version("2147483648.2147483648.2147483648.2147483648", INT32_MAX, INT32_MAX, INT32_MAX, INT32_MAX, "", false), reponse_check_parse_client_version::parsed); + ASSERT_EQ(check_parse_client_version("-2147483649.-2147483649.-2147483649.-2147483649", INT32_MIN, INT32_MIN, INT32_MIN, INT32_MIN, "", false), reponse_check_parse_client_version::parsed); + ASSERT_EQ(check_parse_client_version("0098.+0096.0081.-0056", 98, 96, 81, -56, "", false), reponse_check_parse_client_version::parsed); + ASSERT_EQ(check_parse_client_version("\0" "38.67.31.-24", 38, 67, 31, -24, "", false), reponse_check_parse_client_version::not_parsed); + ASSERT_EQ(check_parse_client_version({'-', '6', '8', '.', '\0', '2', '9', '.', '5', '9', '.', '-', '7', '9'}, {}, {}, {}, {}, {}, {}), reponse_check_parse_client_version::not_parsed); + ASSERT_EQ(check_parse_client_version("....", {}, {}, {}, {}, {}, {}), reponse_check_parse_client_version::not_parsed); + ASSERT_EQ(check_parse_client_version("54.12.-10", {}, {}, {}, {}, {}, {}), reponse_check_parse_client_version::not_parsed); + ASSERT_EQ(check_parse_client_version("-.-.-.-", {}, {}, {}, {}, {}, {}), reponse_check_parse_client_version::not_parsed); + ASSERT_EQ(check_parse_client_version(" . . . ", {}, {}, {}, {}, {}, {}), reponse_check_parse_client_version::not_parsed); + + ASSERT_EQ(check_parse_client_version({'-', '2', '3', '.', '6', '.', '-', '1', '8', '.', '-', '1', '1', '[', '\0', ']'}, -23, 6, -18, -11, std::string{'\0'}, false), + reponse_check_parse_client_version::parsed); + + ASSERT_EQ(check_parse_client_version({'9', '8', '.', '3', '.', '8', '9', '.', '-', '1', '[', '\0', '-', 'd', 'i','r', 't', 'y', ']'}, 98, 3, 89, -1, std::string{'\0'}, true), + reponse_check_parse_client_version::parsed); + + //ASSERT_EQ(check_parse_client_version("5.42.25.-42[].", 5, 42, 25, -42, "", false), reponse_check_parse_client_version::parsed); + //ASSERT_EQ(check_parse_client_version("-84.91.-10.1[-dirty].", 5, 42, 25, -42, "", true), reponse_check_parse_client_version::parsed); + //ASSERT_EQ(check_parse_client_version("33.62.-92.-44.", 33, 62, -92, -44, "", false), reponse_check_parse_client_version::parsed); + //ASSERT_EQ(check_parse_client_version("...", {}, {}, {}, {}, {}, {}), reponse_check_parse_client_version::not_parsed); + //ASSERT_EQ(check_parse_client_version("-80.28.-6.1[", -80, 28, -6, 1, "", false), reponse_check_parse_client_version::parsed); + //ASSERT_EQ(check_parse_client_version("-88.-36.11.-25[", -80, 28, -6, 1, "", false), reponse_check_parse_client_version::parsed); + //ASSERT_EQ(check_parse_client_version("0.0.0.[]", {}, {}, {}, {}, {}, {}), reponse_check_parse_client_version::not_parsed); } From b836742f664b40fc8f95ea7f6246a2d3a07e6141 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=D1=91pa=20Dolgorukov?= Date: Tue, 15 Oct 2024 05:30:05 +0500 Subject: [PATCH 017/106] coretests: improve of the test "tx_pool_semantic_validation" (#465) * Coretests: implement the test "tx_pool_semantic_validation" * Play the test "tx_pool_semantic_validation" on the HF 3 * Delete an extra space after the ":" * Remove an extra adding of objects to the transaction * Create the input objects of the specified type in the "unsupported input type" case * Modify the function bodies "inputs_sum", "outputs_sum" to be short * Remove adding output to the transaction in the cases "inputs amount overflow", "two entries of the same type in extra" * Get rid of use the object "image" of the type key_image in the case "equal key images in inputs" --- tests/core_tests/chaingen_main.cpp | 4 +- tests/core_tests/tx_validation.cpp | 225 ++++++++++++++++++++++++++--- tests/core_tests/tx_validation.h | 2 +- 3 files changed, 212 insertions(+), 19 deletions(-) diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index 9c3edec0..4d3acee1 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -1218,7 +1218,9 @@ int main(int argc, char* argv[]) GENERATE_AND_PLAY(tx_expiration_time_and_chain_switching); GENERATE_AND_PLAY(tx_key_image_pool_conflict); //GENERATE_AND_PLAY_HF(tx_version_against_hardfork, "4-*"); - GENERATE_AND_PLAY_HF(tx_pool_semantic_validation, "4-*"); + /* To execute the check of bare balance (function "check_tx_bare_balance") we need to run the test "tx_pool_semantic_validation" on the HF 3. By default behaviour bare outputs are disallowed on + the heights >= 10. */ + GENERATE_AND_PLAY_HF(tx_pool_semantic_validation, "3"); // Double spend GENERATE_AND_PLAY(gen_double_spend_in_tx); diff --git a/tests/core_tests/tx_validation.cpp b/tests/core_tests/tx_validation.cpp index f82c3a10..ea5f6b38 100644 --- a/tests/core_tests/tx_validation.cpp +++ b/tests/core_tests/tx_validation.cpp @@ -1728,7 +1728,7 @@ bool tx_pool_semantic_validation::generate(std::vector& events { transaction tx{}; - tx.vin.emplace_back(); + tx.vin.emplace_back(txin_gen{}); CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false); DO_CALLBACK(events, "mark_invalid_tx"); ADD_CUSTOM_EVENT(events, tx); @@ -1750,7 +1750,6 @@ bool tx_pool_semantic_validation::generate(std::vector& events point_t point_public_key{}; txout_to_key target{}; std::array inputs{}; - tx_out_bare output{}; transaction tx{}; CHECK_AND_ASSERT_EQ(point_public_key.from_string("499790c3302b9f0514e2db09b390679283d43d971383d33dc24c7991ea4cf6d7"), true); @@ -1763,10 +1762,6 @@ bool tx_pool_semantic_validation::generate(std::vector& events tx.vin.push_back(input); } - output.amount = 1; - output.target = target; - tx.vout.push_back(output); - CHECK_AND_ASSERT_GREATER(inputs.at(0).amount, inputs.at(0).amount + inputs.at(1).amount); CHECK_AND_ASSERT_GREATER(inputs.at(1).amount, inputs.at(0).amount + inputs.at(1).amount); CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false); @@ -1803,41 +1798,38 @@ bool tx_pool_semantic_validation::generate(std::vector& events // Equal key images in inputs. { - tx_out_bare output; + tx_out_bare output{}; transaction tx{}; std::array inputs{}; point_t key_image_point{}; - key_image image{}; output.amount = 1; tx.vout.push_back(output); CHECK_AND_ASSERT_EQ(key_image_point.from_string("8fc7cbfd1054690767d0c20917a68371b34b190aac5997581641f064b93d1b96"), true); - image = key_image_point.to_key_image(); for (int position{}; position < 2; ++position) { - inputs.at(position).k_image = image; - tx.vin.push_back(inputs.at(position)); + auto& input{inputs.at(position)}; + + input.k_image = key_image_point.to_key_image(); + tx.vin.push_back(input); } CHECK_AND_ASSERT_EQ(tx.vin.at(0).type(), typeid(txin_to_key)); CHECK_AND_ASSERT_EQ(tx.vin.at(0).type(), tx.vin.at(1).type()); CHECK_AND_ASSERT_EQ(boost::get(tx.vin.at(0)).k_image, boost::get(tx.vin.at(1)).k_image); CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false); + DO_CALLBACK(events, "mark_invalid_tx"); ADD_CUSTOM_EVENT(events, tx); } // Two entries of the same type in extra. { - tx_out_bare output; transaction tx{}; std::array inputs{}; std::array key_image_points{}; - key_image image{}; - output.amount = 1; - tx.vout.push_back(output); CHECK_AND_ASSERT_EQ(key_image_points.at(0).from_string("de3c22a62f15e6de8abe6b217085b2aead196daf5ddd67d9c4b366330736fbeb"), true); CHECK_AND_ASSERT_EQ(key_image_points.at(1).from_string("9f3eef913921ca35239e696725595e3686bb0d69e3e805791c5aa93d5754aa5c"), true); @@ -1859,8 +1851,8 @@ bool tx_pool_semantic_validation::generate(std::vector& events // tx.version <= TRANSACTION_VERSION_PRE_HF4. Balance check fail: sum of inputs <= sum of outputs. { + tx_out_bare output{}; transaction tx{}; - tx_out_bare output; std::array key_image_points{}; std::array inputs{}; @@ -1879,7 +1871,10 @@ bool tx_pool_semantic_validation::generate(std::vector& events tx.vin.push_back(input); } - CHECK_AND_ASSERT_GREATER(output.amount, std::accumulate(inputs.begin(), inputs.end(), std::uint64_t{}, [](uint64_t sum, const txin_to_key& input) { return sum + input.amount; })); + const uint64_t sum_inputs{std::accumulate(inputs.begin(), inputs.end(), std::uint64_t{}, [](uint64_t sum, const txin_to_key& input) { return sum + input.amount; })}; + + CHECK_AND_ASSERT_LESS(sum_inputs, output.amount); + CHECK_AND_ASSERT_EQ(output.amount - sum_inputs, 1); CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false); DO_CALLBACK(events, "mark_invalid_tx"); ADD_CUSTOM_EVENT(events, tx); @@ -1911,5 +1906,201 @@ bool tx_pool_semantic_validation::generate(std::vector& events ADD_CUSTOM_EVENT(events, tx); } + REWIND_BLOCKS_N(events, blk_0r, blk_0, miner, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); + + // Construct a valid transaction and then modify it so that the transaction is no longer semantically correct. + + // No inputs. + { + MAKE_TX_FEE(events, tx, miner, miner, MK_TEST_COINS(2), TESTS_DEFAULT_FEE, blk_0r); + + CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), true); + tx.vin = {}; + CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false); + DO_CALLBACK(events, "mark_invalid_tx"); + ADD_CUSTOM_EVENT(events, tx); + } + + // Unsupported input type. + { + MAKE_TX_FEE(events, tx, miner, miner, MK_TEST_COINS(2), TESTS_DEFAULT_FEE, blk_0r); + + CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), true); + tx.vin.emplace_back(txin_gen{}); + CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false); + DO_CALLBACK(events, "mark_invalid_tx"); + ADD_CUSTOM_EVENT(events, tx); + } + + // Unsupported output type. + { + MAKE_TX_FEE(events, tx, miner, miner, MK_TEST_COINS(2), TESTS_DEFAULT_FEE, blk_0r); + + CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), true); + tx.vout.emplace_back(); + CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false); + DO_CALLBACK(events, "mark_invalid_tx"); + ADD_CUSTOM_EVENT(events, tx); + } + + // Inputs amount overflow. + { + point_t point_public_key{}; + txout_to_key target{}; + std::array inputs{}; + MAKE_TX_FEE(events, tx, miner, miner, MK_TEST_COINS(2), TESTS_DEFAULT_FEE, blk_0r); + + CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), true); + CHECK_AND_ASSERT_EQ(point_public_key.from_string("499790c3302b9f0514e2db09b390679283d43d971383d33dc24c7991ea4cf6d7"), true); + target.key = point_public_key.to_public_key(); + inputs.at(0).amount = 1; + inputs.at(1).amount = UINT64_MAX; + + for (const auto& input : inputs) + { + tx.vin.push_back(input); + } + + CHECK_AND_ASSERT_GREATER(inputs.at(0).amount, inputs.at(0).amount + inputs.at(1).amount); + CHECK_AND_ASSERT_GREATER(inputs.at(1).amount, inputs.at(0).amount + inputs.at(1).amount); + CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false); + DO_CALLBACK(events, "mark_invalid_tx"); + ADD_CUSTOM_EVENT(events, tx); + } + + // Outputs amount overflow. + { + point_t point_public_key{}; + txout_to_key target{}; + std::array outputs{}; + MAKE_TX_FEE(events, tx, miner, miner, MK_TEST_COINS(2), TESTS_DEFAULT_FEE, blk_0r); + + CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), true); + CHECK_AND_ASSERT_EQ(point_public_key.from_string("78ef3d9af7b5e3d09556d57820cf68c2b3553a9d8205c01fe40fc70aae86bb4f"), true); + target.key = point_public_key.to_public_key(); + outputs.at(0).amount = 1; + outputs.at(1).amount = UINT64_MAX; + + for (auto& output : outputs) + { + output.target = target; + tx.vout.push_back(output); + } + + tx.vin.push_back(txin_to_key{}); + + CHECK_AND_ASSERT_GREATER(outputs.at(0).amount, outputs.at(0).amount + outputs.at(1).amount); + CHECK_AND_ASSERT_GREATER(outputs.at(1).amount, outputs.at(0).amount + outputs.at(1).amount); + CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false); + DO_CALLBACK(events, "mark_invalid_tx"); + ADD_CUSTOM_EVENT(events, tx); + } + + // Equal key images in inputs. + { + tx_out_bare output{}; + std::array inputs{}; + point_t key_image_point{}; + MAKE_TX_FEE(events, tx, miner, miner, MK_TEST_COINS(2), TESTS_DEFAULT_FEE, blk_0r); + + CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), true); + output.amount = 1; + tx.vout.push_back(output); + CHECK_AND_ASSERT_EQ(key_image_point.from_string("8fc7cbfd1054690767d0c20917a68371b34b190aac5997581641f064b93d1b96"), true); + + for (int position{}; position < 2; ++position) + { + inputs.at(position).k_image = key_image_point.to_key_image(); + tx.vin.push_back(inputs.at(position)); + } + + { + const auto& input_preceding_last{tx.vin.at(tx.vin.size() - 2u)}; + + CHECK_AND_ASSERT_EQ(tx.vin.back().type(), typeid(txin_to_key)); + CHECK_AND_ASSERT_EQ(tx.vin.back().type(), input_preceding_last.type()); + CHECK_AND_ASSERT_EQ(boost::get(tx.vin.back()).k_image, boost::get(input_preceding_last).k_image); + } + + CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false); + DO_CALLBACK(events, "mark_invalid_tx"); + ADD_CUSTOM_EVENT(events, tx); + } + + // Two entries of the same type in extra. + { + MAKE_TX_FEE(events, tx, miner, miner, MK_TEST_COINS(2), TESTS_DEFAULT_FEE, blk_0r); + + CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), true); + tx.extra.push_back(null_pkey); + tx.extra.push_back(null_pkey); + + CHECK_AND_ASSERT_GREATER(tx.extra.size(), 2); + CHECK_AND_ASSERT_EQ(typeid(tx.extra.back()), typeid(tx.extra.at(tx.extra.size() - 2))); + CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false); + DO_CALLBACK(events, "mark_invalid_tx"); + ADD_CUSTOM_EVENT(events, tx); + } + + // tx.version <= TRANSACTION_VERSION_PRE_HF4. Balance check fail: sum of inputs <= sum of outputs. + { + tx_out_bare output{}; + std::array key_image_points{}; + std::array inputs{}; + MAKE_TX_FEE(events, tx, miner, miner, MK_TEST_COINS(1), TESTS_DEFAULT_FEE, blk_0r); + + CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), true); + output.amount = 10'000'000'003; + tx.vout.push_back(output); + tx.version = 0; + CHECK_AND_ASSERT_EQ(key_image_points.at(0).from_string("8fc7cbfd1054690767d0c20917a68371b34b190aac5997581641f064b93d1b96"), true); + CHECK_AND_ASSERT_EQ(key_image_points.at(1).from_string("dc48b741dacda5ac026ad0a7d193b816049eb08724907a1ff6f95839cfb0efa5"), true); + + for (int position{}; position < 2; ++position) + { + auto& input{inputs.at(position)}; + + input.amount = 1; + input.k_image = key_image_points.at(position).to_key_image(); + tx.vin.push_back(input); + } + + const auto inputs_sum{[](const uint64_t sum, const txin_v& input) -> uint64_t + { + if (input.type() == typeid(txin_to_key)) + { + return sum + boost::get(input).amount; + } + + if (input.type() == typeid(txin_multisig)) + { + return sum + boost::get(input).amount; + } + } + }; + + const auto outputs_sum{[](const uint64_t sum, const tx_out_v& output) -> uint64_t + { + if (output.type() == typeid(tx_out_bare)) + { + return sum + boost::get(output).amount; + } + } + }; + + const uint64_t inputs_amount{std::accumulate(tx.vin.begin(), tx.vin.end(), std::uint64_t{}, inputs_sum)}; + const uint64_t outputs_amount{std::accumulate(tx.vout.begin(), tx.vout.end(), std::uint64_t{}, outputs_sum)}; + + CHECK_AND_ASSERT_LESS(inputs_amount, outputs_amount); + CHECK_AND_ASSERT_EQ(outputs_amount - inputs_amount, 1); + CHECK_AND_ASSERT(tx.version <= TRANSACTION_VERSION_PRE_HF4, false); + CHECK_AND_ASSERT_EQ(get_block_height(blk_0r), 10); + CHECK_AND_ASSERT_EQ(m_hardforks.is_hardfork_active_for_height(ZANO_HARDFORK_03, 10), true); + CHECK_AND_ASSERT_EQ(m_hardforks.is_hardfork_active_for_height(ZANO_HARDFORK_04_ZARCANUM, 10), false); + CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false); + DO_CALLBACK(events, "mark_invalid_tx"); + ADD_CUSTOM_EVENT(events, tx); + } + return true; } diff --git a/tests/core_tests/tx_validation.h b/tests/core_tests/tx_validation.h index 5a8f81cd..9888ee3e 100644 --- a/tests/core_tests/tx_validation.h +++ b/tests/core_tests/tx_validation.h @@ -161,7 +161,7 @@ struct tx_version_against_hardfork : public test_chain_unit_enchanced bool generate(std::vector& events) const; }; -struct tx_pool_semantic_validation : public test_chain_unit_enchanced +struct tx_pool_semantic_validation : public test_chain_unit_enchanced { bool generate(std::vector& events) const; }; From ca51bd4886f18deb289f797c666df49ec06eb5df Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Tue, 15 Oct 2024 17:54:50 +0400 Subject: [PATCH 018/106] fixing RPC API for burn --- src/wallet/wallet2.cpp | 22 +++++++++++++++++++--- src/wallet/wallet2.h | 6 +++--- src/wallet/wallet_public_structs_defs.h | 13 ++++++++++--- src/wallet/wallet_rpc_server.cpp | 2 +- 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index f7078763..3d1a1fc8 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -5543,7 +5543,7 @@ void wallet2::transfer_asset_ownership(const crypto::public_key& asset_id, const result_tx = ft.tx; } //---------------------------------------------------------------------------------------------------- -void wallet2::burn_asset(const crypto::public_key& asset_id, uint64_t amount_to_burn, currency::finalized_tx& ft) +void wallet2::burn_asset(const crypto::public_key& asset_id, uint64_t amount_to_burn, currency::finalized_tx& ft, const std::vector& service_entries, const std::string& address_to_point/* = const std::string()*/, uint64_t native_amount_to_point/* = 0*/) { currency::asset_descriptor_base last_adb{}; bool r = this->daemon_get_asset_info(asset_id, last_adb); @@ -5565,14 +5565,30 @@ void wallet2::burn_asset(const crypto::public_key& asset_id, uint64_t amount_to_ ctp.need_at_least_1_zc = true; ctp.dsts.push_back(dst_to_burn); ctp.tx_meaning_for_logs = "asset burn"; + if (service_entries.size()) + { + //put it to extra + ctp.extra.insert(ctp.extra.end(), service_entries.begin(), service_entries.end()); + } + if (address_to_point.size()) + { + currency::account_public_address addr = AUTO_VAL_INIT(addr); + bool r = currency::get_account_address_from_str(addr, address_to_point); + CHECK_AND_ASSERT_THROW_MES(r, "WRONG_ADDRESS"); + currency::tx_destination_entry dst_to_point = AUTO_VAL_INIT(dst_to_point); + dst_to_point.asset_id = native_coin_asset_id; + dst_to_point.amount = native_amount_to_point; + dst_to_point.addr.push_back(addr); + ctp.dsts.push_back(dst_to_point); + } this->transfer(ctp, ft, true, nullptr); } //---------------------------------------------------------------------------------------------------- -void wallet2::burn_asset(const crypto::public_key& asset_id, uint64_t amount_to_burn, currency::transaction& result_tx) +void wallet2::burn_asset(const crypto::public_key& asset_id, uint64_t amount_to_burn, currency::transaction& result_tx, const std::vector& service_entries, const std::string& address_to_point/* = const std::string()*/, uint64_t native_amount_to_point /*= 0*/) { finalized_tx ft{}; - burn_asset(asset_id, amount_to_burn, ft); + burn_asset(asset_id, amount_to_burn, ft, service_entries, address_to_point, native_amount_to_point); result_tx = ft.tx; } //---------------------------------------------------------------------------------------------------- diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index c454480a..d8fd661f 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -422,13 +422,13 @@ namespace tools void deploy_new_asset(const currency::asset_descriptor_base& asset_info, const std::vector& destinations, currency::transaction& result_tx, crypto::public_key& new_asset_id); void emit_asset(const crypto::public_key& asset_id, std::vector& destinations, currency::transaction& result_tx); void update_asset(const crypto::public_key& asset_id, const currency::asset_descriptor_base new_descriptor, currency::transaction& result_tx); - void burn_asset(const crypto::public_key& asset_id, uint64_t amount_to_burn, currency::transaction& result_tx); + void burn_asset(const crypto::public_key& asset_id, uint64_t amount_to_burn, currency::transaction& result_tx, const std::vector& service_entries = std::vector(), const std::string& address_to_point = std::string(), uint64_t native_amount_to_point = 0); void transfer_asset_ownership(const crypto::public_key& asset_id, const currency::asset_owner_pub_key_v& new_owner_v, currency::transaction& result_tx); void deploy_new_asset(const currency::asset_descriptor_base& asset_info, const std::vector& destinations, currency::finalized_tx& ft, crypto::public_key& new_asset_id); void emit_asset(const crypto::public_key& asset_id, const std::vector& destinations, currency::finalized_tx& ft); void update_asset(const crypto::public_key& asset_id, const currency::asset_descriptor_base& new_descriptor, currency::finalized_tx& ft); - void burn_asset(const crypto::public_key& asset_id, uint64_t amount_to_burn, currency::finalized_tx& ft); + void burn_asset(const crypto::public_key& asset_id, uint64_t amount_to_burn, currency::finalized_tx& ft, const std::vector& service_entries = std::vector(), const std::string& address_to_point = std::string(), uint64_t native_amount_to_point = 0); void transfer_asset_ownership(const crypto::public_key& asset_id, const currency::asset_owner_pub_key_v& new_owner_v, currency::finalized_tx& ft); bool daemon_get_asset_info(const crypto::public_key& asset_id, currency::asset_descriptor_base& adb); @@ -960,7 +960,7 @@ private: uint64_t m_last_known_daemon_height = 0; - //this needed to access wallets state in coretests, for creating abnormal blocks and tranmsactions + //this needed to access wallets state in coretests, for creating abnormal blocks and transactions friend class test_generator; }; // class wallet2 diff --git a/src/wallet/wallet_public_structs_defs.h b/src/wallet/wallet_public_structs_defs.h index b6d78d46..ba276a0a 100644 --- a/src/wallet/wallet_public_structs_defs.h +++ b/src/wallet/wallet_public_structs_defs.h @@ -153,8 +153,8 @@ namespace wallet_public std::vector service_entries; std::vector remote_addresses; //optional std::vector remote_aliases; //optional, describe only if there only one remote address - std::vector subtransfers; + boost::optional data_for_external_signing; //not included in streaming serialization uint64_t fee = 0; @@ -2087,12 +2087,19 @@ namespace wallet_public struct request { - crypto::public_key asset_id; - uint64_t burn_amount; + crypto::public_key asset_id = currency::null_pkey; + uint64_t burn_amount = 0; + //optional params + std::string point_tx_to_address; + uint64_t native_amount = 0; + std::vector service_entries; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_POD_AS_HEX_STRING(asset_id) DOC_DSCR("Id of the asset to burn") DOC_EXMP("40fa6db923728b38962718c61b4dc3af1acaa1967479c73703e260dc3609c58d") DOC_END KV_SERIALIZE(burn_amount) DOC_DSCR("Amount to burn") DOC_EXMP(10000000) DOC_END + KV_SERIALIZE(point_tx_to_address) DOC_DSCR("Optional, if we need this transaction to be seen by particular wallet") DOC_EXMP("ZxBvJDuQjMG9R2j4WnYUhBYNrwZPwuyXrC7FHdVmWqaESgowDvgfWtiXeNGu8Px9B24pkmjsA39fzSSiEQG1ekB225ZnrMTBp") DOC_END + KV_SERIALIZE(native_amount) DOC_DSCR("Optional, if we need this transaction to be seen by particular wallet") DOC_EXMP(0) DOC_END + KV_SERIALIZE(service_entries) DOC_DSCR("Optional, if we need to include service entries for burn transaction") DOC_EXMP_AUTO(1) DOC_END END_KV_SERIALIZE_MAP() }; diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 069e6856..305bd6d4 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -1388,7 +1388,7 @@ namespace tools WALLET_RPC_BEGIN_TRY_ENTRY(); currency::finalized_tx ft{}; - w.get_wallet()->burn_asset(req.asset_id, req.burn_amount, ft); + w.get_wallet()->burn_asset(req.asset_id, req.burn_amount, ft, req.service_entries, req.point_tx_to_address, req.native_amount); res.tx_id = ft.tx_id; return true; From 40539c1f9d932d0ca77ffce98efb44999b8c539f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=D1=91pa=20Dolgorukov?= Date: Thu, 17 Oct 2024 20:58:48 +0500 Subject: [PATCH 019/106] coretests: edit the test "asset_current_and_total_supplies_comparative constraints" (#469) * Coretests: implement the test "asset_current_and_total_supplies_comparative_constraints" * Core tests: correct the test "asset_current_and_total_supplies_comparative_constraints" * Add a return statement to the body end of the "asset_current_and_total_supplies_comparative_constraints::assert_asset_beta_registered" function --- tests/core_tests/multiassets_test.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/core_tests/multiassets_test.cpp b/tests/core_tests/multiassets_test.cpp index 670787af..c5a4948a 100644 --- a/tests/core_tests/multiassets_test.cpp +++ b/tests/core_tests/multiassets_test.cpp @@ -2264,6 +2264,8 @@ bool asset_current_and_total_supplies_comparative_constraints::assert_asset_beta CHECK_AND_ASSERT_MES(alice_wallet->balance(beta_asset_id) == current_supply, false, "Alice has got not exactly " + std::to_string(current_supply) + ' ' + register_ado.descriptor.ticker); } + + return true; } bool asset_current_and_total_supplies_comparative_constraints::public_burn_asset_beta_with_incorrect_supply(currency::core& c, size_t ev_index, const std::vector& events) const From b2e9872645ff321158135c0e7c090b5bf4841b70 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Fri, 18 Oct 2024 00:26:30 +0400 Subject: [PATCH 020/106] implemented proper versioning in serialization asset_descriptor_* structs + serialization refactoring + boost serialization maps versioning self-vaidation --- src/common/boost_serialization_maps.h | 53 +++++++++ src/currency_core/currency_basic.h | 108 +++++++++++++----- .../currency_basic_backward_comp.inl | 71 ++++++++++++ .../currency_boost_serialization.h | 11 +- src/currency_core/currency_format_utils.h | 8 +- src/currency_core/miner.cpp | 8 ++ 6 files changed, 221 insertions(+), 38 deletions(-) diff --git a/src/common/boost_serialization_maps.h b/src/common/boost_serialization_maps.h index 5fbecea0..2bd9fda3 100644 --- a/src/common/boost_serialization_maps.h +++ b/src/common/boost_serialization_maps.h @@ -3,6 +3,8 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #pragma once +#include +#include #define BEGIN_BOOST_SERIALIZATION() template void serialize(t_archive &_arch, const unsigned int ver) { @@ -26,6 +28,57 @@ template struct TAssertEquality { #define END_BOOST_SERIALIZATION() } + +/********************************************************************************************************************************** + This serialization closing macro adds self-validation by checking the total number of fields in the structure using boost::pfr. + + Note: "num_fields" does NOT represent the number of fields included in the serialization. Instead, it indicates the total number + of fields in the structure, some of which might not be included in the serialization for valid reasons. If someone adds new + fields to the structure but forgets to update the serialization map, the compilation will fail. Any update to "num_fields" must + be accompanied by a thorough review of the serialization map to ensure no fields are omitted. +**********************************************************************************************************************************/ +#define END_BOOST_SERIALIZATION_TOTAL_FIELDS(num_fields) static_assert(num_fields == boost::pfr::tuple_size::type>::value, "Unexpected number of fields!"); } + + +#define BOOST_SERIALIZATION_CURRENT_ARCHIVE_VER(current_version) static const unsigned int current_boost_version_serialization_version = current_version; + +#define LOOP_BACK_BOOST_SERIALIZATION_VERSION(type) BOOST_CLASS_VERSION(type, type::current_boost_version_serialization_version); + + + +template +struct boost_transition_t {}; + +template +struct boost_transition_t +{ + template + static void chain_serialize(archive& ar, const origin_type& origin_tx) + { + destination_t dst_tx = AUTO_VAL_INIT(dst_tx); + transition_convert(origin_tx, dst_tx); + ar & dst_tx; + } +}; + +template +struct boost_transition_t +{ + template + static void chain_serialize(archive& ar, origin_type& origin_tx) + { + // TODO: consider using move semantic for temporary 'dst_tx' + destination_t dst_tx = AUTO_VAL_INIT(dst_tx); + ar & dst_tx; + transition_convert(dst_tx, origin_tx); + } +}; + + +#define BOOST_CHAIN_TRANSITION_VER(obj_version, old_type) if (obj_version == ver) {boost_transition_t::chain_serialize(_arch, *this);return;} + +#define BOOST_CHAIN_TRANSITION_IF_COND_TRUE(condition, old_type) if (condition) {boost_transition_t::chain_serialize(_arch, *this);return;} + /* example of use: diff --git a/src/currency_core/currency_basic.h b/src/currency_core/currency_basic.h index c6581611..b968e617 100644 --- a/src/currency_core/currency_basic.h +++ b/src/currency_core/currency_basic.h @@ -696,9 +696,17 @@ namespace currency } }; -#define ASSET_DESCRIPTOR_BASE_STRUCTURE_VER 1 +#define ASSET_DESCRIPTOR_BASE_STRUCTURE_LAST_VER 2 - typedef boost::variant asset_owner_pub_key_v; + struct dummy{ + BEGIN_SERIALIZE() + END_SERIALIZE() + + BEGIN_BOOST_SERIALIZATION() + END_BOOST_SERIALIZATION_TOTAL_FIELDS(0) + }; + + typedef boost::variant asset_descriptor_base_etc_fields; struct asset_descriptor_base { @@ -710,11 +718,14 @@ namespace currency std::string meta_info; crypto::public_key owner = currency::null_pkey; // consider premultipling by 1/8 bool hidden_supply = false; + uint8_t version = 0; + //version 1 members boost::optional owner_eth_pub_key; // note: the size is 33 bytes (if present) // NOTE: using boost::optional instead of std::optional because of the Boost compilation issue: https://github.com/boostorg/serialization/issues/319 -- sowle + //version 2 members + std::vector etc; //container for future use if we would be adding some optional parameters that is not known yet, but without mess related to format version - uint8_t version = ASSET_DESCRIPTOR_BASE_STRUCTURE_VER; - BEGIN_VERSIONED_SERIALIZE(ASSET_DESCRIPTOR_BASE_STRUCTURE_VER, version) + BEGIN_VERSIONED_SERIALIZE(ASSET_DESCRIPTOR_BASE_STRUCTURE_LAST_VER, version) FIELD(total_max_supply) FIELD(current_supply) FIELD(decimal_point) @@ -725,8 +736,11 @@ namespace currency FIELD(hidden_supply) END_VERSION_UNDER(1) FIELD(owner_eth_pub_key) + END_VERSION_UNDER(2) + FIELD(etc) END_SERIALIZE() + BOOST_SERIALIZATION_CURRENT_ARCHIVE_VER(2) BEGIN_BOOST_SERIALIZATION() BOOST_SERIALIZE(total_max_supply) BOOST_SERIALIZE(current_supply) @@ -738,7 +752,10 @@ namespace currency BOOST_SERIALIZE(hidden_supply) BOOST_END_VERSION_UNDER(1) BOOST_SERIALIZE(owner_eth_pub_key) - END_BOOST_SERIALIZATION() + BOOST_END_VERSION_UNDER(2) + BOOST_SERIALIZE(etc) + BOOST_SERIALIZE(version) + END_BOOST_SERIALIZATION_TOTAL_FIELDS(11) BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(total_max_supply) DOC_DSCR("Maximum possible supply for a given asset, cannot be changed after deployment.") DOC_EXMP(1000000000000000000) DOC_END @@ -778,38 +795,56 @@ namespace currency #define ASSET_DESCRIPTOR_OPERATION_UPDATE 3 #define ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN 4 -#define ASSET_DESCRIPTOR_OPERATION_STRUCTURE_VER 1 +#define ASSET_DESCRIPTOR_OPERATION_LAST_VER 2 + + typedef boost::variant asset_descriptor_operator_etc_fields; struct asset_descriptor_operation { - uint8_t operation_type = ASSET_DESCRIPTOR_OPERATION_UNDEFINED; - asset_descriptor_base descriptor; - crypto::public_key amount_commitment; // premultiplied by 1/8 - boost::optional opt_asset_id; // target asset_id - for update/emit - uint8_t verion = ASSET_DESCRIPTOR_OPERATION_STRUCTURE_VER; + uint8_t operation_type = ASSET_DESCRIPTOR_OPERATION_UNDEFINED; + uint8_t version = 1; - BEGIN_VERSIONED_SERIALIZE(ASSET_DESCRIPTOR_OPERATION_STRUCTURE_VER, verion) + boost::optional opt_amount_commitment; // premultiplied by 1/8 + boost::optional opt_asset_id; // target asset_id - for update/emit + boost::optional opt_descriptor; //used in deploy/update + boost::optional opt_amount; //used in burn/emit + std::vector etc; //reserved for future use + + + BEGIN_VERSIONED_SERIALIZE(ASSET_DESCRIPTOR_OPERATION_LAST_VER, version) + CHAIN_TRANSITION_VER(0, asset_descriptor_operation_v1) + CHAIN_TRANSITION_VER(1, asset_descriptor_operation_v1) FIELD(operation_type) - FIELD(descriptor) - FIELD(amount_commitment) - END_VERSION_UNDER(1) + FIELD(opt_amount_commitment) FIELD(opt_asset_id) + FIELD(opt_descriptor) + FIELD(opt_amount) + FIELD(etc) END_SERIALIZE() + BOOST_SERIALIZATION_CURRENT_ARCHIVE_VER(2) BEGIN_BOOST_SERIALIZATION() + BOOST_CHAIN_TRANSITION_VER(1, asset_descriptor_operation_v1) + BOOST_CHAIN_TRANSITION_VER(0, asset_descriptor_operation_v1) + BOOST_SERIALIZE(version) BOOST_SERIALIZE(operation_type) - BOOST_SERIALIZE(descriptor) - BOOST_SERIALIZE(amount_commitment) - BOOST_END_VERSION_UNDER(1) + BOOST_SERIALIZE(opt_amount_commitment) BOOST_SERIALIZE(opt_asset_id) - END_BOOST_SERIALIZATION() + BOOST_SERIALIZE(opt_descriptor) + BOOST_SERIALIZE(opt_amount) + BOOST_SERIALIZE(etc) + END_BOOST_SERIALIZATION_TOTAL_FIELDS(7) BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(operation_type) DOC_DSCR("Asset operation type identifier") DOC_EXMP(1) DOC_END - KV_SERIALIZE(descriptor) DOC_DSCR("Asset descriptor") DOC_EXMP_AUTO() DOC_END - KV_SERIALIZE_POD_AS_HEX_STRING(amount_commitment) DOC_DSCR("Amount commitment") DOC_EXMP("f74bb56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END - KV_SERIALIZE_POD_AS_HEX_STRING(opt_asset_id) DOC_DSCR("ID of an asset.") DOC_EXMP("cc4e69455e63f4a581257382191de6856c2156630b3fba0db4bdd73ffcfb36b6") DOC_END + KV_SERIALIZE(version) DOC_DSCR("Asset operation type struct version") DOC_EXMP(2) DOC_END + KV_SERIALIZE(operation_type) DOC_DSCR("Asset operation type identifier") DOC_EXMP(1) DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(opt_amount_commitment) DOC_DSCR("Asset operation amount commitment(optional)") DOC_EXMP("5688b56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(opt_asset_id) DOC_DSCR("ID of an asset.(optional)") DOC_EXMP("cc4e69455e63f4a581257382191de6856c2156630b3fba0db4bdd73ffcfb36b6") DOC_END + KV_SERIALIZE(opt_descriptor) DOC_DSCR("Asset operation amount commitment(optional)") DOC_EXMP_AUTO() DOC_END + KV_SERIALIZE(opt_amount) DOC_DSCR("Asset operation amount(optional, needed only for burn/emit)") DOC_EXMP_AUTO() DOC_END + //KV_SERIALIZE(etc) DOC_DSCR("Extra operations") DOC_EXMP_AUTO() DOC_END <---- serialization for variant not supported yet END_KV_SERIALIZE_MAP() + }; struct asset_operation_proof @@ -824,6 +859,7 @@ namespace currency FIELD(opt_amount_commitment_g_proof) END_SERIALIZE() + BOOST_SERIALIZATION_CURRENT_ARCHIVE_VER(1) BEGIN_BOOST_SERIALIZATION() BOOST_SERIALIZE(opt_amount_commitment_composition_proof) BOOST_SERIALIZE(opt_amount_commitment_g_proof) @@ -842,6 +878,7 @@ namespace currency FIELD(gss) END_SERIALIZE() + BOOST_SERIALIZATION_CURRENT_ARCHIVE_VER(1) BEGIN_BOOST_SERIALIZATION() BOOST_SERIALIZE(gss) BOOST_END_VERSION_UNDER(1) @@ -1023,6 +1060,19 @@ namespace currency FIELD(signatures) FIELD(proofs) END_SERIALIZE() + + + BOOST_SERIALIZATION_CURRENT_ARCHIVE_VER(0) + BEGIN_BOOST_SERIALIZATION() + BOOST_SERIALIZE(version) + BOOST_SERIALIZE(vin) + BOOST_SERIALIZE(vout) + BOOST_SERIALIZE(extra) + BOOST_SERIALIZE(signatures) + BOOST_SERIALIZE(attachment) + BOOST_END_VERSION_UNDER(1) + BOOST_SERIALIZE(proofs) + END_BOOST_SERIALIZATION_TOTAL_FIELDS(4) }; @@ -1158,9 +1208,12 @@ BLOB_SERIALIZER(currency::txout_to_key); VARIANT_TAG(json_archive, type_name, json_tag) -BOOST_CLASS_VERSION(currency::asset_descriptor_operation, 1); -BOOST_CLASS_VERSION(currency::asset_operation_proof, 1); -BOOST_CLASS_VERSION(currency::asset_operation_ownership_proof, 1); + +LOOP_BACK_BOOST_SERIALIZATION_VERSION(currency::asset_descriptor_operation); +LOOP_BACK_BOOST_SERIALIZATION_VERSION(currency::asset_descriptor_base); +LOOP_BACK_BOOST_SERIALIZATION_VERSION(currency::asset_operation_proof); +LOOP_BACK_BOOST_SERIALIZATION_VERSION(currency::asset_operation_ownership_proof); +LOOP_BACK_BOOST_SERIALIZATION_VERSION(currency::transaction); // txin_v variant currency @@ -1235,7 +1288,8 @@ SET_VARIANT_TAGS(currency::asset_operation_ownership_proof, 51, "asset_operation SET_VARIANT_TAGS(currency::asset_operation_ownership_proof_eth, 52, "asset_operation_ownership_proof_eth"); SET_VARIANT_TAGS(crypto::eth_public_key, 60, "eth_public_key"); -//SET_VARIANT_TAGS(crypto::eth_signature, 61, "eth_signature");s +//SET_VARIANT_TAGS(crypto::eth_signature, 61, "eth_signature"); +SET_VARIANT_TAGS(currency::dummy, 62, "dummy"); diff --git a/src/currency_core/currency_basic_backward_comp.inl b/src/currency_core/currency_basic_backward_comp.inl index 7dffea0e..6ce249ee 100644 --- a/src/currency_core/currency_basic_backward_comp.inl +++ b/src/currency_core/currency_basic_backward_comp.inl @@ -116,3 +116,74 @@ bool transition_convert(const transaction_v1& from, transaction_current_t& to) } return true; } + +struct asset_descriptor_operation_v1 +{ + uint8_t operation_type = ASSET_DESCRIPTOR_OPERATION_UNDEFINED; + asset_descriptor_base descriptor; + crypto::public_key amount_commitment; // premultiplied by 1/8 + boost::optional opt_asset_id; // target asset_id - for update/emit + uint8_t verion = 1; + + BEGIN_VERSIONED_SERIALIZE(1, verion) + FIELD(operation_type) + FIELD(descriptor) + FIELD(amount_commitment) + END_VERSION_UNDER(1) + FIELD(opt_asset_id) + END_SERIALIZE() + + + //this map doesn't store internal version member, but it set it by default to val "1", which then transfered via transition_convert() to destination struct + BEGIN_BOOST_SERIALIZATION() + BOOST_SERIALIZE(operation_type) + BOOST_SERIALIZE(descriptor) + BOOST_SERIALIZE(amount_commitment) + BOOST_END_VERSION_UNDER(1) + BOOST_SERIALIZE(opt_asset_id) + END_BOOST_SERIALIZATION() +}; + +template +bool transition_convert(const asset_descriptor_operation_t& from, asset_descriptor_operation_v1& to) +{ + to.verion = from.version; + to.operation_type = from.operation_type; + if(from.opt_descriptor.has_value()) + { + to.descriptor = *from.opt_descriptor; + } + else + { + throw std::runtime_error(std::string("Unexpected: missing descriptor in from transaction_current_t")); + } + + if (from.opt_amount_commitment.has_value()) + { + to.amount_commitment = *from.opt_amount_commitment; + } + else + { + throw std::runtime_error(std::string("Unexpected: missing amount_commitment in from transaction_current_t")); + } + + to.opt_asset_id = from.opt_asset_id; + + if(from.opt_amount.has_value() || from.etc.size()) + { + throw std::runtime_error(std::string("Unexpected: opt_amount or etc have values during convention, looks like object slicing with information getting lost")); + } + + return true; +} + +template +bool transition_convert(const asset_descriptor_operation_v1& from, asset_descriptor_operation_t& to) +{ + to.operation_type = from.operation_type; + to.opt_descriptor = from.descriptor; + to.opt_amount_commitment = from.amount_commitment; + to.opt_asset_id = to.opt_asset_id; // target asset_id - for update/emit + to.version = from.verion; + return true; +} \ No newline at end of file diff --git a/src/currency_core/currency_boost_serialization.h b/src/currency_core/currency_boost_serialization.h index 5d7c40f7..c12caf25 100644 --- a/src/currency_core/currency_boost_serialization.h +++ b/src/currency_core/currency_boost_serialization.h @@ -222,16 +222,7 @@ namespace boost a & x.buff; } - template - inline void serialize(Archive &a, currency::transaction &x, const boost::serialization::version_type ver) - { - a & x.version; - a & x.vin; - a & x.vout; - a & x.extra; - a & x.signatures; - a & x.attachment; - } + template inline void serialize(Archive &a, currency::keypair &kp, const boost::serialization::version_type ver) diff --git a/src/currency_core/currency_format_utils.h b/src/currency_core/currency_format_utils.h index e1899ea1..0928a142 100644 --- a/src/currency_core/currency_format_utils.h +++ b/src/currency_core/currency_format_utils.h @@ -1209,7 +1209,13 @@ namespace currency tv.short_view = std::string("op:") + get_asset_operation_type_string(ado.operation_type, true); if (ado.opt_asset_id.has_value()) tv.short_view += std::string(" , id:") + crypto::pod_to_hex(ado.opt_asset_id); - tv.details_view = tv.short_view + std::string(" , ticker:") + ado.descriptor.ticker + std::string(" , cur.supply:") + print_money_brief(ado.descriptor.current_supply, ado.descriptor.decimal_point); + tv.details_view = tv.short_view; + if (ado.opt_descriptor.has_value()) + { + tv.details_view += std::string(" , ticker:") + ado.opt_descriptor->ticker + std::string(" , cur.supply:") + print_money_brief(ado.opt_descriptor->current_supply, ado.opt_descriptor->decimal_point); + } + //@#@ TODO: add other info from asset_descriptor_operation v2+ + return true; } template diff --git a/src/currency_core/miner.cpp b/src/currency_core/miner.cpp index d52e51e2..8f4f9f82 100644 --- a/src/currency_core/miner.cpp +++ b/src/currency_core/miner.cpp @@ -124,6 +124,14 @@ namespace currency //----------------------------------------------------------------------------------------------------- void miner::do_print_hashrate(bool do_hr) { +#ifdef _DEBUG + currency::asset_descriptor_operation ado; + std::stringstream ss; + bool r = tools::portble_serialize_obj_to_stream(ado, ss); + std::cout << r; +#endif + + m_do_print_hashrate = do_hr; } //----------------------------------------------------------------------------------------------------- From b5a44b1698b67de3a2f5dc479a455238d75c8db4 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Fri, 18 Oct 2024 00:27:18 +0400 Subject: [PATCH 021/106] removed temporary code --- src/currency_core/miner.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/currency_core/miner.cpp b/src/currency_core/miner.cpp index 8f4f9f82..d52e51e2 100644 --- a/src/currency_core/miner.cpp +++ b/src/currency_core/miner.cpp @@ -124,14 +124,6 @@ namespace currency //----------------------------------------------------------------------------------------------------- void miner::do_print_hashrate(bool do_hr) { -#ifdef _DEBUG - currency::asset_descriptor_operation ado; - std::stringstream ss; - bool r = tools::portble_serialize_obj_to_stream(ado, ss); - std::cout << r; -#endif - - m_do_print_hashrate = do_hr; } //----------------------------------------------------------------------------------------------------- From 2ba22bc53ba1330653a27405b58c3608598cd7f5 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Fri, 18 Oct 2024 01:15:47 +0400 Subject: [PATCH 022/106] fixed typos and minor changes in currency_format_utils.cpp --- src/currency_core/currency_basic.h | 4 ++-- src/currency_core/currency_format_utils.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/currency_core/currency_basic.h b/src/currency_core/currency_basic.h index b968e617..271b8706 100644 --- a/src/currency_core/currency_basic.h +++ b/src/currency_core/currency_basic.h @@ -797,7 +797,7 @@ namespace currency #define ASSET_DESCRIPTOR_OPERATION_LAST_VER 2 - typedef boost::variant asset_descriptor_operator_etc_fields; + typedef boost::variant asset_descriptor_operation_etc_fields; struct asset_descriptor_operation { @@ -808,7 +808,7 @@ namespace currency boost::optional opt_asset_id; // target asset_id - for update/emit boost::optional opt_descriptor; //used in deploy/update boost::optional opt_amount; //used in burn/emit - std::vector etc; //reserved for future use + std::vector etc; //reserved for future use BEGIN_VERSIONED_SERIALIZE(ASSET_DESCRIPTOR_OPERATION_LAST_VER, version) diff --git a/src/currency_core/currency_format_utils.cpp b/src/currency_core/currency_format_utils.cpp index 9dfae001..bb04b760 100644 --- a/src/currency_core/currency_format_utils.cpp +++ b/src/currency_core/currency_format_utils.cpp @@ -609,7 +609,7 @@ namespace currency CHECK_AND_ASSERT_MES(count_type_in_variant_container(context.tx.proofs) == 1, false, "asset_operation_proof not present or present more than once"); const asset_operation_proof& aop = get_type_in_variant_container_by_ref(context.tx.proofs); - if (context.ado.descriptor.hidden_supply) + if (context.ado.opt_descriptor.has_value() && context.ado.opt_descriptor->hidden_supply) { CHECK_AND_ASSERT_MES(aop.opt_amount_commitment_composition_proof.has_value(), false, "opt_amount_commitment_composition_proof is absent"); // TODO @#@# if asset is hidden -- check composition proof From 2632446ee5105ad722225e2d4a313e4885a7ec4a Mon Sep 17 00:00:00 2001 From: sowle Date: Fri, 18 Oct 2024 03:15:33 +0200 Subject: [PATCH 023/106] core: asset operation refactoring WIP --- src/currency_core/blockchain_storage.cpp | 364 ++++++++++++++++---- src/currency_core/blockchain_storage.h | 11 +- src/currency_core/currency_basic.h | 24 +- src/currency_core/currency_format_utils.cpp | 56 +-- src/currency_core/currency_format_utils.h | 1 + src/wallet/wallet_rpc_server.cpp | 7 + 6 files changed, 346 insertions(+), 117 deletions(-) diff --git a/src/currency_core/blockchain_storage.cpp b/src/currency_core/blockchain_storage.cpp index 6d4cbc1f..49455e7f 100644 --- a/src/currency_core/blockchain_storage.cpp +++ b/src/currency_core/blockchain_storage.cpp @@ -830,7 +830,7 @@ bool blockchain_storage::purge_transaction_from_blockchain(const crypto::hash& t fee = get_tx_fee(tx_res_ptr->tx); purge_transaction_keyimages_from_blockchain(tx, true); - bool r = unprocess_blockchain_tx_extra(tx); + bool r = unprocess_blockchain_tx_extra(tx, tx_res_ptr->m_keeper_block_height); CHECK_AND_ASSERT_MES(r, false, "failed to unprocess_blockchain_tx_extra for tx " << tx_id); r = unprocess_blockchain_tx_attachments(tx, get_current_blockchain_size(), 0/*TODO: add valid timestamp here in future if need*/); @@ -3805,7 +3805,7 @@ bool blockchain_storage::pop_transaction_from_global_index(const transaction& tx return true; } //------------------------------------------------------------------ -bool blockchain_storage::unprocess_blockchain_tx_extra(const transaction& tx) +bool blockchain_storage::unprocess_blockchain_tx_extra(const transaction& tx, const uint64_t height) { tx_extra_info ei = AUTO_VAL_INIT(ei); bool r = parse_and_validate_tx_extra(tx, ei); @@ -3818,9 +3818,7 @@ bool blockchain_storage::unprocess_blockchain_tx_extra(const transaction& tx) if (ei.m_asset_operation.operation_type != ASSET_DESCRIPTOR_OPERATION_UNDEFINED) { - crypto::public_key asset_id = currency::null_pkey; - CHECK_AND_ASSERT_MES(get_or_calculate_asset_id(ei.m_asset_operation, nullptr, &asset_id), false, "get_or_calculate_asset_id failed"); - r = pop_asset_info(asset_id); + r = pop_asset_info(ei.m_asset_operation, height); CHECK_AND_ASSERT_MES(r, false, "failed to pop_alias_info"); } return true; @@ -3862,15 +3860,16 @@ bool blockchain_storage::get_asset_info(const crypto::public_key& asset_id, asse { CRITICAL_REGION_LOCAL(m_read_lock); auto as_ptr = m_db_assets.find(asset_id); - if (as_ptr) - { - if (as_ptr->size()) - { - result = as_ptr->back().descriptor; - return true; - } - } - return false; + if (!as_ptr) + return false; + if (as_ptr->empty()) + return false; + // the last history item must have opt_descriptor, but we check it just to be sure + if (!as_ptr->back().opt_descriptor.has_value()) + return false; + + result = as_ptr->back().opt_descriptor.get(); + return true; } //------------------------------------------------------------------ uint64_t blockchain_storage::get_assets(uint64_t offset, uint64_t count, std::list& assets) const @@ -3882,15 +3881,19 @@ uint64_t blockchain_storage::get_assets(uint64_t offset, uint64_t count, std::li if (i < offset) return true; // continue - CHECK_AND_ASSERT_THROW_MES(asset_descriptor_history.size(), "asset_descriptor_history unexpectedly have 0 size, asset_id: " << asset_id); - assets.push_back(asset_descriptor_with_id()); - static_cast(assets.back()) = asset_descriptor_history.back().descriptor; - assets.back().asset_id = asset_id; + CHECK_AND_ASSERT_THROW_MES(asset_descriptor_history.size(), "unexpectedly, the asset_descriptor_history is empty; asset_id: " << asset_id); + CHECK_AND_ASSERT_THROW_MES(asset_descriptor_history.back().opt_descriptor.has_value(), "the last element of asset_descriptor_history doesn't have descriptor; asset_id: " << asset_id); + asset_descriptor_with_id& added_item = assets.emplace_back(); + asset_descriptor_base& added_item_adb = static_cast(added_item); + added_item_adb = asset_descriptor_history.back().opt_descriptor.get(); + replace_asset_ticker_and_full_name_if_invalid(added_item_adb, asset_id); + added_item.asset_id = asset_id; + if (assets.size() >= count) { - return false; + return false; // stop } - return true; + return true; // continue }); return assets.size(); } @@ -4084,21 +4087,71 @@ bool blockchain_storage::put_alias_info(const transaction & tx, extra_alias_entr return true; } //------------------------------------------------------------------ -bool blockchain_storage::pop_asset_info(const crypto::public_key& asset_id) +bool blockchain_storage::pop_asset_info(const asset_descriptor_operation& ado, const uint64_t height) { CRITICAL_REGION_LOCAL(m_read_lock); + crypto::public_key asset_id = currency::null_pkey; + CHECK_AND_ASSERT_MES(get_or_calculate_asset_id(ado, nullptr, &asset_id), false, "get_or_calculate_asset_id failed"); + auto asset_history_ptr = m_db_assets.find(asset_id); CHECK_AND_ASSERT_MES(asset_history_ptr && asset_history_ptr->size(), false, "empty name list in pop_asset_info"); - + assets_container::t_value_type local_asset_hist = *asset_history_ptr; - local_asset_hist.pop_back(); + + if (is_hardfork_active_for_height(ZANO_HARDFORK_05, height)) + { + // NEW HF5 handling + assets_container::t_value_type local_asset_hist = *asset_history_ptr; + asset_descriptor_operation& last_ado = local_asset_hist.back(); // above we made sure that the history isn't empty + CHECK_AND_ASSERT_MES(last_ado.opt_descriptor.has_value(), false, "opt_descriptor is missing during asset pop, op: " << (int)ado.operation_type); + asset_descriptor_base& last_adb = last_ado.opt_descriptor.get(); + + if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT) + { + // just change the most recent history record, don't pop + CHECK_AND_ASSERT_MES(last_ado.opt_amount_commitment.has_value() && ado.opt_amount_commitment.has_value(), false, "last_ado.opt_amount_commitment or ado.opt_amount_commitment is missing (emit)"); + last_ado.opt_amount_commitment.get() = (crypto::point_t(last_ado.opt_amount_commitment.get()) - crypto::point_t(ado.opt_amount_commitment.get())).to_public_key(); + if (!last_adb.hidden_supply) + { + CHECK_AND_ASSERT_MES(last_ado.opt_amount.has_value() && ado.opt_amount.has_value(), false, "last_ado.opt_amount or ado.opt_amount is missing (emit)"); + last_ado.opt_amount.get() -= ado.opt_amount.get(); + } + } + else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN) + { + // just change the most recent history record, don't pop + CHECK_AND_ASSERT_MES(last_ado.opt_amount_commitment.has_value() && ado.opt_amount_commitment.has_value(), false, "last_ado.opt_amount_commitment or ado.opt_amount_commitment is missing (burn)"); + last_ado.opt_amount_commitment.get() = (crypto::point_t(last_ado.opt_amount_commitment.get()) + crypto::point_t(ado.opt_amount_commitment.get())).to_public_key(); + if (!last_adb.hidden_supply) + { + CHECK_AND_ASSERT_MES(last_ado.opt_amount.has_value() && ado.opt_amount.has_value(), false, "last_ado.opt_amount or ado.opt_amount is missing (burn)"); + last_ado.opt_amount.get() += ado.opt_amount.get(); + } + } + else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER || ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE) + { + // just pop the most recent history record + local_asset_hist.pop_back(); + } + else + { + CHECK_AND_ASSERT_MES(false, false, "invalid operation: " << (int)ado.operation_type); + } + } + else + { + // HF4 + local_asset_hist.pop_back(); + } + + // both HF4 and HF5 if (local_asset_hist.size()) m_db_assets.set(asset_id, local_asset_hist); else m_db_assets.erase(asset_id); - LOG_PRINT_MAGENTA("[ASSET_POP]: " << asset_id << ": " << (!local_asset_hist.empty() ? "(prev)" : "(erased)"), LOG_LEVEL_1); + return true; } //------------------------------------------------------------------ @@ -4107,16 +4160,18 @@ bool blockchain_storage::validate_ado_ownership(asset_op_verification_context& a bool r = false; CHECK_AND_ASSERT_MES(avc.asset_op_history->size() != 0, false, "asset with id " << avc.asset_id << " has empty history record"); const asset_descriptor_operation& last_ado = avc.asset_op_history->back(); + CHECK_AND_ASSERT_MES(last_ado.opt_descriptor.has_value(), false, "last ado is missing opt_descriptor; asset id: " << avc.asset_id); + const asset_descriptor_base& last_adb = last_ado.opt_descriptor.get(); - if (is_hardfork_active(ZANO_HARDFORK_05)) // TODO: consider changing to height-specific check + if (is_hardfork_active_for_height(ZANO_HARDFORK_05, avc.height)) { - if (last_ado.descriptor.owner_eth_pub_key.has_value()) + if (last_adb.owner_eth_pub_key.has_value()) { - CHECK_AND_ASSERT_MES(last_ado.descriptor.owner == null_pkey, false, "owner_eth_pub_key is set but owner pubkey is nonzero"); + CHECK_AND_ASSERT_MES(last_adb.owner == null_pkey, false, "owner_eth_pub_key is set but owner pubkey is nonzero"); asset_operation_ownership_proof_eth aoop_eth{}; r = get_type_in_variant_container(avc.tx.proofs, aoop_eth); CHECK_AND_ASSERT_MES(r, false, "Ownership validation failed: asset_operation_ownership_proof_eth is missing"); - if (!crypto::verify_eth_signature(avc.tx_id, last_ado.descriptor.owner_eth_pub_key.value(), aoop_eth.eth_sig)) + if (!crypto::verify_eth_signature(avc.tx_id, last_adb.owner_eth_pub_key.value(), aoop_eth.eth_sig)) { LOG_ERROR("Failed to validate secp256k1 signature for hash: " << avc.tx_id << ", signature: " << aoop_eth.eth_sig); return false; @@ -4133,12 +4188,13 @@ bool blockchain_storage::validate_ado_ownership(asset_op_verification_context& a r = get_type_in_variant_container(avc.tx.proofs, aoop); CHECK_AND_ASSERT_MES(r, false, "Ownership validation failed: asset_operation_ownership_proof is missing"); - return crypto::verify_schnorr_sig(avc.tx_id, last_ado.descriptor.owner, aoop.gss); + return crypto::verify_schnorr_sig(avc.tx_id, last_adb.owner, aoop.gss); } //------------------------------------------------------------------ -bool blockchain_storage::validate_asset_operation_against_current_blochain_state(asset_op_verification_context& avc) const +bool blockchain_storage::validate_asset_operation_hf4(asset_op_verification_context& avc) const { CRITICAL_REGION_LOCAL(m_read_lock); + CHECK_AND_ASSERT_MES(!is_hardfork_active_for_height(ZANO_HARDFORK_05, avc.height), false, "validate_asset_operation_hf4 was called in HF5"); CHECK_AND_ASSERT_MES(get_or_calculate_asset_id(avc.ado, &avc.asset_id_pt, &avc.asset_id), false, "get_or_calculate_asset_id failed"); avc.asset_op_history = m_db_assets.find(avc.asset_id); @@ -4150,16 +4206,25 @@ bool blockchain_storage::validate_asset_operation_against_current_blochain_state if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER) { CHECK_AND_ASSERT_MES(!avc.asset_op_history, false, "asset with id " << avc.asset_id << " has already been registered"); - avc.amount_to_validate = ado.descriptor.current_supply; - if(this->is_hardfork_active(ZANO_HARDFORK_05)) - { - CHECK_AND_ASSERT_MES(validate_ado_initial(ado.descriptor), false, "validate_ado_initial failed!"); - } + CHECK_AND_ASSERT_MES(ado.opt_descriptor.has_value(), false, "opt_descriptor is missing while registering asset " << avc.asset_id); + + // CZ please review the following two added lines, do we need them _here_? I think they should go to hardfork_specific_terms... + // anyway, we musn't miss these checks for the sake of consensus + CHECK_AND_ASSERT_MES(!ado.opt_descriptor.get().owner_eth_pub_key.has_value(), false, "owner_eth_pub_key is prohibited before HF5"); + CHECK_AND_ASSERT_MES(!ado.opt_asset_id_salt.has_value(), false, "opt_asset_id_salt is prohibited before HF5"); + + CHECK_AND_ASSERT_MES(ado.opt_descriptor.has_value(), false, "opt_descriptor is missing while registering asset " << avc.asset_id); + avc.amount_to_validate = ado.opt_descriptor.get().current_supply; } else { CHECK_AND_ASSERT_MES(avc.asset_op_history && avc.asset_op_history->size() > 0, false, "asset with id " << avc.asset_id << " has not been registered"); const asset_descriptor_operation& last_ado = avc.asset_op_history->back(); + CHECK_AND_ASSERT_MES(last_ado.opt_descriptor.has_value(), false, "opt_descriptor is missing in last ado, op: " << (int)ado.operation_type); + const asset_descriptor_base& last_adb = last_ado.opt_descriptor.get(); + CHECK_AND_ASSERT_MES(ado.opt_descriptor.has_value(), false, "opt_descriptor is missing in ado, op: " << (int)ado.operation_type); + const asset_descriptor_base adb = ado.opt_descriptor.get(); + // check ownership permission if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT || ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE) { @@ -4171,23 +4236,23 @@ bool blockchain_storage::validate_asset_operation_against_current_blochain_state if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE) { //check that total current_supply haven't changed - CHECK_AND_ASSERT_MES(ado.descriptor.current_supply == last_ado.descriptor.current_supply, false, "update operation attempted to change emission, failed"); - CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, last_ado.descriptor), false, "update operation modifies asset descriptor in a prohibited manner"); + CHECK_AND_ASSERT_MES(adb.current_supply == last_adb.current_supply, false, "update operation attempted to change emission, failed"); + CHECK_AND_ASSERT_MES(validate_ado_update_allowed(adb, last_adb), false, "update operation modifies asset descriptor in a prohibited manner"); need_to_validate_ao_amount_commitment = false; } else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT) { - CHECK_AND_ASSERT_MES(ado.descriptor.current_supply > last_ado.descriptor.current_supply, false, "emit operation does not increase the current supply, failed"); - CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, last_ado.descriptor), false, "emit operation modifies asset descriptor in a prohibited manner"); - CHECK_AND_ASSERT_MES(ado.descriptor.meta_info == last_ado.descriptor.meta_info, false, "emit operation is not allowed to update meta info"); - avc.amount_to_validate = ado.descriptor.current_supply - last_ado.descriptor.current_supply; + CHECK_AND_ASSERT_MES(adb.current_supply > last_adb.current_supply, false, "emit operation does not increase the current supply, failed"); + CHECK_AND_ASSERT_MES(validate_ado_update_allowed(adb, last_adb), false, "emit operation modifies asset descriptor in a prohibited manner"); + CHECK_AND_ASSERT_MES(adb.meta_info == last_adb.meta_info, false, "emit operation is not allowed to update meta info"); + avc.amount_to_validate = adb.current_supply - last_adb.current_supply; } else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN) { - CHECK_AND_ASSERT_MES(ado.descriptor.current_supply < last_ado.descriptor.current_supply, false, "burn operation does not decrease the current supply, failed"); - CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, last_ado.descriptor), false, "burn operation modifies asset descriptor in a prohibited manner"); - CHECK_AND_ASSERT_MES(ado.descriptor.meta_info == last_ado.descriptor.meta_info, false, "burn operation is not allowed to update meta info"); - avc.amount_to_validate = last_ado.descriptor.current_supply - ado.descriptor.current_supply; + CHECK_AND_ASSERT_MES(adb.current_supply < last_adb.current_supply, false, "burn operation does not decrease the current supply, failed"); + CHECK_AND_ASSERT_MES(validate_ado_update_allowed(adb, last_adb), false, "burn operation modifies asset descriptor in a prohibited manner"); + CHECK_AND_ASSERT_MES(adb.meta_info == last_adb.meta_info, false, "burn operation is not allowed to update meta info"); + avc.amount_to_validate = last_adb.current_supply - adb.current_supply; } else { @@ -4205,35 +4270,182 @@ bool blockchain_storage::validate_asset_operation_against_current_blochain_state return true; } //------------------------------------------------------------------ -bool blockchain_storage::put_asset_info(const transaction& tx, const crypto::hash& tx_id, const asset_descriptor_operation& ado) +bool blockchain_storage::validate_asset_operation(asset_op_verification_context& avc) const +{ + CRITICAL_REGION_LOCAL(m_read_lock); + CHECK_AND_ASSERT_MES(is_hardfork_active_for_height(ZANO_HARDFORK_05, avc.height), false, "validate_asset_operation was called before HF5"); + + CHECK_AND_ASSERT_MES(get_or_calculate_asset_id(avc.ado, &avc.asset_id_pt, &avc.asset_id), false, "get_or_calculate_asset_id failed"); + avc.asset_op_history = m_db_assets.find(avc.asset_id); + + const asset_descriptor_operation& ado = avc.ado; + + bool need_to_validate_ao_amount_commitment = true; + + if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER) + { + CHECK_AND_ASSERT_MES(!avc.asset_op_history, false, "asset with id " << avc.asset_id << " has already been registered"); + CHECK_AND_ASSERT_MES(ado.opt_descriptor.has_value(), false, "opt_descriptor is missing while registering asset " << avc.asset_id); + avc.amount_to_validate = ado.opt_descriptor.get().current_supply; + // HF5 specific + CHECK_AND_ASSERT_MES(validate_ado_initial(ado.opt_descriptor.get()), false, "validate_ado_initial failed!"); + CHECK_AND_ASSERT_MES(ado.opt_amount.has_value() && avc.amount_to_validate == ado.opt_amount.get(), false, "opt_amount is missing or incorrect"); + } + else + { + CHECK_AND_ASSERT_MES(avc.asset_op_history && avc.asset_op_history->size() > 0, false, "asset with id " << avc.asset_id << " has not been registered"); + const asset_descriptor_operation& last_ado = avc.asset_op_history->back(); + CHECK_AND_ASSERT_MES(last_ado.opt_descriptor.has_value(), false, "opt_descriptor is missing in last ado, op: " << (int)ado.operation_type); + const asset_descriptor_base& last_adb = last_ado.opt_descriptor.get(); + + // check ownership permission + if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT || ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE) + { + bool r = validate_ado_ownership(avc); + CHECK_AND_ASSERT_MES(r, false, "Failed to validate ownership of asset_descriptor_operation, rejecting"); + } + + avc.amount_to_validate = 0; + if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE) + { + CHECK_AND_ASSERT_MES(ado.opt_descriptor.has_value(), false, "opt_descriptor is missing (update)"); + //check that total current_supply haven't changed + CHECK_AND_ASSERT_MES(ado.opt_descriptor.get().current_supply == last_adb.current_supply, false, "update operation attempted to change emission, failed"); + CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.opt_descriptor.get(), last_adb), false, "update operation modifies asset descriptor in a prohibited manner"); + need_to_validate_ao_amount_commitment = false; + } + else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT) + { + CHECK_AND_ASSERT_MES(ado.descriptor.current_supply > last_adb.current_supply, false, "emit operation does not increase the current supply, failed"); + CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, last_adb), false, "emit operation modifies asset descriptor in a prohibited manner"); + CHECK_AND_ASSERT_MES(ado.descriptor.meta_info == last_adb.meta_info, false, "emit operation is not allowed to update meta info"); + + if (!last_adb.hidden_supply) + { + CHECK_AND_ASSERT_MES(ado.opt_amount.has_value(), false, "opt_amount is missing (emit)"); + avc.amount_to_validate = ado.opt_amount.get(); + } + } + else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN) + { + CHECK_AND_ASSERT_MES(ado.descriptor.current_supply < last_adb.current_supply, false, "burn operation does not decrease the current supply, failed"); + CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, last_adb), false, "burn operation modifies asset descriptor in a prohibited manner"); + CHECK_AND_ASSERT_MES(ado.descriptor.meta_info == last_adb.meta_info, false, "burn operation is not allowed to update meta info"); + + if (!last_adb.hidden_supply) + { + CHECK_AND_ASSERT_MES(ado.opt_amount.has_value(), false, "opt_amount is missing (burn)"); + avc.amount_to_validate = ado.opt_amount.get(); + } + } + else + { + LOG_ERROR("Unknown operation type: " << (int)ado.operation_type); + return false; + } + } + + if (need_to_validate_ao_amount_commitment) + { + bool r = validate_asset_operation_amount_commitment(avc); + CHECK_AND_ASSERT_MES(r, false, "Balance proof validation failed for asset_descriptor_operation"); + } + + return true; +} +//------------------------------------------------------------------ +bool blockchain_storage::put_asset_info(const transaction& tx, const crypto::hash& tx_id, const asset_descriptor_operation& ado, const uint64_t height) { CRITICAL_REGION_LOCAL(m_read_lock); - asset_op_verification_context avc = { tx, tx_id, ado }; - CHECK_AND_ASSERT_MES(validate_asset_operation_against_current_blochain_state(avc), false, "asset operation validation failed"); - - assets_container::t_value_type local_asset_history{}; - if (avc.asset_op_history) - local_asset_history = *avc.asset_op_history; - local_asset_history.push_back(ado); - m_db_assets.set(avc.asset_id, local_asset_history); - - switch(ado.operation_type) + asset_op_verification_context avc = { tx, tx_id, ado, height }; + if (is_hardfork_active_for_height(ZANO_HARDFORK_05, height)) { - case ASSET_DESCRIPTOR_OPERATION_REGISTER: - LOG_PRINT_MAGENTA("[ASSET_REGISTERED]: " << print_money_brief(ado.descriptor.current_supply, ado.descriptor.decimal_point) << ", " << avc.asset_id << ": " << ado.descriptor.ticker << ", \"" << ado.descriptor.full_name << "\"", LOG_LEVEL_1); - break; - case ASSET_DESCRIPTOR_OPERATION_UPDATE: - LOG_PRINT_MAGENTA("[ASSET_UPDATED]: " << avc.asset_id << ": " << ado.descriptor.ticker << ", \"" << ado.descriptor.full_name << "\"", LOG_LEVEL_1); - break; - case ASSET_DESCRIPTOR_OPERATION_EMIT: - LOG_PRINT_MAGENTA("[ASSET_EMITTED]: " << print_money_brief(avc.amount_to_validate, ado.descriptor.decimal_point) << ", " << avc.asset_id << ": " << ado.descriptor.ticker << ", \"" << ado.descriptor.full_name << "\"", LOG_LEVEL_1); - break; - case ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN: - LOG_PRINT_MAGENTA("[ASSET_BURNT]: " << print_money_brief(avc.amount_to_validate, ado.descriptor.decimal_point) << ", " << avc.asset_id << ": " << ado.descriptor.ticker << ", \"" << ado.descriptor.full_name << "\"", LOG_LEVEL_1); - break; - default: - LOG_ERROR("Unknown operation type: " << (int)ado.operation_type); + CHECK_AND_ASSERT_MES(validate_asset_operation(avc), false, "asset operation validation failed (HF5)"); + // NEW HF5 handling here + + assets_container::t_value_type local_asset_history{}; + if (avc.asset_op_history) + local_asset_history = *avc.asset_op_history; + + if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER) + { + CHECK_AND_ASSERT_MES(ado.opt_descriptor.has_value(), false, "opt_descriptor is missing (register)"); // validated in validate_asset_operation(), just in case + const asset_descriptor_base& adb = ado.opt_descriptor.get(); + local_asset_history.push_back(ado); + LOG_PRINT_MAGENTA("[ASSET_REGISTERED]: " << print_money_brief(adb.current_supply, adb.decimal_point) << ", " << avc.asset_id << ": " << adb.ticker << ", \"" << adb.full_name << "\"", LOG_LEVEL_1); + } + else + { + CHECK_AND_ASSERT_MES(!local_asset_history.empty(), false, "local_asset_history is empty (update/emit/burn)"); + asset_descriptor_operation& last_ado = local_asset_history.back(); + CHECK_AND_ASSERT_MES(last_ado.opt_descriptor.has_value(), false, "last_ado.opt_descriptor is missing (update/emit/burn)"); + asset_descriptor_base& last_adb = local_asset_history.back().opt_descriptor.get(); + + if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE) + { + local_asset_history.push_back(ado); + LOG_PRINT_MAGENTA("[ASSET_UPDATED]: " << avc.asset_id << ": " << last_adb.ticker << ", \"" << last_adb.full_name << "\"", LOG_LEVEL_1); + } + else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT) + { + // just change the most recent history record, don't push + CHECK_AND_ASSERT_MES(last_ado.opt_amount_commitment.has_value() && ado.opt_amount_commitment.has_value(), false, "last_ado.opt_amount_commitment or ado.opt_amount_commitment is missing (emit)"); + last_ado.opt_amount_commitment.get() = (crypto::point_t(last_ado.opt_amount_commitment.get()) + crypto::point_t(ado.opt_amount_commitment.get())).to_public_key(); + if (!last_adb.hidden_supply) + { + CHECK_AND_ASSERT_MES(last_ado.opt_amount.has_value() && ado.opt_amount.has_value(), false, "last_ado.opt_amount or ado.opt_amount is missing (emit)"); + last_ado.opt_amount.get() += ado.opt_amount.get(); + } + LOG_PRINT_MAGENTA("[ASSET_EMITTED]: " << print_money_brief(avc.amount_to_validate, last_adb.decimal_point) << ", " << avc.asset_id << ": " << last_adb.ticker << ", \"" << last_adb.full_name << "\"", LOG_LEVEL_1); + } + else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN) + { + // just change the most recent history record, don't push + CHECK_AND_ASSERT_MES(last_ado.opt_amount_commitment.has_value() && ado.opt_amount_commitment.has_value(), false, "last_ado.opt_amount_commitment or ado.opt_amount_commitment is missing (burn)"); + last_ado.opt_amount_commitment.get() = (crypto::point_t(last_ado.opt_amount_commitment.get()) - crypto::point_t(ado.opt_amount_commitment.get())).to_public_key(); + if (!last_adb.hidden_supply) + { + CHECK_AND_ASSERT_MES(last_ado.opt_amount.has_value() && ado.opt_amount.has_value(), false, "last_ado.opt_amount or ado.opt_amount is missing (burn)"); + last_ado.opt_amount.get() -= ado.opt_amount.get(); + } + LOG_PRINT_MAGENTA("[ASSET_BURNT]: " << print_money_brief(avc.amount_to_validate, last_adb.decimal_point) << ", " << avc.asset_id << ": " << last_adb.ticker << ", \"" << last_adb.full_name << "\"", LOG_LEVEL_1); + } + else + CHECK_AND_ASSERT_MES(false, false, "Unknown operation type: " << (int)ado.operation_type); + } + + m_db_assets.set(avc.asset_id, local_asset_history); + } + else + { + // HF4 + CHECK_AND_ASSERT_MES(validate_asset_operation_hf4(avc), false, "asset operation validation failed (HF4)"); + + assets_container::t_value_type local_asset_history{}; + if (avc.asset_op_history) + local_asset_history = *avc.asset_op_history; + local_asset_history.push_back(ado); + m_db_assets.set(avc.asset_id, local_asset_history); + + const asset_descriptor_base& adb = ado.opt_descriptor.get(); // in HF4 descriptor must always be present, validated in validate_asset_operation_hf4() + switch(ado.operation_type) + { + case ASSET_DESCRIPTOR_OPERATION_REGISTER: + LOG_PRINT_MAGENTA("[ASSET_REGISTERED]: " << print_money_brief(adb.current_supply, adb.decimal_point) << ", " << avc.asset_id << ": " << adb.ticker << ", \"" << adb.full_name << "\"", LOG_LEVEL_1); + break; + case ASSET_DESCRIPTOR_OPERATION_UPDATE: + LOG_PRINT_MAGENTA("[ASSET_UPDATED]: " << avc.asset_id << ": " << adb.ticker << ", \"" << adb.full_name << "\"", LOG_LEVEL_1); + break; + case ASSET_DESCRIPTOR_OPERATION_EMIT: + LOG_PRINT_MAGENTA("[ASSET_EMITTED]: " << print_money_brief(avc.amount_to_validate, adb.decimal_point) << ", " << avc.asset_id << ": " << adb.ticker << ", \"" << adb.full_name << "\"", LOG_LEVEL_1); + break; + case ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN: + LOG_PRINT_MAGENTA("[ASSET_BURNT]: " << print_money_brief(avc.amount_to_validate, adb.decimal_point) << ", " << avc.asset_id << ": " << adb.ticker << ", \"" << adb.full_name << "\"", LOG_LEVEL_1); + break; + default: + LOG_ERROR("Unknown operation type: " << (int)ado.operation_type); + } } return true; @@ -4309,7 +4521,7 @@ bool blockchain_storage::prevalidate_alias_info(const transaction& tx, const ext return true; } //------------------------------------------------------------------ -bool blockchain_storage::process_blockchain_tx_extra(const transaction& tx, const crypto::hash& tx_id) +bool blockchain_storage::process_blockchain_tx_extra(const transaction& tx, const crypto::hash& tx_id, const uint64_t height) { //check transaction extra tx_extra_info ei = AUTO_VAL_INIT(ei); @@ -4325,7 +4537,7 @@ bool blockchain_storage::process_blockchain_tx_extra(const transaction& tx, cons } if (ei.m_asset_operation.operation_type != ASSET_DESCRIPTOR_OPERATION_UNDEFINED) { - r = put_asset_info(tx, tx_id, ei.m_asset_operation); + r = put_asset_info(tx, tx_id, ei.m_asset_operation, height); CHECK_AND_ASSERT_MES(r, false, "failed to put_asset_info"); } @@ -4525,7 +4737,7 @@ bool blockchain_storage::add_transaction_from_block(const transaction& tx, const CHECK_AND_ASSERT_MES(validate_tx_for_hardfork_specific_terms(tx, tx_id, bl_height), false, "tx " << tx_id << ": hardfork-specific validation failed"); TIME_MEASURE_START_PD(tx_process_extra); - bool r = process_blockchain_tx_extra(tx, tx_id); + bool r = process_blockchain_tx_extra(tx, tx_id, bl_height); CHECK_AND_ASSERT_MES(r, false, "failed to process_blockchain_tx_extra"); TIME_MEASURE_FINISH_PD_COND(need_to_profile, tx_process_extra); @@ -4541,7 +4753,7 @@ bool blockchain_storage::add_transaction_from_block(const transaction& tx, const { LOG_ERROR("critical internal error: add_transaction_input_visitor failed. but key_images should be already checked"); purge_transaction_keyimages_from_blockchain(tx, false); - bool r = unprocess_blockchain_tx_extra(tx); + bool r = unprocess_blockchain_tx_extra(tx, bl_height); CHECK_AND_ASSERT_MES(r, false, "failed to unprocess_blockchain_tx_extra"); unprocess_blockchain_tx_attachments(tx, bl_height, timestamp); @@ -4562,7 +4774,7 @@ bool blockchain_storage::add_transaction_from_block(const transaction& tx, const { LOG_ERROR("critical internal error: tx with id: " << tx_id << " in block id: " << bl_id << " already in blockchain"); purge_transaction_keyimages_from_blockchain(tx, true); - bool r = unprocess_blockchain_tx_extra(tx); + bool r = unprocess_blockchain_tx_extra(tx, bl_height); CHECK_AND_ASSERT_MES(r, false, "failed to unprocess_blockchain_tx_extra"); unprocess_blockchain_tx_attachments(tx, bl_height, timestamp); @@ -6967,7 +7179,7 @@ bool blockchain_storage::is_hardfork_active(size_t hardfork_id) const //------------------------------------------------------------------ bool blockchain_storage::is_hardfork_active_for_height(size_t hardfork_id, uint64_t height) const { - return m_core_runtime_config.is_hardfork_active_for_height(hardfork_id, height); + return m_core_runtime_config.is_hardfork_active_for_height(hardfork_id, height != UINT64_MAX ? height : m_db_blocks.size()); } //------------------------------------------------------------------ bool blockchain_storage::prevalidate_block(const block& bl) diff --git a/src/currency_core/blockchain_storage.h b/src/currency_core/blockchain_storage.h index 9d7cba3a..e6b30edf 100644 --- a/src/currency_core/blockchain_storage.h +++ b/src/currency_core/blockchain_storage.h @@ -381,7 +381,8 @@ namespace currency const alt_chain_type& alt_chain = alt_chain_type(), uint64_t split_height = 0)const; bool validate_ado_ownership(asset_op_verification_context& avc) const; - bool validate_asset_operation_against_current_blochain_state(asset_op_verification_context& avc) const; + bool validate_asset_operation_hf4(asset_op_verification_context& avc) const; + bool validate_asset_operation(asset_op_verification_context& avc) const; void set_core_runtime_config(const core_runtime_config& pc) const; const core_runtime_config& get_core_runtime_config()const; @@ -679,14 +680,14 @@ namespace currency uint64_t get_adjusted_time()const; bool complete_timestamps_vector(uint64_t start_height, std::vector& timestamps); bool update_next_comulative_size_limit(); - bool process_blockchain_tx_extra(const transaction& tx, const crypto::hash& tx_id); - bool unprocess_blockchain_tx_extra(const transaction& tx); + bool process_blockchain_tx_extra(const transaction& tx, const crypto::hash& tx_id, const uint64_t height); + bool unprocess_blockchain_tx_extra(const transaction& tx, const uint64_t height); bool process_blockchain_tx_attachments(const transaction& tx, uint64_t h, const crypto::hash& bl_id, uint64_t timestamp); bool unprocess_blockchain_tx_attachments(const transaction& tx, uint64_t h, uint64_t timestamp); bool pop_alias_info(const extra_alias_entry& ai); bool put_alias_info(const transaction& tx, extra_alias_entry& ai); - bool pop_asset_info(const crypto::public_key& asset_id); - bool put_asset_info(const transaction& tx, const crypto::hash& tx_id, const asset_descriptor_operation& ado); + bool pop_asset_info(const asset_descriptor_operation& ado, const uint64_t height); + bool put_asset_info(const transaction& tx, const crypto::hash& tx_id, const asset_descriptor_operation& ado, const uint64_t height); void fill_addr_to_alias_dict(); //bool resync_spent_tx_flags(); bool prune_ring_signatures_and_attachments_if_need(); diff --git a/src/currency_core/currency_basic.h b/src/currency_core/currency_basic.h index 271b8706..d2abec65 100644 --- a/src/currency_core/currency_basic.h +++ b/src/currency_core/currency_basic.h @@ -803,12 +803,13 @@ namespace currency { uint8_t operation_type = ASSET_DESCRIPTOR_OPERATION_UNDEFINED; uint8_t version = 1; - - boost::optional opt_amount_commitment; // premultiplied by 1/8 - boost::optional opt_asset_id; // target asset_id - for update/emit - boost::optional opt_descriptor; //used in deploy/update - boost::optional opt_amount; //used in burn/emit - std::vector etc; //reserved for future use + // register emit burn update + boost::optional opt_amount_commitment; // + + + - (premultiplied by 1/8) + boost::optional opt_asset_id; // - + + + + boost::optional opt_descriptor; // + - - + + boost::optional opt_amount; // ? ? ? - (only for non-hidden supply) + boost::optional opt_asset_id_salt; // ? - - - (optional) + std::vector etc; // (reserved for future use) BEGIN_VERSIONED_SERIALIZE(ASSET_DESCRIPTOR_OPERATION_LAST_VER, version) @@ -819,6 +820,7 @@ namespace currency FIELD(opt_asset_id) FIELD(opt_descriptor) FIELD(opt_amount) + FIELD(opt_asset_id_salt) FIELD(etc) END_SERIALIZE() @@ -832,16 +834,18 @@ namespace currency BOOST_SERIALIZE(opt_asset_id) BOOST_SERIALIZE(opt_descriptor) BOOST_SERIALIZE(opt_amount) + BOOST_SERIALIZE(opt_asset_id_salt) BOOST_SERIALIZE(etc) END_BOOST_SERIALIZATION_TOTAL_FIELDS(7) BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(version) DOC_DSCR("Asset operation type struct version") DOC_EXMP(2) DOC_END KV_SERIALIZE(operation_type) DOC_DSCR("Asset operation type identifier") DOC_EXMP(1) DOC_END - KV_SERIALIZE_POD_AS_HEX_STRING(opt_amount_commitment) DOC_DSCR("Asset operation amount commitment(optional)") DOC_EXMP("5688b56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END - KV_SERIALIZE_POD_AS_HEX_STRING(opt_asset_id) DOC_DSCR("ID of an asset.(optional)") DOC_EXMP("cc4e69455e63f4a581257382191de6856c2156630b3fba0db4bdd73ffcfb36b6") DOC_END - KV_SERIALIZE(opt_descriptor) DOC_DSCR("Asset operation amount commitment(optional)") DOC_EXMP_AUTO() DOC_END - KV_SERIALIZE(opt_amount) DOC_DSCR("Asset operation amount(optional, needed only for burn/emit)") DOC_EXMP_AUTO() DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(opt_amount_commitment) DOC_DSCR("(optional) Asset operation amount commitment (register/emit/burn).") DOC_EXMP("5688b56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(opt_asset_id) DOC_DSCR("(optional) ID of an asset (emit/burn/update).") DOC_EXMP("cc4e69455e63f4a581257382191de6856c2156630b3fba0db4bdd73ffcfb36b6") DOC_END + KV_SERIALIZE(opt_descriptor) DOC_DSCR("(optional) Asset operation descriptor (register/update).") DOC_EXMP_AUTO() DOC_END + KV_SERIALIZE(opt_amount) DOC_DSCR("(optional) Asset operation amount (register/emit/burn when supply is non-hidden).") DOC_EXMP_AUTO() DOC_END + KV_SERIALIZE(opt_asset_id_salt) DOC_DSCR("(optional) Asset ID salt. May only be used for asset registration.") DOC_EXMP_AUTO() DOC_END //KV_SERIALIZE(etc) DOC_DSCR("Extra operations") DOC_EXMP_AUTO() DOC_END <---- serialization for variant not supported yet END_KV_SERIALIZE_MAP() diff --git a/src/currency_core/currency_format_utils.cpp b/src/currency_core/currency_format_utils.cpp index fc3cc932..5765e7c0 100644 --- a/src/currency_core/currency_format_utils.cpp +++ b/src/currency_core/currency_format_utils.cpp @@ -619,7 +619,8 @@ namespace currency { // make sure that amount commitment corresponds to opt_amount_commitment_g_proof CHECK_AND_ASSERT_MES(aop.opt_amount_commitment_g_proof.has_value(), false, "opt_amount_commitment_g_proof is absent"); - crypto::point_t A = crypto::point_t(context.ado.amount_commitment).modify_mul8() - context.amount_to_validate * context.asset_id_pt; + CHECK_AND_ASSERT_MES(context.ado.opt_amount_commitment.has_value(), false, "amount_commitment is absent"); + crypto::point_t A = crypto::point_t(context.ado.opt_amount_commitment.get()).modify_mul8() - context.amount_to_validate * context.asset_id_pt; bool r = crypto::check_signature(context.tx_id, A.to_public_key(), aop.opt_amount_commitment_g_proof.get()); CHECK_AND_ASSERT_MES(r, false, "opt_amount_commitment_g_proof check failed"); @@ -675,12 +676,14 @@ namespace currency { if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER || ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT) { + CHECK_AND_ASSERT_MES(ado.opt_amount_commitment.has_value(), false, "opt_amount_commitment is missing"); // amount_commitment supposed to be validated earlier in validate_asset_operation_amount_commitment() - sum_of_pseudo_out_amount_commitments += crypto::point_t(ado.amount_commitment); // *1/8 + sum_of_pseudo_out_amount_commitments += crypto::point_t(ado.opt_amount_commitment.get()); // *1/8 } else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN) { - outs_commitments_sum += crypto::point_t(ado.amount_commitment); // *1/8 + CHECK_AND_ASSERT_MES(ado.opt_amount_commitment.has_value(), false, "opt_amount_commitment is missing"); + outs_commitments_sum += crypto::point_t(ado.opt_amount_commitment.get()); // *1/8 } } size_t zc_sigs_count = 0; @@ -2186,18 +2189,23 @@ namespace currency // otherwise, it must be a register operation CHECK_AND_ASSERT_MES(ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER, false, "unexpected asset operation type: " << (int)ado.operation_type << ", " << get_asset_operation_type_string(ado.operation_type)); + CHECK_AND_ASSERT_MES(ado.opt_descriptor.has_value(), false, "opt_descriptor is missing: " << (int)ado.operation_type << ", " << get_asset_operation_type_string(ado.operation_type)); + const asset_descriptor_base &adb = ado.opt_descriptor.get(); // calculate asset id crypto::hash_helper_t::hs_t hsc; hsc.add_32_chars(CRYPTO_HDS_ASSET_ID); - hsc.add_hash(crypto::hash_helper_t::h(ado.descriptor.ticker)); - hsc.add_hash(crypto::hash_helper_t::h(ado.descriptor.full_name)); - hsc.add_hash(crypto::hash_helper_t::h(ado.descriptor.meta_info)); - hsc.add_scalar(crypto::scalar_t(ado.descriptor.total_max_supply)); - hsc.add_scalar(crypto::scalar_t(ado.descriptor.decimal_point)); - hsc.add_pub_key(ado.descriptor.owner); - if (ado.descriptor.owner_eth_pub_key.has_value()) - hsc.add_eth_pub_key(ado.descriptor.owner_eth_pub_key.value()); + hsc.add_hash(crypto::hash_helper_t::h(adb.ticker)); + hsc.add_hash(crypto::hash_helper_t::h(adb.full_name)); + hsc.add_hash(crypto::hash_helper_t::h(adb.meta_info)); + hsc.add_scalar(crypto::scalar_t(adb.total_max_supply)); + hsc.add_scalar(crypto::scalar_t(adb.decimal_point)); + hsc.add_pub_key(adb.owner); + if (adb.owner_eth_pub_key.has_value()) + hsc.add_eth_pub_key(adb.owner_eth_pub_key.value()); + if (ado.opt_asset_id_salt.has_value()) + hsc.add_scalar(crypto::scalar_t(ado.opt_asset_id_salt.get())); + crypto::hash h = hsc.calc_hash_no_reduce(); // this hash function needs to be computationally expensive (s.a. the whitepaper) @@ -2242,16 +2250,11 @@ namespace currency { if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER) { - //crypto::secret_key asset_control_key{}; - //bool r = derive_key_pair_from_key_pair(sender_account_keys.account_address.spend_public_key, tx_key.sec, asset_control_key, ado.descriptor.owner, CRYPTO_HDS_ASSET_CONTROL_KEY); - //CHECK_AND_ASSERT_MES(r, false, "derive_key_pair_from_key_pair failed"); - // - // old: - // asset_control_key = Hs(CRYPTO_HDS_ASSET_CONTROL_KEY, 8 * tx_key.sec * sender_account_keys.account_address.spend_public_key, 0) - // ado.descriptor.owner = asset_control_key * G + CHECK_AND_ASSERT_MES(ado.opt_descriptor.has_value(), false, "opt_descriptor is missing (register)"); + asset_descriptor_base& adb = ado.opt_descriptor.get(); - if (!ado.descriptor.owner_eth_pub_key.has_value()) - ado.descriptor.owner = sender_account_keys.account_address.spend_public_key; + if (!adb.owner_eth_pub_key.has_value()) + adb.owner = sender_account_keys.account_address.spend_public_key; CHECK_AND_ASSERT_MES(get_or_calculate_asset_id(ado, &gen_context.ao_asset_id_pt, &gen_context.ao_asset_id), false, "get_or_calculate_asset_id failed"); @@ -2268,10 +2271,11 @@ namespace currency amount_of_emitted_asset += item.amount; } } - ado.descriptor.current_supply = amount_of_emitted_asset; // TODO: consider setting current_supply beforehand, not setting it hear in ad-hoc manner -- sowle + adb.current_supply = amount_of_emitted_asset; + ado.opt_amount = amount_of_emitted_asset; // TODO: support hidden supply -- sowle gen_context.ao_amount_commitment = amount_of_emitted_asset * gen_context.ao_asset_id_pt + gen_context.ao_amount_blinding_mask * crypto::c_point_G; - ado.amount_commitment = (crypto::c_scalar_1div8 * gen_context.ao_amount_commitment).to_public_key(); + ado.opt_amount_commitment = (crypto::c_scalar_1div8 * gen_context.ao_amount_commitment).to_public_key(); } else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN) { @@ -2298,10 +2302,10 @@ namespace currency amount_of_burned_assets -= item.amount; } } - ado.descriptor.current_supply -= amount_of_burned_assets; + ado.opt_amount = amount_of_burned_assets; // TODO: support hidden supply -- sowle gen_context.ao_amount_commitment = amount_of_burned_assets * gen_context.ao_asset_id_pt + gen_context.ao_amount_blinding_mask * crypto::c_point_G; - ado.amount_commitment = (crypto::c_scalar_1div8 * gen_context.ao_amount_commitment).to_public_key(); + ado.opt_amount_commitment = (crypto::c_scalar_1div8 * gen_context.ao_amount_commitment).to_public_key(); if (ftp.pevents_dispatcher) ftp.pevents_dispatcher->RAISE_DEBUG_EVENT(wde_construct_tx_handle_asset_descriptor_operation_before_burn{ &ado }); @@ -2325,10 +2329,10 @@ namespace currency item.asset_id = gen_context.ao_asset_id; } } - ado.descriptor.current_supply += amount_of_emitted_asset; + ado.opt_amount = amount_of_emitted_asset; // TODO: support hidden supply -- sowle gen_context.ao_amount_commitment = amount_of_emitted_asset * gen_context.ao_asset_id_pt + gen_context.ao_amount_blinding_mask * crypto::c_point_G; - ado.amount_commitment = (crypto::c_scalar_1div8 * gen_context.ao_amount_commitment).to_public_key(); + ado.opt_amount_commitment = (crypto::c_scalar_1div8 * gen_context.ao_amount_commitment).to_public_key(); } else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE) diff --git a/src/currency_core/currency_format_utils.h b/src/currency_core/currency_format_utils.h index f1706b54..2f4e1783 100644 --- a/src/currency_core/currency_format_utils.h +++ b/src/currency_core/currency_format_utils.h @@ -259,6 +259,7 @@ namespace currency const transaction& tx; const crypto::hash& tx_id; const asset_descriptor_operation& ado; + uint64_t height = UINT64_MAX; // default value means the height of the upcoming block (top_block + 1) crypto::public_key asset_id = currency::null_pkey; crypto::point_t asset_id_pt = crypto::c_point_0; uint64_t amount_to_validate = 0; diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 305bd6d4..afd527b3 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -1316,6 +1316,13 @@ namespace tools { WALLET_RPC_BEGIN_TRY_ENTRY(); + if (!currency::validate_asset_ticker_and_full_name(req.asset_descriptor)) + { + er.code = WALLET_RPC_ERROR_CODE_WRONG_ARGUMENT; + er.message = "asset ticker or full_name is invalid"; + return false; + } + std::vector currency_destinations; rpc_destinations_to_currency_destinations(req.destinations, true, !req.do_not_split_destinations, currency_destinations); From 09300903651efc735cd03e0cf844916a3a378b67 Mon Sep 17 00:00:00 2001 From: sowle Date: Fri, 18 Oct 2024 13:49:21 +0200 Subject: [PATCH 024/106] resurrected asset_owner_pub_key_v --- src/currency_core/currency_basic.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/currency_core/currency_basic.h b/src/currency_core/currency_basic.h index d2abec65..1c233c6a 100644 --- a/src/currency_core/currency_basic.h +++ b/src/currency_core/currency_basic.h @@ -698,7 +698,8 @@ namespace currency #define ASSET_DESCRIPTOR_BASE_STRUCTURE_LAST_VER 2 - struct dummy{ + struct dummy + { BEGIN_SERIALIZE() END_SERIALIZE() @@ -707,6 +708,7 @@ namespace currency }; typedef boost::variant asset_descriptor_base_etc_fields; + typedef boost::variant asset_owner_pub_key_v; struct asset_descriptor_base { From 1fe0cd17ad498d5d9f23072b3e5c578b859bc103 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sat, 19 Oct 2024 17:49:38 +0400 Subject: [PATCH 025/106] wallet2: implemented proper versioning in serialization asset_descriptor_* structs etc --- src/currency_core/currency_basic.h | 8 +- src/wallet/wallet2.cpp | 134 ++++++++++++++++++++--------- src/wallet/wallet2.h | 3 + 3 files changed, 98 insertions(+), 47 deletions(-) diff --git a/src/currency_core/currency_basic.h b/src/currency_core/currency_basic.h index 1c233c6a..6165495a 100644 --- a/src/currency_core/currency_basic.h +++ b/src/currency_core/currency_basic.h @@ -696,7 +696,7 @@ namespace currency } }; -#define ASSET_DESCRIPTOR_BASE_STRUCTURE_LAST_VER 2 +#define ASSET_DESCRIPTOR_BASE_LAST_VER 2 struct dummy { @@ -727,7 +727,7 @@ namespace currency std::vector etc; //container for future use if we would be adding some optional parameters that is not known yet, but without mess related to format version - BEGIN_VERSIONED_SERIALIZE(ASSET_DESCRIPTOR_BASE_STRUCTURE_LAST_VER, version) + BEGIN_VERSIONED_SERIALIZE(ASSET_DESCRIPTOR_BASE_LAST_VER, version) FIELD(total_max_supply) FIELD(current_supply) FIELD(decimal_point) @@ -838,7 +838,7 @@ namespace currency BOOST_SERIALIZE(opt_amount) BOOST_SERIALIZE(opt_asset_id_salt) BOOST_SERIALIZE(etc) - END_BOOST_SERIALIZATION_TOTAL_FIELDS(7) + END_BOOST_SERIALIZATION_TOTAL_FIELDS(8) BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(version) DOC_DSCR("Asset operation type struct version") DOC_EXMP(2) DOC_END @@ -1078,7 +1078,7 @@ namespace currency BOOST_SERIALIZE(attachment) BOOST_END_VERSION_UNDER(1) BOOST_SERIALIZE(proofs) - END_BOOST_SERIALIZATION_TOTAL_FIELDS(4) + END_BOOST_SERIALIZATION() }; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 138dc975..0205a33c 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -398,7 +398,7 @@ const crypto::public_key& wallet2::out_get_pub_key(const currency::tx_out_v& out void wallet2::process_ado_in_new_transaction(const currency::asset_descriptor_operation& ado, process_transaction_context& ptc) { auto print_ado_owner = [ado](std::ostream& o){ - ado.descriptor.owner_eth_pub_key.has_value() ? o << ado.descriptor.owner_eth_pub_key.value() << " (ETH)" : o << ado.descriptor.owner; + (ado.opt_descriptor.has_value() && ado.opt_descriptor->owner_eth_pub_key.has_value()) ? o << ado.opt_descriptor->owner_eth_pub_key.value() << " (ETH)" : o << ado.opt_descriptor->owner; }; do @@ -409,17 +409,19 @@ void wallet2::process_ado_in_new_transaction(const currency::asset_descriptor_op if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER) { + + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(ado.opt_descriptor.has_value(), "ado. missing opt_descriptor value at ASSET_DESCRIPTOR_OPERATION_REGISTER"); // Add an asset to ownership list if either: // 1) we're the owner of the asset; // or // 2) we spent native coins in the tx (i.e. we sent it) AND it registers an asset with third-party ownership. - if (ado.descriptor.owner != m_account.get_public_address().spend_public_key && - (!ado.descriptor.owner_eth_pub_key.has_value() || !ptc.spent_own_native_inputs)) + if (ado.opt_descriptor->owner != m_account.get_public_address().spend_public_key && + (!ado.opt_descriptor->owner_eth_pub_key.has_value() || !ptc.spent_own_native_inputs)) break; WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(m_own_asset_descriptors.count(asset_id) == 0, "asset with asset_id " << asset_id << " has already been registered in the wallet as own asset"); wallet_own_asset_context& asset_context = m_own_asset_descriptors[asset_id]; - epee::misc_utils::cast_assign_a_to_b(ado.descriptor, asset_context); + epee::misc_utils::cast_assign_a_to_b(*ado.opt_descriptor, asset_context); std::stringstream ss; ss << "New Asset Registered:" @@ -439,34 +441,39 @@ void wallet2::process_ado_in_new_transaction(const currency::asset_descriptor_op } else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT || ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN) { - auto it = m_own_asset_descriptors.find(asset_id); - if (it == m_own_asset_descriptors.end()) - break; + // do nothing on emit/burn + // + // + //auto it = m_own_asset_descriptors.find(asset_id); + //if (it == m_own_asset_descriptors.end()) + // break; //asset had been updated - add_rollback_event(ptc.height, asset_update_event{ it->first, it->second }); - epee::misc_utils::cast_assign_a_to_b(ado.descriptor, it->second); + //add_rollback_event(ptc.height, asset_update_event{ it->first, it->second }); + //epee::misc_utils::cast_assign_a_to_b(ado.opt_descriptor, it->second); } else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE) { + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(ado.opt_descriptor.has_value(), "ado. missing opt_descriptor value at ASSET_DESCRIPTOR_OPERATION_UPDATE"); + auto it = m_own_asset_descriptors.find(asset_id); if (it == m_own_asset_descriptors.end()) { - if (ado.descriptor.owner == m_account.get_public_address().spend_public_key) + if (ado.opt_descriptor->owner == m_account.get_public_address().spend_public_key) { // ownership of the asset acquired wallet_own_asset_context& asset_context = m_own_asset_descriptors[asset_id]; - epee::misc_utils::cast_assign_a_to_b(ado.descriptor, asset_context); + epee::misc_utils::cast_assign_a_to_b(*ado.opt_descriptor, asset_context); std::stringstream ss; ss << "Asset ownership acquired:" << ENDL << "asset id: " << asset_id - << ENDL << "Name: " << ado.descriptor.full_name - << ENDL << "Ticker: " << ado.descriptor.ticker - << ENDL << "Total Max Supply: " << print_asset_money(ado.descriptor.total_max_supply, ado.descriptor.decimal_point) - << ENDL << "Current Supply: " << print_asset_money(ado.descriptor.current_supply, ado.descriptor.decimal_point) - << ENDL << "Decimal Point: " << (int)ado.descriptor.decimal_point; + << ENDL << "Name: " << ado.opt_descriptor->full_name + << ENDL << "Ticker: " << ado.opt_descriptor->ticker + << ENDL << "Total Max Supply: " << print_asset_money(ado.opt_descriptor->total_max_supply, ado.opt_descriptor->decimal_point) + << ENDL << "Current Supply: " << print_asset_money(ado.opt_descriptor->current_supply, ado.opt_descriptor->decimal_point) + << ENDL << "Decimal Point: " << (int)ado.opt_descriptor->decimal_point; add_rollback_event(ptc.height, asset_register_event{ asset_id }); @@ -483,7 +490,7 @@ void wallet2::process_ado_in_new_transaction(const currency::asset_descriptor_op else { // check our ownership status: we lost it if the asset has new non-null owner (null means a third-party ownership, and in such a case we retain it in the own list whatever happens) - if (ado.descriptor.owner != null_pkey && ado.descriptor.owner != m_account.get_public_address().spend_public_key) + if (ado.opt_descriptor->owner != null_pkey && ado.opt_descriptor->owner != m_account.get_public_address().spend_public_key) { //ownership of the asset had been transfered add_rollback_event(ptc.height, asset_unown_event{ it->first, it->second }); @@ -493,11 +500,11 @@ void wallet2::process_ado_in_new_transaction(const currency::asset_descriptor_op ss << "Asset ownership lost:" << ENDL << "asset id: " << asset_id << ENDL << "New owner: " << print_ado_owner - << ENDL << "Name: " << ado.descriptor.full_name - << ENDL << "Ticker: " << ado.descriptor.ticker - << ENDL << "Total Max Supply: " << print_asset_money(ado.descriptor.total_max_supply, ado.descriptor.decimal_point) - << ENDL << "Current Supply: " << print_asset_money(ado.descriptor.current_supply, ado.descriptor.decimal_point) - << ENDL << "Decimal Point: " << (int)ado.descriptor.decimal_point; + << ENDL << "Name: " << ado.opt_descriptor->full_name + << ENDL << "Ticker: " << ado.opt_descriptor->ticker + << ENDL << "Total Max Supply: " << print_asset_money(ado.opt_descriptor->total_max_supply, ado.opt_descriptor->decimal_point) + << ENDL << "Current Supply: " << print_asset_money(ado.opt_descriptor->current_supply, ado.opt_descriptor->decimal_point) + << ENDL << "Decimal Point: " << (int)ado.opt_descriptor->decimal_point; add_rollback_event(ptc.height, asset_register_event{ asset_id }); WLT_LOG_MAGENTA(ss.str(), LOG_LEVEL_0); @@ -508,7 +515,7 @@ void wallet2::process_ado_in_new_transaction(const currency::asset_descriptor_op { //just an update of the asset add_rollback_event(ptc.height, asset_update_event{ it->first, it->second }); - epee::misc_utils::cast_assign_a_to_b(ado.descriptor, it->second); + epee::misc_utils::cast_assign_a_to_b(*ado.opt_descriptor, it->second); } } } @@ -966,7 +973,7 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t //check if there are asset_registration that belong to this wallet const asset_descriptor_operation* pado = get_type_in_variant_container(tx.extra); - if (pado && (ptc.employed_entries.receive.size() || ptc.employed_entries.spent.size() || pado->descriptor.owner == m_account.get_public_address().spend_public_key)) + if (pado && (ptc.employed_entries.receive.size() || ptc.employed_entries.spent.size() || (pado->opt_descriptor.has_value() && pado->opt_descriptor->owner == m_account.get_public_address().spend_public_key))) { //check if there are asset_registration that belong to this wallet process_ado_in_new_transaction(*pado, ptc); @@ -5506,12 +5513,38 @@ void wallet2::request_alias_registration(currency::extra_alias_entry& ai, curren transfer(destinations, 0, 0, fee, extra, attachments, get_current_split_strategy(), tx_dust_policy(DEFAULT_DUST_THRESHOLD), res_tx, CURRENCY_TO_KEY_OUT_RELAXED, false); } //---------------------------------------------------------------------------------------------------- +void wallet2::fill_ado_version_based_onhardfork(currency::asset_descriptor_operation& asset_reg_info) +{ + if (!is_in_hardfork_zone(ZANO_HARDFORK_05)) + { + asset_reg_info.version = 1; + } + else + { + asset_reg_info.version = ASSET_DESCRIPTOR_OPERATION_LAST_VER; + } +} +//---------------------------------------------------------------------------------------------------- +void wallet2::fill_adb_version_based_onhardfork(currency::asset_descriptor_base& asset_base) +{ + if (!is_in_hardfork_zone(ZANO_HARDFORK_05)) + { + asset_base.version = 0; + } + else + { + asset_base.version = ASSET_DESCRIPTOR_BASE_LAST_VER; + } +} +//---------------------------------------------------------------------------------------------------- void wallet2::deploy_new_asset(const currency::asset_descriptor_base& asset_info, const std::vector& destinations, currency::finalized_tx& ft, crypto::public_key& new_asset_id) { WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(asset_info.decimal_point <= 18, "too big decimal point: " << (int)asset_info.decimal_point); asset_descriptor_operation asset_reg_info{}; - asset_reg_info.descriptor = asset_info; + fill_ado_version_based_onhardfork(asset_reg_info); + asset_reg_info.opt_descriptor = asset_info; + fill_adb_version_based_onhardfork(*asset_reg_info.opt_descriptor); asset_reg_info.operation_type = ASSET_DESCRIPTOR_OPERATION_REGISTER; construct_tx_param ctp = get_default_construct_tx_param(); ctp.dsts = destinations; @@ -5526,7 +5559,7 @@ void wallet2::deploy_new_asset(const currency::asset_descriptor_base& asset_info CHECK_AND_ASSERT_THROW_MES(r, "asset_descriptor_operation cannot be found in tx extra as expected"); CHECK_AND_ASSERT_THROW_MES(get_or_calculate_asset_id(ado, nullptr, &new_asset_id), "get_or_calculate_asset_id failed"); - m_custom_assets[new_asset_id] = ado.descriptor; + m_custom_assets[new_asset_id] = *ado.opt_descriptor; } //---------------------------------------------------------------------------------------------------- void wallet2::deploy_new_asset(const currency::asset_descriptor_base& asset_info, const std::vector& destinations, currency::transaction& result_tx, crypto::public_key& new_asset_id) @@ -5540,12 +5573,19 @@ void wallet2::emit_asset(const crypto::public_key& asset_id, const std::vectorsecond.thirdparty_custody || own_asset_entry_it->second.owner_eth_pub_key.has_value() ) { send_to_network = false; ctp.additional_transfer_flags_to_mark = WALLET_TRANSFER_DETAIL_FLAG_ASSET_OP_RESERVATION; - ctp.tx_meaning_for_logs = "asset eth emission"; + ctp.tx_meaning_for_logs = "asset thirdparty/eth emission"; } this->transfer(ctp, ft, send_to_network, nullptr); @@ -5581,7 +5621,9 @@ void wallet2::update_asset(const crypto::public_key& asset_id, const currency::a CHECK_AND_ASSERT_THROW_MES(r, "Failed to get asset info from daemon"); asset_descriptor_operation asset_update_info{}; - asset_update_info.descriptor = new_descriptor; + fill_ado_version_based_onhardfork(asset_update_info); + asset_update_info.opt_descriptor = new_descriptor; + fill_adb_version_based_onhardfork(*asset_update_info.opt_descriptor); asset_update_info.operation_type = ASSET_DESCRIPTOR_OPERATION_UPDATE; asset_update_info.opt_asset_id = asset_id; construct_tx_param ctp = get_default_construct_tx_param(); @@ -5616,14 +5658,16 @@ void wallet2::transfer_asset_ownership(const crypto::public_key& asset_id, const CHECK_AND_ASSERT_THROW_MES(r, "Failed to get asset info from daemon"); asset_descriptor_operation asset_update_info{}; - asset_update_info.descriptor = last_adb; + fill_ado_version_based_onhardfork(asset_update_info); + asset_update_info.opt_descriptor = last_adb; + fill_adb_version_based_onhardfork(*asset_update_info.opt_descriptor); asset_update_info.operation_type = ASSET_DESCRIPTOR_OPERATION_UPDATE; asset_update_info.opt_asset_id = asset_id; if (new_owner_v.type() == typeid(crypto::public_key)) - asset_update_info.descriptor.owner = boost::get(new_owner_v); + asset_update_info.opt_descriptor->owner = boost::get(new_owner_v); else - asset_update_info.descriptor.owner_eth_pub_key = boost::get(new_owner_v); + asset_update_info.opt_descriptor->owner_eth_pub_key = boost::get(new_owner_v); construct_tx_param ctp = get_default_construct_tx_param(); ctp.extra.push_back(asset_update_info); @@ -5649,15 +5693,19 @@ void wallet2::transfer_asset_ownership(const crypto::public_key& asset_id, const //---------------------------------------------------------------------------------------------------- void wallet2::burn_asset(const crypto::public_key& asset_id, uint64_t amount_to_burn, currency::finalized_tx& ft, const std::vector& service_entries, const std::string& address_to_point/* = const std::string()*/, uint64_t native_amount_to_point/* = 0*/) { - currency::asset_descriptor_base last_adb{}; - bool r = this->daemon_get_asset_info(asset_id, last_adb); - CHECK_AND_ASSERT_THROW_MES(r, "Failed to get asset info from daemon"); - asset_descriptor_operation asset_burn_info{}; - asset_burn_info.descriptor = last_adb; - - CHECK_AND_ASSERT_THROW_MES(last_adb.current_supply >= amount_to_burn, "amount_to_burn is incorrect: " << print_money_brief(amount_to_burn, last_adb.decimal_point) << ", current_supply: " << print_money_brief(last_adb.current_supply, last_adb.decimal_point)); + fill_ado_version_based_onhardfork(asset_burn_info); + if (!is_in_hardfork_zone(ZANO_HARDFORK_05)) + { + currency::asset_descriptor_base last_adb{}; + bool r = this->daemon_get_asset_info(asset_id, last_adb); + CHECK_AND_ASSERT_THROW_MES(r, "Failed to get asset info from daemon"); + asset_burn_info.opt_descriptor = last_adb; + fill_adb_version_based_onhardfork(*asset_burn_info.opt_descriptor); + CHECK_AND_ASSERT_THROW_MES(last_adb.current_supply >= amount_to_burn, "amount_to_burn is incorrect: " << print_money_brief(amount_to_burn, last_adb.decimal_point) << ", current_supply: " << print_money_brief(last_adb.current_supply, last_adb.decimal_point)); + } + currency::tx_destination_entry dst_to_burn{}; dst_to_burn.amount = amount_to_burn; dst_to_burn.asset_id = asset_id; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index c5403ab2..6853d011 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -752,6 +752,9 @@ namespace tools bool accept_ionic_swap_proposal(const std::string& raw_proposal, currency::transaction& result_tx); bool accept_ionic_swap_proposal(const wallet_public::ionic_swap_proposal& proposal, currency::transaction& result_tx); + void fill_ado_version_based_onhardfork(currency::asset_descriptor_operation& asset_reg_info); + void fill_adb_version_based_onhardfork(currency::asset_descriptor_base& asset_base); + // Signing and auth bool sign_buffer(const std::string& buff, crypto::signature& sig); bool validate_sign(const std::string& buff, const crypto::signature& sig, const crypto::public_key& pkey); From 844e5b823f472df9935c4911986a80abfd6e0d21 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sat, 19 Oct 2024 18:50:11 +0400 Subject: [PATCH 026/106] wallet2.cpp compilation fixed --- src/common/crypto_serialization.h | 11 +++++++++++ src/crypto/crypto-sugar.h | 1 - 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/common/crypto_serialization.h b/src/common/crypto_serialization.h index 481b3ff2..99b49354 100644 --- a/src/common/crypto_serialization.h +++ b/src/common/crypto_serialization.h @@ -313,5 +313,16 @@ namespace boost { a & reinterpret_cast(x); } + + //TODO: @sowle please add serialization here +#ifndef _DEBUG + static_assert(false, "todo") +#endif + template + inline void serialize(Archive& a, crypto::scalar_vec_t& x, const boost::serialization::version_type ver) + { + + } + } // namespace serialization } // namespace boost diff --git a/src/crypto/crypto-sugar.h b/src/crypto/crypto-sugar.h index 9dad0b4e..3a76fa59 100644 --- a/src/crypto/crypto-sugar.h +++ b/src/crypto/crypto-sugar.h @@ -1083,7 +1083,6 @@ namespace crypto make_random(); } - }; // scalar_vec_t From f3618e641892842f216b427359cb0e83fccf8fc3 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sat, 19 Oct 2024 21:33:42 +0400 Subject: [PATCH 027/106] construc_tx compilation fixed --- src/currency_core/currency_basic.h | 1 + src/currency_core/currency_format_utils.cpp | 27 +++++++++++++++++---- src/currency_core/currency_format_utils.h | 5 +++- src/wallet/wallet2.cpp | 2 ++ src/wallet/wallet2_base.h | 3 +++ 5 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/currency_core/currency_basic.h b/src/currency_core/currency_basic.h index 6165495a..52f25be8 100644 --- a/src/currency_core/currency_basic.h +++ b/src/currency_core/currency_basic.h @@ -696,6 +696,7 @@ namespace currency } }; +#define ASSET_DESCRIPTOR_BASE_HF5_VER 2 #define ASSET_DESCRIPTOR_BASE_LAST_VER 2 struct dummy diff --git a/src/currency_core/currency_format_utils.cpp b/src/currency_core/currency_format_utils.cpp index 5765e7c0..a6b19db1 100644 --- a/src/currency_core/currency_format_utils.cpp +++ b/src/currency_core/currency_format_utils.cpp @@ -2302,8 +2302,17 @@ namespace currency amount_of_burned_assets -= item.amount; } } - ado.opt_amount = amount_of_burned_assets; // TODO: support hidden supply -- sowle - + if (ado.version < ASSET_DESCRIPTOR_BASE_HF5_VER) + { + CHECK_AND_ASSERT_THROW_MES(ado.opt_descriptor.has_value(), "Internal error: opt_descriptor unset during ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN for version less then 2"); + ado.opt_descriptor->current_supply -= amount_of_burned_assets; + } + else + { + CHECK_AND_ASSERT_THROW_MES(!ado.opt_descriptor.has_value(), "Internal error: opt_descriptor unset during ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN for version less then 2"); + ado.opt_amount = amount_of_burned_assets; // TODO: support hidden supply -- sowle + } + gen_context.ao_amount_commitment = amount_of_burned_assets * gen_context.ao_asset_id_pt + gen_context.ao_amount_blinding_mask * crypto::c_point_G; ado.opt_amount_commitment = (crypto::c_scalar_1div8 * gen_context.ao_amount_commitment).to_public_key(); @@ -2329,7 +2338,16 @@ namespace currency item.asset_id = gen_context.ao_asset_id; } } - ado.opt_amount = amount_of_emitted_asset; // TODO: support hidden supply -- sowle + if (ado.version < ASSET_DESCRIPTOR_BASE_HF5_VER) + { + CHECK_AND_ASSERT_THROW_MES(ado.opt_descriptor.has_value(), "Internal error: opt_descriptor unset during ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN for version less then 2"); + ado.opt_descriptor->current_supply += amount_of_emitted_asset; + } + else + { + CHECK_AND_ASSERT_THROW_MES(!ado.opt_descriptor.has_value(), "Internal error: opt_descriptor unset during ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN for version less then 2"); + ado.opt_amount = amount_of_emitted_asset; // TODO: support hidden supply -- sowle + } gen_context.ao_amount_commitment = amount_of_emitted_asset * gen_context.ao_asset_id_pt + gen_context.ao_amount_blinding_mask * crypto::c_point_G; ado.opt_amount_commitment = (crypto::c_scalar_1div8 * gen_context.ao_amount_commitment).to_public_key(); @@ -2778,8 +2796,7 @@ namespace currency const asset_descriptor_operation* pado = get_type_in_variant_container(tx.extra); if (pado != nullptr) { - if ((pado->operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT || pado->operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE) && - !pado->descriptor.owner_eth_pub_key.has_value()) + if ((pado->operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT || pado->operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE) && (!ftp.ado_sign_thirdparty)) { asset_operation_ownership_proof aoop{}; r = crypto::generate_schnorr_sig(tx_prefix_hash, sender_account_keys.spend_secret_key, aoop.gss); diff --git a/src/currency_core/currency_format_utils.h b/src/currency_core/currency_format_utils.h index 2f4e1783..f96210a6 100644 --- a/src/currency_core/currency_format_utils.h +++ b/src/currency_core/currency_format_utils.h @@ -29,6 +29,7 @@ #include "currency_format_utils_transactions.h" #include "core_runtime_config.h" #include "wallet/wallet_public_structs_defs.h" +#include "wallet/wallet_public_structs_defs.h" #include "bc_attachments_helpers.h" #include "bc_payments_id_service.h" #include "bc_offers_service_basic.h" @@ -164,9 +165,11 @@ namespace currency uint64_t tx_version; uint64_t mode_separate_fee = 0; - epee::misc_utils::events_dispatcher* pevents_dispatcher; + epee::misc_utils::events_dispatcher* pevents_dispatcher = nullptr; tx_generation_context gen_context{}; // solely for consolidated txs + + bool ado_sign_thirdparty = false;//@#@ TODO: add to serialization map @zoidberg BEGIN_SERIALIZE_OBJECT() diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 0205a33c..26976ecb 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -5600,6 +5600,7 @@ void wallet2::emit_asset(const crypto::public_key& asset_id, const std::vectortransfer(ctp, ft, send_to_network, nullptr); @@ -7949,6 +7950,7 @@ bool wallet2::prepare_transaction(construct_tx_param& ctp, currency::finalize_tx ftp.flags = ctp.flags; ftp.multisig_id = ctp.multisig_id; ftp.spend_pub_key = m_account.get_public_address().spend_public_key; + ftp.ado_sign_thirdparty = ctp.ado_sign_thirdparty; /* TODO WLT_LOG_GREEN("[prepare_transaction]: get_needed_money_time: " << get_needed_money_time << " ms" diff --git a/src/wallet/wallet2_base.h b/src/wallet/wallet2_base.h index 649909e8..4c88dc58 100644 --- a/src/wallet/wallet2_base.h +++ b/src/wallet/wallet2_base.h @@ -229,6 +229,9 @@ namespace tools // misc std::string tx_meaning_for_logs; // used to correctly log things, e.g. "escrow" or "asset emission". uint32_t additional_transfer_flags_to_mark = 0; + + //ado + bool ado_sign_thirdparty = false; }; struct mode_separate_context From 9dc293ec04a0354b5714acd2bb47ac606b803541 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sat, 19 Oct 2024 22:53:43 +0400 Subject: [PATCH 028/106] core and wallet fixed, coretests still broken --- src/currency_core/blockchain_storage.cpp | 31 ++++++++++++++++-------- src/currency_core/blockchain_storage.h | 3 ++- src/currency_core/tx_pool.cpp | 2 +- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/currency_core/blockchain_storage.cpp b/src/currency_core/blockchain_storage.cpp index 49455e7f..71d8f20c 100644 --- a/src/currency_core/blockchain_storage.cpp +++ b/src/currency_core/blockchain_storage.cpp @@ -4270,7 +4270,7 @@ bool blockchain_storage::validate_asset_operation_hf4(asset_op_verification_cont return true; } //------------------------------------------------------------------ -bool blockchain_storage::validate_asset_operation(asset_op_verification_context& avc) const +bool blockchain_storage::validate_asset_operation_hf5(asset_op_verification_context& avc) const { CRITICAL_REGION_LOCAL(m_read_lock); CHECK_AND_ASSERT_MES(is_hardfork_active_for_height(ZANO_HARDFORK_05, avc.height), false, "validate_asset_operation was called before HF5"); @@ -4316,9 +4316,9 @@ bool blockchain_storage::validate_asset_operation(asset_op_verification_context& } else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT) { - CHECK_AND_ASSERT_MES(ado.descriptor.current_supply > last_adb.current_supply, false, "emit operation does not increase the current supply, failed"); - CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, last_adb), false, "emit operation modifies asset descriptor in a prohibited manner"); - CHECK_AND_ASSERT_MES(ado.descriptor.meta_info == last_adb.meta_info, false, "emit operation is not allowed to update meta info"); + //CHECK_AND_ASSERT_MES(ado.descriptor.current_supply > last_adb.current_supply, false, "emit operation does not increase the current supply, failed"); + //CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, last_adb), false, "emit operation modifies asset descriptor in a prohibited manner"); + //CHECK_AND_ASSERT_MES(ado.descriptor.meta_info == last_adb.meta_info, false, "emit operation is not allowed to update meta info"); if (!last_adb.hidden_supply) { @@ -4328,9 +4328,9 @@ bool blockchain_storage::validate_asset_operation(asset_op_verification_context& } else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN) { - CHECK_AND_ASSERT_MES(ado.descriptor.current_supply < last_adb.current_supply, false, "burn operation does not decrease the current supply, failed"); - CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, last_adb), false, "burn operation modifies asset descriptor in a prohibited manner"); - CHECK_AND_ASSERT_MES(ado.descriptor.meta_info == last_adb.meta_info, false, "burn operation is not allowed to update meta info"); + //CHECK_AND_ASSERT_MES(ado.descriptor.current_supply < last_adb.current_supply, false, "burn operation does not decrease the current supply, failed"); + //CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, last_adb), false, "burn operation modifies asset descriptor in a prohibited manner"); + //CHECK_AND_ASSERT_MES(ado.descriptor.meta_info == last_adb.meta_info, false, "burn operation is not allowed to update meta info"); if (!last_adb.hidden_supply) { @@ -4354,14 +4354,27 @@ bool blockchain_storage::validate_asset_operation(asset_op_verification_context& return true; } //------------------------------------------------------------------ +bool blockchain_storage::validate_asset_operation(asset_op_verification_context& avc, uint64_t height) const +{ + if (is_hardfork_active_for_height(ZANO_HARDFORK_05, height)) + { + return validate_asset_operation_hf5(avc); + } + else + { + return validate_asset_operation_hf4(avc); + } +} +//------------------------------------------------------------------ bool blockchain_storage::put_asset_info(const transaction& tx, const crypto::hash& tx_id, const asset_descriptor_operation& ado, const uint64_t height) { CRITICAL_REGION_LOCAL(m_read_lock); asset_op_verification_context avc = { tx, tx_id, ado, height }; + CHECK_AND_ASSERT_MES(validate_asset_operation(avc, height), false, "asset operation validation failed (HF5)"); + if (is_hardfork_active_for_height(ZANO_HARDFORK_05, height)) { - CHECK_AND_ASSERT_MES(validate_asset_operation(avc), false, "asset operation validation failed (HF5)"); // NEW HF5 handling here assets_container::t_value_type local_asset_history{}; @@ -4420,8 +4433,6 @@ bool blockchain_storage::put_asset_info(const transaction& tx, const crypto::has else { // HF4 - CHECK_AND_ASSERT_MES(validate_asset_operation_hf4(avc), false, "asset operation validation failed (HF4)"); - assets_container::t_value_type local_asset_history{}; if (avc.asset_op_history) local_asset_history = *avc.asset_op_history; diff --git a/src/currency_core/blockchain_storage.h b/src/currency_core/blockchain_storage.h index e6b30edf..78c75e66 100644 --- a/src/currency_core/blockchain_storage.h +++ b/src/currency_core/blockchain_storage.h @@ -382,7 +382,8 @@ namespace currency uint64_t split_height = 0)const; bool validate_ado_ownership(asset_op_verification_context& avc) const; bool validate_asset_operation_hf4(asset_op_verification_context& avc) const; - bool validate_asset_operation(asset_op_verification_context& avc) const; + bool validate_asset_operation_hf5(asset_op_verification_context& avc) const; + bool validate_asset_operation(asset_op_verification_context& avc, uint64_t height) const; void set_core_runtime_config(const core_runtime_config& pc) const; const core_runtime_config& get_core_runtime_config()const; diff --git a/src/currency_core/tx_pool.cpp b/src/currency_core/tx_pool.cpp index 33315870..d076d147 100644 --- a/src/currency_core/tx_pool.cpp +++ b/src/currency_core/tx_pool.cpp @@ -247,7 +247,7 @@ namespace currency r = process_type_in_variant_container_and_make_sure_its_unique(tx.extra, [&](const asset_descriptor_operation& ado){ asset_op_verification_context avc = { tx, id, ado }; - return m_blockchain.validate_asset_operation_against_current_blochain_state(avc); + return m_blockchain.validate_asset_operation(avc, m_blockchain.get_current_blockchain_size()); }, true); CHECK_AND_ASSERT_MES_CUSTOM(r, false, { tvc.m_verification_failed = true; }, "post-HF4 tx: asset operation is invalid"); } From 60423e71b8bc232538b5b28ad8cddfb26b03e8ec Mon Sep 17 00:00:00 2001 From: sowle Date: Sun, 20 Oct 2024 01:57:30 +0200 Subject: [PATCH 029/106] boost serialization for crypto::scalar_vec_t and scalar_mat_t --- src/common/crypto_serialization.h | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/common/crypto_serialization.h b/src/common/crypto_serialization.h index 99b49354..21845ae9 100644 --- a/src/common/crypto_serialization.h +++ b/src/common/crypto_serialization.h @@ -313,15 +313,17 @@ namespace boost { a & reinterpret_cast(x); } - - //TODO: @sowle please add serialization here -#ifndef _DEBUG - static_assert(false, "todo") -#endif template inline void serialize(Archive& a, crypto::scalar_vec_t& x, const boost::serialization::version_type ver) { - + static_assert(sizeof(std::vector) == sizeof(crypto::scalar_vec_t)); + a & static_cast&>(x); + } + template + inline void serialize(Archive& a, crypto::scalar_mat_t& x, const boost::serialization::version_type ver) + { + static_assert(sizeof(std::vector) == sizeof(crypto::scalar_mat_t)); + a & static_cast&>(x); } } // namespace serialization From 2dd029fb5d702540449df7857a7c6990da9213c1 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sun, 20 Oct 2024 15:57:25 +0400 Subject: [PATCH 030/106] fixed multiassets_test.cpp --- contrib/epee/include/misc_log_ex.h | 2 +- src/currency_core/currency_basic.h | 11 ++-- src/wallet/wallet2.cpp | 4 +- tests/core_tests/multiassets_test.cpp | 92 +++++++++++++++++---------- 4 files changed, 68 insertions(+), 41 deletions(-) diff --git a/contrib/epee/include/misc_log_ex.h b/contrib/epee/include/misc_log_ex.h index 376af660..4f329ca4 100644 --- a/contrib/epee/include/misc_log_ex.h +++ b/contrib/epee/include/misc_log_ex.h @@ -90,7 +90,7 @@ DISABLE_VS_WARNINGS(4100) #define LOG_JOURNAL_MAX_ELEMENTS 100 #ifdef _DEBUG - #define _ASSERTE__(expr) if(!expr) {__debugbreak();} + #define _ASSERTE__(expr) if(!(expr)) {__debugbreak();} #else #define _ASSERTE__(expr) #endif diff --git a/src/currency_core/currency_basic.h b/src/currency_core/currency_basic.h index 52f25be8..b56b6385 100644 --- a/src/currency_core/currency_basic.h +++ b/src/currency_core/currency_basic.h @@ -696,8 +696,9 @@ namespace currency } }; -#define ASSET_DESCRIPTOR_BASE_HF5_VER 2 -#define ASSET_DESCRIPTOR_BASE_LAST_VER 2 +#define ASSET_DESCRIPTOR_BASE_HF4_VER 0 +#define ASSET_DESCRIPTOR_BASE_HF5_VER 2 +#define ASSET_DESCRIPTOR_BASE_LAST_VER 2 struct dummy { @@ -721,7 +722,7 @@ namespace currency std::string meta_info; crypto::public_key owner = currency::null_pkey; // consider premultipling by 1/8 bool hidden_supply = false; - uint8_t version = 0; + uint8_t version = ASSET_DESCRIPTOR_BASE_HF4_VER; //version 1 members boost::optional owner_eth_pub_key; // note: the size is 33 bytes (if present) // NOTE: using boost::optional instead of std::optional because of the Boost compilation issue: https://github.com/boostorg/serialization/issues/319 -- sowle //version 2 members @@ -798,6 +799,8 @@ namespace currency #define ASSET_DESCRIPTOR_OPERATION_UPDATE 3 #define ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN 4 +#define ASSET_DESCRIPTOR_OPERATION_HF4_VER 1 +#define ASSET_DESCRIPTOR_OPERATION_HF5_VER 2 #define ASSET_DESCRIPTOR_OPERATION_LAST_VER 2 typedef boost::variant asset_descriptor_operation_etc_fields; @@ -805,7 +808,7 @@ namespace currency struct asset_descriptor_operation { uint8_t operation_type = ASSET_DESCRIPTOR_OPERATION_UNDEFINED; - uint8_t version = 1; + uint8_t version = ASSET_DESCRIPTOR_OPERATION_HF4_VER; // register emit burn update boost::optional opt_amount_commitment; // + + + - (premultiplied by 1/8) boost::optional opt_asset_id; // - + + + diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 26976ecb..ce43b617 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -5517,7 +5517,7 @@ void wallet2::fill_ado_version_based_onhardfork(currency::asset_descriptor_opera { if (!is_in_hardfork_zone(ZANO_HARDFORK_05)) { - asset_reg_info.version = 1; + asset_reg_info.version = ASSET_DESCRIPTOR_OPERATION_HF4_VER; } else { @@ -5529,7 +5529,7 @@ void wallet2::fill_adb_version_based_onhardfork(currency::asset_descriptor_base& { if (!is_in_hardfork_zone(ZANO_HARDFORK_05)) { - asset_base.version = 0; + asset_base.version = ASSET_DESCRIPTOR_BASE_HF4_VER; } else { diff --git a/tests/core_tests/multiassets_test.cpp b/tests/core_tests/multiassets_test.cpp index 670787af..90f47c23 100644 --- a/tests/core_tests/multiassets_test.cpp +++ b/tests/core_tests/multiassets_test.cpp @@ -310,10 +310,19 @@ bool multiassets_basic_test::c1(currency::core& c, size_t ev_index, const std::v //miner_wlt->refresh(); + // check emit_asset() with modified 'current_supply' miner_wlt->get_debug_events_dispatcher().SUBSCIRBE_DEBUG_EVENT([&](const wde_construct_tx_handle_asset_descriptor_operation_before_seal& o) { - o.pado->descriptor.current_supply += 1000000; + if(o.pado->version < ASSET_DESCRIPTOR_OPERATION_HF5_VER) + { + //hf4 + o.pado->opt_descriptor->current_supply += 1000000; + } + else + { + *o.pado->opt_amount += 1000000; + } }); //test emit function but re-adjust current_supply to wrong amount r = false; @@ -345,7 +354,15 @@ bool multiassets_basic_test::c1(currency::core& c, size_t ev_index, const std::v miner_wlt->get_debug_events_dispatcher().SUBSCIRBE_DEBUG_EVENT([&](const wde_construct_tx_handle_asset_descriptor_operation_before_burn& o) { - o.pado->descriptor.current_supply -= 1000000; + if (o.pado->version < ASSET_DESCRIPTOR_OPERATION_HF5_VER) + { + //hf4 + o.pado->opt_descriptor->current_supply -= 1000000; + } + else + { + *o.pado->opt_amount -= 1000000; + } }); @@ -894,6 +911,7 @@ bool asset_emission_and_unconfirmed_balance::c1(currency::core& c, size_t ev_ind asset_operation_and_hardfork_checks::asset_operation_and_hardfork_checks() { + m_adb_hello.version = ASSET_DESCRIPTOR_BASE_HF4_VER; m_adb_hello.total_max_supply = 1'000'000'000'000'000'000; m_adb_hello.current_supply = 1'000'000'000'000'000'000; m_adb_hello.ticker = "HLO"; @@ -903,6 +921,7 @@ asset_operation_and_hardfork_checks::asset_operation_and_hardfork_checks() m_ado_hello.operation_type = ASSET_DESCRIPTOR_OPERATION_REGISTER; m_ado_hello.opt_asset_id = currency::null_pkey; + m_ado_hello.version = ASSET_DESCRIPTOR_OPERATION_HF4_VER; m_adb_bye.total_max_supply = 1'000'000'000'000'000'000; m_adb_bye.current_supply = 1'000'000'000'000'000'000; @@ -913,6 +932,7 @@ asset_operation_and_hardfork_checks::asset_operation_and_hardfork_checks() m_ado_bye.operation_type = ASSET_DESCRIPTOR_OPERATION_REGISTER; m_ado_hello.opt_asset_id = currency::null_pkey; + m_ado_hello.version = ASSET_DESCRIPTOR_OPERATION_HF4_VER; REGISTER_CALLBACK_METHOD(asset_operation_and_hardfork_checks, c1); REGISTER_CALLBACK_METHOD(asset_operation_and_hardfork_checks, c2); @@ -942,10 +962,10 @@ bool asset_operation_and_hardfork_checks::generate( alice.generate(); m_adb_hello.owner = alice.get_public_address().spend_public_key; - m_ado_hello.descriptor = m_adb_hello; + m_ado_hello.opt_descriptor = m_adb_hello; m_adb_bye.owner = alice.get_public_address().spend_public_key; - m_ado_bye.descriptor = m_adb_bye; + m_ado_bye.opt_descriptor = m_adb_bye; MAKE_GENESIS_BLOCK(events, blk_0, @@ -1212,6 +1232,7 @@ bool asset_operation_and_hardfork_checks::c2( asset_operation_in_consolidated_tx::asset_operation_in_consolidated_tx() { + m_adb_alice_currency.version = ASSET_DESCRIPTOR_BASE_HF4_VER; m_adb_alice_currency.total_max_supply = 1'000'000'000'000'000'000; m_adb_alice_currency.current_supply = 1'000'000'000'000'000'000; m_adb_alice_currency.ticker = "ALC"; @@ -1221,6 +1242,7 @@ asset_operation_in_consolidated_tx::asset_operation_in_consolidated_tx() m_ado_alice_currency.operation_type = ASSET_DESCRIPTOR_OPERATION_REGISTER; m_ado_alice_currency.opt_asset_id = currency::null_pkey; + m_ado_alice_currency.version = ASSET_DESCRIPTOR_OPERATION_HF4_VER; REGISTER_CALLBACK_METHOD(asset_operation_in_consolidated_tx, assert_balances); REGISTER_CALLBACK_METHOD(asset_operation_in_consolidated_tx, assert_alice_currency_not_registered); @@ -1242,7 +1264,7 @@ bool asset_operation_in_consolidated_tx::generate(std::vector& m_accounts.push_back(alice); m_accounts.push_back(bob); m_adb_alice_currency.owner = m_accounts.at(ALICE_ACC_IDX).get_public_address().spend_public_key; - m_ado_alice_currency.descriptor = m_adb_alice_currency; + m_ado_alice_currency.opt_descriptor = m_adb_alice_currency; MAKE_GENESIS_BLOCK(events, blk_0, miner, test_core_time::get_time()); DO_CALLBACK(events, "configure_core"); @@ -1805,8 +1827,8 @@ bool eth_signed_asset_via_rpc::c1(currency::core& c, size_t ev_index, const std: CHECK_AND_ASSERT_EQ(pado->operation_type, ASSET_DESCRIPTOR_OPERATION_UPDATE); CHECK_AND_ASSERT_EQ(pado->opt_asset_id.has_value(), true); CHECK_AND_ASSERT_EQ(pado->opt_asset_id.get(), asset_id); - CHECK_AND_ASSERT_EQ(pado->descriptor.owner_eth_pub_key.has_value(), true); - CHECK_AND_ASSERT_EQ(pado->descriptor.owner_eth_pub_key.get(), eth_pk_2); // the most important condition for an ownership transfer + CHECK_AND_ASSERT_EQ(pado->opt_descriptor->owner_eth_pub_key.has_value(), true); + CHECK_AND_ASSERT_EQ(pado->opt_descriptor->owner_eth_pub_key.get(), eth_pk_2); // the most important condition for an ownership transfer // other fileds of pado->descriptor may also be checked here // @@ -2007,12 +2029,13 @@ bool asset_current_and_total_supplies_comparative_constraints::generate(std::vec m_accounts.push_back(miner); m_accounts.push_back(alice); m_adbs.at(asset_position::alpha).owner = m_adbs.at(asset_position::beta).owner = m_adbs.at(asset_position::gamma).owner = alice.get_public_address().spend_public_key; - m_ados_register.at(asset_position::alpha).descriptor = m_adbs.at(asset_position::alpha); - m_ados_register.at(asset_position::beta).descriptor = m_ado_emit.descriptor = m_adbs.at(asset_position::beta); - m_ados_register.at(asset_position::gamma).descriptor = m_adbs.at(asset_position::gamma); - CHECK_AND_ASSERT(m_ado_emit.descriptor.current_supply <= m_ado_emit.descriptor.total_max_supply, false); - ++m_ado_emit.descriptor.current_supply; - CHECK_AND_ASSERT(m_ado_emit.descriptor.current_supply > m_ado_emit.descriptor.total_max_supply, false); + m_ados_register.at(asset_position::alpha).opt_descriptor = m_adbs.at(asset_position::alpha); + m_ados_register.at(asset_position::beta).opt_descriptor = m_ado_emit.opt_descriptor = m_adbs.at(asset_position::beta); + m_ados_register.at(asset_position::gamma).opt_descriptor = m_adbs.at(asset_position::gamma); + + CHECK_AND_ASSERT((*m_ado_emit.opt_descriptor).current_supply <= (*m_ado_emit.opt_descriptor).total_max_supply, false); + ++(m_ado_emit.opt_descriptor->current_supply); + CHECK_AND_ASSERT(m_ado_emit.opt_descriptor->current_supply > m_ado_emit.opt_descriptor->total_max_supply, false); MAKE_GENESIS_BLOCK(events, blk_0, miner, test_core_time::get_time()); DO_CALLBACK(events, "configure_core"); @@ -2043,9 +2066,9 @@ bool asset_current_and_total_supplies_comparative_constraints::generate(std::vec success = fill_tx_sources_and_destinations(events, top, alice.get_keys(), alice.get_public_address(), MK_TEST_COINS(2), TESTS_DEFAULT_FEE, 0, sources, destinations); CHECK_AND_ASSERT_EQ(success, true); - destinations.emplace_back(ado.descriptor.current_supply, alice.get_public_address(), null_pkey); - CHECK_AND_ASSERT_EQ(ado.descriptor.total_max_supply, 0); - CHECK_AND_ASSERT_EQ(ado.descriptor.total_max_supply, ado.descriptor.current_supply); + destinations.emplace_back(ado.opt_descriptor->current_supply, alice.get_public_address(), null_pkey); + CHECK_AND_ASSERT_EQ(ado.opt_descriptor->total_max_supply, 0); + CHECK_AND_ASSERT_EQ(ado.opt_descriptor->total_max_supply, ado.opt_descriptor->current_supply); success = construct_tx(alice.get_keys(), sources, destinations, {ado}, empty_attachment, tx_1, get_tx_version(get_block_height(top), m_hardforks), one_time, 0); CHECK_AND_ASSERT_EQ(success, true); } @@ -2066,8 +2089,8 @@ bool asset_current_and_total_supplies_comparative_constraints::generate(std::vec success = fill_tx_sources_and_destinations(events, top, alice.get_keys(), alice.get_public_address(), MK_TEST_COINS(2), TESTS_DEFAULT_FEE, 0, sources, destinations); CHECK_AND_ASSERT_EQ(success, true); - destinations.emplace_back(ado.descriptor.current_supply, alice.get_public_address(), null_pkey); - CHECK_AND_ASSERT_MES(ado.descriptor.current_supply > ado.descriptor.total_max_supply, false, "current_supply <= total_max_supply"); + destinations.emplace_back(ado.opt_descriptor->current_supply, alice.get_public_address(), null_pkey); + CHECK_AND_ASSERT_MES(ado.opt_descriptor->current_supply > ado.opt_descriptor->total_max_supply, false, "current_supply <= total_max_supply"); success = construct_tx(alice.get_keys(), sources, destinations, {ado}, empty_attachment, tx_2, get_tx_version(get_block_height(top), m_hardforks), one_time, 0); CHECK_AND_ASSERT_EQ(success, true); } @@ -2088,8 +2111,8 @@ bool asset_current_and_total_supplies_comparative_constraints::generate(std::vec success = fill_tx_sources_and_destinations(events, top, alice.get_keys(), alice.get_public_address(), MK_TEST_COINS(2), TESTS_DEFAULT_FEE, 0, sources, destinations); CHECK_AND_ASSERT_EQ(success, true); - destinations.emplace_back(ado.descriptor.current_supply, alice.get_public_address(), null_pkey); - CHECK_AND_ASSERT(ado.descriptor.current_supply <= ado.descriptor.total_max_supply, false); + destinations.emplace_back(ado.opt_descriptor->current_supply, alice.get_public_address(), null_pkey); + CHECK_AND_ASSERT(ado.opt_descriptor->current_supply <= ado.opt_descriptor->total_max_supply, false); success = construct_tx(alice.get_keys(), sources, destinations, {ado}, empty_attachment, tx_3, get_tx_version(get_block_height(top), m_hardforks), one_time, 0); CHECK_AND_ASSERT_EQ(success, true); } @@ -2108,7 +2131,7 @@ bool asset_current_and_total_supplies_comparative_constraints::generate(std::vec m_ado_emit.opt_asset_id = beta_asset_id; } - CHECK_AND_ASSERT_GREATER(m_ado_emit.descriptor.current_supply, m_ado_emit.descriptor.total_max_supply); + CHECK_AND_ASSERT_GREATER(m_ado_emit.opt_descriptor->current_supply, m_ado_emit.opt_descriptor->total_max_supply); // Alice emits asset BETA. The emission is performed through the wallet object. There is no emission, because .current_supply > .total_max_supply in the asset base descriptor. DO_CALLBACK(events, "emit_asset_beta_with_incorrect_supply"); @@ -2126,15 +2149,15 @@ bool asset_current_and_total_supplies_comparative_constraints::generate(std::vec success = fill_tx_sources_and_destinations(events, top, alice.get_keys(), alice.get_public_address(), MK_TEST_COINS(2), TESTS_DEFAULT_FEE, 0, sources, destinations); CHECK_AND_ASSERT_EQ(success, true); - CHECK_AND_ASSERT_GREATER(m_ado_emit.descriptor.current_supply, ado_register.descriptor.current_supply); - destinations.emplace_back(m_ado_emit.descriptor.current_supply - ado_register.descriptor.current_supply, alice.get_public_address(), null_pkey); + CHECK_AND_ASSERT_GREATER(m_ado_emit.opt_descriptor->current_supply, ado_register.opt_descriptor->current_supply); + destinations.emplace_back(m_ado_emit.opt_descriptor->current_supply - ado_register.opt_descriptor->current_supply, alice.get_public_address(), null_pkey); ftp.sources = sources; ftp.prepared_destinations = destinations; ftp.tx_version = get_tx_version(get_block_height(top), m_hardforks); ftp.extra = {m_ado_emit}; ftp.shuffle = true; - CHECK_AND_ASSERT_GREATER(m_ado_emit.descriptor.current_supply, m_ado_emit.descriptor.total_max_supply); + CHECK_AND_ASSERT_GREATER(m_ado_emit.opt_descriptor->current_supply, m_ado_emit.opt_descriptor->total_max_supply); success = construct_tx(alice.get_keys(), ftp, ftx); CHECK_AND_ASSERT_EQ(success, true); tx_4 = ftx.tx; @@ -2153,7 +2176,7 @@ bool asset_current_and_total_supplies_comparative_constraints::assert_asset_alph { const std::shared_ptr alice_wallet{init_playtime_test_wallet_t(events, c, ALICE_ACC_IDX)}; crypto::public_key alpha_asset_id{}; - const std::string ticker{m_ados_register.at(asset_position::alpha).descriptor.ticker}; + const std::string ticker{m_ados_register.at(asset_position::alpha).opt_descriptor->ticker}; alice_wallet->refresh(); @@ -2176,7 +2199,7 @@ bool asset_current_and_total_supplies_comparative_constraints::assert_asset_beta { const std::shared_ptr alice_wallet{init_playtime_test_wallet_t(events, c, ALICE_ACC_IDX)}; crypto::public_key key_beta_asset_id{}; - const std::string ticker{m_ados_register.at(asset_position::beta).descriptor.ticker}; + const std::string ticker{m_ados_register.at(asset_position::beta).opt_descriptor->ticker}; alice_wallet->refresh(); @@ -2220,11 +2243,11 @@ bool asset_current_and_total_supplies_comparative_constraints::emit_asset_beta_w { const auto& ado_register{m_ados_register.at(asset_position::beta)}; - CHECK_AND_ASSERT_GREATER(m_ado_emit.descriptor.current_supply, ado_register.descriptor.current_supply); - destinations.emplace_back(m_ado_emit.descriptor.current_supply - ado_register.descriptor.current_supply, alice_wallet->get_account().get_public_address(), beta_asset_id); + CHECK_AND_ASSERT_GREATER(m_ado_emit.opt_descriptor->current_supply, ado_register.opt_descriptor->current_supply); + destinations.emplace_back(m_ado_emit.opt_descriptor->current_supply - ado_register.opt_descriptor->current_supply, alice_wallet->get_account().get_public_address(), beta_asset_id); } - CHECK_AND_ASSERT_GREATER(m_ado_emit.descriptor.current_supply, m_ado_emit.descriptor.total_max_supply); + CHECK_AND_ASSERT_GREATER(m_ado_emit.opt_descriptor->current_supply, m_ado_emit.opt_descriptor->total_max_supply); try { @@ -2260,10 +2283,11 @@ bool asset_current_and_total_supplies_comparative_constraints::assert_asset_beta } { - const uint64_t& current_supply{register_ado.descriptor.current_supply}; + const uint64_t& current_supply{register_ado.opt_descriptor->current_supply}; - CHECK_AND_ASSERT_MES(alice_wallet->balance(beta_asset_id) == current_supply, false, "Alice has got not exactly " + std::to_string(current_supply) + ' ' + register_ado.descriptor.ticker); + CHECK_AND_ASSERT_MES(alice_wallet->balance(beta_asset_id) == current_supply, false, "Alice has got not exactly " + std::to_string(current_supply) + ' ' + register_ado.opt_descriptor->ticker); } + return true; } bool asset_current_and_total_supplies_comparative_constraints::public_burn_asset_beta_with_incorrect_supply(currency::core& c, size_t ev_index, const std::vector& events) const @@ -2283,7 +2307,7 @@ bool asset_current_and_total_supplies_comparative_constraints::public_burn_asset { transaction tx{}; - alice_wallet->burn_asset(beta_asset_id, m_ado_emit.descriptor.current_supply, tx); + alice_wallet->burn_asset(beta_asset_id, m_ado_emit.opt_descriptor->current_supply, tx); } catch (const std::runtime_error&) { @@ -2298,7 +2322,7 @@ bool asset_current_and_total_supplies_comparative_constraints::assert_asset_gamm { const std::shared_ptr alice_wallet{init_playtime_test_wallet_t(events, c, ALICE_ACC_IDX)}; crypto::public_key key_gamma_asset_id{}; - const std::string ticker{m_ados_register.at(asset_position::gamma).descriptor.ticker}; + const std::string ticker{m_ados_register.at(asset_position::gamma).opt_descriptor->ticker}; alice_wallet->refresh(); @@ -2314,7 +2338,7 @@ bool asset_current_and_total_supplies_comparative_constraints::assert_asset_gamm CHECK_AND_ASSERT_MES(c.get_blockchain_storage().get_asset_info(key_gamma_asset_id, gamma_adb), false, "the asset " + ticker + " must be registered"); } - CHECK_AND_ASSERT_EQ(alice_wallet->balance(key_gamma_asset_id), m_ados_register.at(asset_position::gamma).descriptor.current_supply); + CHECK_AND_ASSERT_EQ(alice_wallet->balance(key_gamma_asset_id), m_ados_register.at(asset_position::gamma).opt_descriptor->current_supply); return true; } From 97b145f8b690a4d06c3d905c8bf753fa411647d6 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sun, 20 Oct 2024 23:08:44 +0400 Subject: [PATCH 031/106] fixes in a coretests related to ado-operations --- .../currency_basic_backward_comp.inl | 4 +- src/currency_core/currency_format_utils.cpp | 5 +- tests/core_tests/multiassets_test.cpp | 61 ++++++++++++++++++- 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/src/currency_core/currency_basic_backward_comp.inl b/src/currency_core/currency_basic_backward_comp.inl index 6ce249ee..ef024377 100644 --- a/src/currency_core/currency_basic_backward_comp.inl +++ b/src/currency_core/currency_basic_backward_comp.inl @@ -121,7 +121,7 @@ struct asset_descriptor_operation_v1 { uint8_t operation_type = ASSET_DESCRIPTOR_OPERATION_UNDEFINED; asset_descriptor_base descriptor; - crypto::public_key amount_commitment; // premultiplied by 1/8 + crypto::public_key amount_commitment = currency::null_pkey; // premultiplied by 1/8 boost::optional opt_asset_id; // target asset_id - for update/emit uint8_t verion = 1; @@ -164,7 +164,7 @@ bool transition_convert(const asset_descriptor_operation_t& from, asset_descript } else { - throw std::runtime_error(std::string("Unexpected: missing amount_commitment in from transaction_current_t")); + //not used over update operations //throw std::runtime_error(std::string("Unexpected: missing amount_commitment in from transaction_current_t")); } to.opt_asset_id = from.opt_asset_id; diff --git a/src/currency_core/currency_format_utils.cpp b/src/currency_core/currency_format_utils.cpp index a6b19db1..ef6a4144 100644 --- a/src/currency_core/currency_format_utils.cpp +++ b/src/currency_core/currency_format_utils.cpp @@ -2272,7 +2272,10 @@ namespace currency } } adb.current_supply = amount_of_emitted_asset; - ado.opt_amount = amount_of_emitted_asset; // TODO: support hidden supply -- sowle + if (ado.version >= ASSET_DESCRIPTOR_BASE_HF5_VER) + { + ado.opt_amount = amount_of_emitted_asset; // TODO: support hidden supply -- sowle + } gen_context.ao_amount_commitment = amount_of_emitted_asset * gen_context.ao_asset_id_pt + gen_context.ao_amount_blinding_mask * crypto::c_point_G; ado.opt_amount_commitment = (crypto::c_scalar_1div8 * gen_context.ao_amount_commitment).to_public_key(); diff --git a/tests/core_tests/multiassets_test.cpp b/tests/core_tests/multiassets_test.cpp index 90f47c23..e8c7e09d 100644 --- a/tests/core_tests/multiassets_test.cpp +++ b/tests/core_tests/multiassets_test.cpp @@ -424,6 +424,31 @@ bool multiassets_basic_test::c1(currency::core& c, size_t ev_index, const std::v } //------------------------------------------------------------------------------ +//@#@ TODO: subject for refactoring: this fill_ado*/fill_adb* are copy/paste clones of wallet's, need to be implemented in one place at some point +//---------------------------------------------------------------------------------------------------- +void fill_ado_version_based_onhardfork(currency::asset_descriptor_operation& asset_reg_info, size_t current_latest_hf) +{ + if (current_latest_hf < ZANO_HARDFORK_05) + { + asset_reg_info.version = ASSET_DESCRIPTOR_OPERATION_HF4_VER; + } + else + { + asset_reg_info.version = ASSET_DESCRIPTOR_OPERATION_LAST_VER; + } +} +//---------------------------------------------------------------------------------------------------- +void fill_adb_version_based_onhardfork(currency::asset_descriptor_base& asset_base, size_t current_latest_hf) +{ + if (current_latest_hf < ZANO_HARDFORK_05) + { + asset_base.version = ASSET_DESCRIPTOR_BASE_HF4_VER; + } + else + { + asset_base.version = ASSET_DESCRIPTOR_BASE_LAST_VER; + } +} assets_and_explicit_native_coins_in_outs::assets_and_explicit_native_coins_in_outs() { @@ -1034,7 +1059,9 @@ bool asset_operation_and_hardfork_checks::generate( /* asset_id = */ currency::null_pkey); tx_version = get_tx_version(get_block_height(blk_1r), m_hardforks); - + size_t hf_n = m_hardforks.get_the_most_recent_hardfork_id_for_height(get_block_height(blk_1r)); + fill_ado_version_based_onhardfork(m_ado_hello, hf_n); + fill_adb_version_based_onhardfork(*m_ado_hello.opt_descriptor, hf_n); success = construct_tx(alice.get_keys(), sources, destinations, @@ -1081,6 +1108,9 @@ bool asset_operation_and_hardfork_checks::generate( tx_version = get_tx_version(get_block_height(blk_2r), m_hardforks); + hf_n = m_hardforks.get_the_most_recent_hardfork_id_for_height(get_block_height(blk_2r)); + fill_ado_version_based_onhardfork(m_ado_hello, hf_n); + fill_adb_version_based_onhardfork(*m_ado_hello.opt_descriptor, hf_n); success = construct_tx(alice.get_keys(), sources, destinations, @@ -1119,6 +1149,10 @@ bool asset_operation_and_hardfork_checks::generate( tx_version = get_tx_version(get_block_height(blk_2r), m_hardforks); + hf_n = m_hardforks.get_the_most_recent_hardfork_id_for_height(get_block_height(blk_2r)); + fill_ado_version_based_onhardfork(m_ado_bye, hf_n); + fill_adb_version_based_onhardfork(*m_ado_bye.opt_descriptor, hf_n); + success = construct_tx(alice.get_keys(), sources, destinations, @@ -1154,6 +1188,10 @@ bool asset_operation_and_hardfork_checks::generate( tx_version = get_tx_version(get_block_height(blk_2r), m_hardforks); + hf_n = m_hardforks.get_the_most_recent_hardfork_id_for_height(get_block_height(blk_2r)); + fill_ado_version_based_onhardfork(m_ado_bye, hf_n); + fill_adb_version_based_onhardfork(*m_ado_bye.opt_descriptor, hf_n); + success = construct_tx(alice.get_keys(), sources, destinations, @@ -1310,6 +1348,11 @@ bool asset_operation_in_consolidated_tx::generate(std::vector& destinations.emplace_back(MK_TEST_COINS(/* 10 - 5 - 0 = */ 5), bob.get_public_address()); destinations.emplace_back(m_adb_alice_currency.current_supply, alice.get_public_address(), null_pkey); tx_version = get_tx_version(get_block_height(blk_2r), m_hardforks); + size_t hf_n = m_hardforks.get_the_most_recent_hardfork_id_for_height(get_block_height(blk_2r)); + fill_ado_version_based_onhardfork(m_ado_alice_currency, hf_n); + fill_adb_version_based_onhardfork(*m_ado_alice_currency.opt_descriptor, hf_n); + + success = construct_tx(bob.get_keys(), sources, destinations, { m_ado_alice_currency }, empty_attachment, tx_2, tx_version, one_time, 0, 0, 0, true, TX_FLAG_SIGNATURE_MODE_SEPARATE, /* fee = */ 0, context_tx_2); CHECK_AND_ASSERT_MES(success, false, "failed to construct transaction tx_2 on step 2"); @@ -2063,6 +2106,9 @@ bool asset_current_and_total_supplies_comparative_constraints::generate(std::vec std::vector destinations{}; const auto& ado{m_ados_register.at(asset_position::gamma)}; crypto::secret_key one_time{}; + size_t hf_n = m_hardforks.get_the_most_recent_hardfork_id_for_height(get_block_height(blk_1r)); + //fill_ado_version_based_onhardfork(ado, hf_n); + //fill_adb_version_based_onhardfork(*ado.opt_descriptor, hf_n); success = fill_tx_sources_and_destinations(events, top, alice.get_keys(), alice.get_public_address(), MK_TEST_COINS(2), TESTS_DEFAULT_FEE, 0, sources, destinations); CHECK_AND_ASSERT_EQ(success, true); @@ -2086,6 +2132,9 @@ bool asset_current_and_total_supplies_comparative_constraints::generate(std::vec std::vector destinations{}; crypto::secret_key one_time{}; const auto& ado{m_ados_register.at(asset_position::alpha)}; + size_t hf_n = m_hardforks.get_the_most_recent_hardfork_id_for_height(get_block_height(blk_2r)); + //fill_ado_version_based_onhardfork(ado, hf_n); + //fill_adb_version_based_onhardfork(*ado.opt_descriptor, hf_n); success = fill_tx_sources_and_destinations(events, top, alice.get_keys(), alice.get_public_address(), MK_TEST_COINS(2), TESTS_DEFAULT_FEE, 0, sources, destinations); CHECK_AND_ASSERT_EQ(success, true); @@ -2108,6 +2157,9 @@ bool asset_current_and_total_supplies_comparative_constraints::generate(std::vec std::vector destinations{}; crypto::secret_key one_time{}; const auto& ado{m_ados_register.at(asset_position::beta)}; + size_t hf_n = m_hardforks.get_the_most_recent_hardfork_id_for_height(get_block_height(blk_2r)); + //fill_ado_version_based_onhardfork(ado, hf_n); + //fill_adb_version_based_onhardfork(*ado.opt_descriptor, hf_n); success = fill_tx_sources_and_destinations(events, top, alice.get_keys(), alice.get_public_address(), MK_TEST_COINS(2), TESTS_DEFAULT_FEE, 0, sources, destinations); CHECK_AND_ASSERT_EQ(success, true); @@ -2146,12 +2198,19 @@ bool asset_current_and_total_supplies_comparative_constraints::generate(std::vec tx_source_entry source{}; finalize_tx_param ftp{}; finalized_tx ftx{}; + + size_t hf_n = m_hardforks.get_the_most_recent_hardfork_id_for_height(get_block_height(blk_3r)); + //fill_ado_version_based_onhardfork(ado_register, hf_n); + //fill_adb_version_based_onhardfork(*ado_register.opt_descriptor, hf_n); success = fill_tx_sources_and_destinations(events, top, alice.get_keys(), alice.get_public_address(), MK_TEST_COINS(2), TESTS_DEFAULT_FEE, 0, sources, destinations); CHECK_AND_ASSERT_EQ(success, true); CHECK_AND_ASSERT_GREATER(m_ado_emit.opt_descriptor->current_supply, ado_register.opt_descriptor->current_supply); destinations.emplace_back(m_ado_emit.opt_descriptor->current_supply - ado_register.opt_descriptor->current_supply, alice.get_public_address(), null_pkey); + fill_ado_version_based_onhardfork(m_ado_emit, hf_n); + if (m_ado_emit.opt_descriptor.has_value()) fill_adb_version_based_onhardfork(*m_ado_emit.opt_descriptor, hf_n); + ftp.sources = sources; ftp.prepared_destinations = destinations; ftp.tx_version = get_tx_version(get_block_height(top), m_hardforks); From 3b1f08676e01a4dd7e0c2b7fffbd4c39cdc4cb45 Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 21 Oct 2024 12:37:52 +0200 Subject: [PATCH 032/106] warning fixed --- contrib/epee/include/misc_log_ex.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/epee/include/misc_log_ex.h b/contrib/epee/include/misc_log_ex.h index 376af660..e32a9bc5 100644 --- a/contrib/epee/include/misc_log_ex.h +++ b/contrib/epee/include/misc_log_ex.h @@ -1,4 +1,4 @@ -// Copyright (c) 2019, Zano Project +// Copyright (c) 2019-2024, Zano Project // Copyright (c) 2019, anonimal // Copyright (c) 2006-2013, Andrey N. Sabelnikov, www.sabelnikov.net // All rights reserved. @@ -90,7 +90,7 @@ DISABLE_VS_WARNINGS(4100) #define LOG_JOURNAL_MAX_ELEMENTS 100 #ifdef _DEBUG - #define _ASSERTE__(expr) if(!expr) {__debugbreak();} + #define _ASSERTE__(expr) if(!(expr)) {__debugbreak();} #else #define _ASSERTE__(expr) #endif From ecc6cc4605fe5477e1ed7682db437b8b455272f5 Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 21 Oct 2024 12:39:33 +0200 Subject: [PATCH 033/106] coretests: several_asset_emit_burn_txs_in_pool test added --- tests/core_tests/chaingen_main.cpp | 1 + tests/core_tests/multiassets_test.cpp | 166 ++++++++++++++++++++++++++ tests/core_tests/multiassets_test.h | 7 ++ 3 files changed, 174 insertions(+) diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index 4d3acee1..ff0dbc59 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -1299,6 +1299,7 @@ int main(int argc, char* argv[]) GENERATE_AND_PLAY_HF(eth_signed_asset_basics, "5-*"); // TODO: make HF4 version GENERATE_AND_PLAY_HF(eth_signed_asset_via_rpc, "5-*"); // TODO: make HF4 version //GENERATE_AND_PLAY_HF(asset_current_and_total_supplies_comparative_constraints, "4-*"); <-- temporary disabled, waiting for Stepan's fix -- sowle + //GENERATE_AND_PLAY_HF(several_asset_emit_burn_txs_in_pool, "5-*"); <-- temporary disable, waiting till assets' refactoring is finished -- sowle GENERATE_AND_PLAY_HF(pos_fuse_test, "4-*"); GENERATE_AND_PLAY_HF(wallet_reorganize_and_trim_test, "4-*"); diff --git a/tests/core_tests/multiassets_test.cpp b/tests/core_tests/multiassets_test.cpp index c5a4948a..f1b4c651 100644 --- a/tests/core_tests/multiassets_test.cpp +++ b/tests/core_tests/multiassets_test.cpp @@ -2318,5 +2318,171 @@ bool asset_current_and_total_supplies_comparative_constraints::assert_asset_gamm CHECK_AND_ASSERT_EQ(alice_wallet->balance(key_gamma_asset_id), m_ados_register.at(asset_position::gamma).descriptor.current_supply); + return true; +} + +//------------------------------------------------------------------------------ + +several_asset_emit_burn_txs_in_pool::several_asset_emit_burn_txs_in_pool() +{ + REGISTER_CALLBACK_METHOD(several_asset_emit_burn_txs_in_pool, c1); +} + +bool several_asset_emit_burn_txs_in_pool::generate(std::vector& events) const +{ + uint64_t ts = test_core_time::get_time(); + m_accounts.resize(TOTAL_ACCS_COUNT); + account_base& miner_acc = m_accounts[MINER_ACC_IDX]; miner_acc.generate(); miner_acc.set_createtime(ts); + account_base& alice_acc = m_accounts[ALICE_ACC_IDX]; alice_acc.generate(); alice_acc.set_createtime(ts); + miner_acc.generate(); + + MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, ts); + // rebuild genesis miner tx + std::vector destinations; + destinations.emplace_back(MK_TEST_COINS(1), alice_acc.get_public_address()); + destinations.emplace_back(MK_TEST_COINS(1), alice_acc.get_public_address()); + CHECK_AND_ASSERT_MES(replace_coinbase_in_genesis_block(destinations, generator, events, blk_0), false, ""); // leftover amount will be also send to miner + + DO_CALLBACK(events, "configure_core"); // default configure_core callback will initialize core runtime config with m_hardforks + REWIND_BLOCKS_N_WITH_TIME(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 3); + + DO_CALLBACK(events, "c1"); + + return true; +} + +bool several_asset_emit_burn_txs_in_pool::c1(currency::core& c, size_t ev_index, const std::vector& events) +{ + bool r = false; + + std::shared_ptr miner_wlt = init_playtime_test_wallet(events, c, MINER_ACC_IDX); + miner_wlt->refresh(); + std::shared_ptr alice_wlt = init_playtime_test_wallet(events, c, ALICE_ACC_IDX); + alice_wlt->refresh(); + + // asset description + asset_descriptor_base adb{}; + adb.decimal_point = 3; + adb.total_max_supply = 10'000; + adb.full_name = "Lets gooo!"; + adb.ticker = "BRNDN"; + + uint64_t initial_register_amount = 5'000; + + // 1. Miner registers an asset and sends some initial amount to Alice + std::vector destinations; + destinations.emplace_back(initial_register_amount, m_accounts[ALICE_ACC_IDX].get_public_address(), null_pkey); + finalized_tx ft{}; + crypto::public_key asset_id{}; + miner_wlt->deploy_new_asset(adb, destinations, ft, asset_id); + LOG_PRINT_GREEN_L0("Asset " << asset_id << " was successfully deployed with tx " << ft.tx_id); + + // make sure tx was added to the pool, then mine a block to confirm it + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count()); + CHECK_AND_ASSERT_MES(mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c), false, ""); + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count()); + + size_t blocks_fetched = 0; + miner_wlt->refresh(blocks_fetched); + CHECK_AND_ASSERT_EQ(blocks_fetched, 1); + + // Alice checks her asset balance + alice_wlt->refresh(blocks_fetched); + CHECK_AND_ASSERT_EQ(blocks_fetched, 1); + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt, "Alice", initial_register_amount, asset_id, adb.decimal_point), false, ""); + + + // + // 2. Miner emits additional amount of the asset in two transactions (all goes to Alice) + // + + // 2.1 the first emit + uint64_t additional_emit_amount = 2'500; + uint64_t total_asset_amount = initial_register_amount + additional_emit_amount; + + destinations.clear(); + destinations.emplace_back(additional_emit_amount, m_accounts[ALICE_ACC_IDX].get_public_address(), null_pkey); + ft = finalized_tx{}; + miner_wlt->emit_asset(asset_id, destinations, ft); + + // make sure tx was added to the pool + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count()); + + // Alice checks her asset balance (including unconfirmed txs) + alice_wlt->refresh(blocks_fetched); + CHECK_AND_ASSERT_EQ(blocks_fetched, 0); + bool stub{}; + alice_wlt->scan_tx_pool(stub); + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt, "Alice", total_asset_amount, asset_id, adb.decimal_point), false, ""); + + // 2.2 the second emit + //additional_emit_amount = 2'500; + //total_asset_amount += additional_emit_amount; + + //destinations.clear(); + //destinations.emplace_back(additional_emit_amount, m_accounts[ALICE_ACC_IDX].get_public_address(), null_pkey); + //ft = finalized_tx{}; + //miner_wlt->emit_asset(asset_id, destinations, ft); + + //// make sure the second tx was added to the pool + //CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 2, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count()); + + //// Alice checks her asset balance (including unconfirmed txs) + //alice_wlt->refresh(blocks_fetched); + //CHECK_AND_ASSERT_EQ(blocks_fetched, 0); + //alice_wlt->scan_tx_pool(stub); + //CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt, "Alice", total_asset_amount, asset_id, adb.decimal_point), false, ""); + + // 2.3 + // mine a block to confirm both txs to make sure everything is alright + CHECK_AND_ASSERT_MES(mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c), false, ""); + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count()); + + alice_wlt->refresh(blocks_fetched); + CHECK_AND_ASSERT_EQ(blocks_fetched, 1); + + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt, "Alice", total_asset_amount, asset_id, adb.decimal_point), false, ""); + + + // make sure these txs are fully confirmed + CHECK_AND_ASSERT_MES(mine_next_pow_blocks_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c, CURRENCY_MINED_MONEY_UNLOCK_WINDOW), false, ""); + + + // + // 3. Alice burns amount of the asset in two transactions + // + alice_wlt->refresh(); + + // 3.1, the first burn + uint64_t burn_amount = 2'500; + total_asset_amount -= burn_amount; + + ft = finalized_tx{}; + alice_wlt->burn_asset(asset_id, burn_amount, ft); + + // make sure tx was added to the pool + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count()); + + alice_wlt->refresh(blocks_fetched); + CHECK_AND_ASSERT_EQ(blocks_fetched, 0); + alice_wlt->scan_tx_pool(stub); + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt, "Alice", total_asset_amount, asset_id, adb.decimal_point), false, ""); + + // 3.2, the second burn + burn_amount = 2'500; + total_asset_amount -= burn_amount; + + ft = finalized_tx{}; + alice_wlt->burn_asset(asset_id, burn_amount, ft); + + // make sure both txs are now in the pool + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 2, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count()); + + alice_wlt->refresh(blocks_fetched); + CHECK_AND_ASSERT_EQ(blocks_fetched, 0); + alice_wlt->scan_tx_pool(stub); + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt, "Alice", total_asset_amount, asset_id, adb.decimal_point), false, ""); + + return true; } diff --git a/tests/core_tests/multiassets_test.h b/tests/core_tests/multiassets_test.h index c82e00f3..d306a0f4 100644 --- a/tests/core_tests/multiassets_test.h +++ b/tests/core_tests/multiassets_test.h @@ -114,3 +114,10 @@ private: mutable std::array m_ados_register{}; mutable currency::asset_descriptor_operation m_ado_emit{}; }; + +struct several_asset_emit_burn_txs_in_pool : public wallet_test +{ + several_asset_emit_burn_txs_in_pool(); + bool generate(std::vector& events) const; + bool c1(currency::core& c, size_t ev_index, const std::vector& events); +}; From 42b5f321168dd5c7cdd1c512e99df939e1cb5732 Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 21 Oct 2024 14:21:55 +0200 Subject: [PATCH 034/106] test code for wallet2::update_asset --- src/wallet/wallet2.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index ce43b617..3b76a544 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -5640,7 +5640,18 @@ void wallet2::update_asset(const crypto::public_key& asset_id, const currency::a ctp.tx_meaning_for_logs = "asset eth update"; } - this->transfer(ctp, ft, send_to_network, nullptr); + // old: + //this->transfer(ctp, ft, send_to_network, nullptr); + + //// test code -- TODO remove this -- sowle + this->transfer(ctp, ft, false, nullptr); + crypto::hash h1 = get_transaction_hash(ft.tx); + blobdata tx_blob = t_serializable_object_to_blob(ft.tx); + transaction tx{}; + parse_and_validate_tx_from_blob(tx_blob, tx); + crypto::hash h2 = get_transaction_hash(tx); + CHECK_AND_ASSERT_THROW_MES(h1 == h2, "h1 != h2"); + //// end of test code } //---------------------------------------------------------------------------------------------------- void wallet2::update_asset(const crypto::public_key& asset_id, const currency::asset_descriptor_base new_descriptor, currency::transaction& result_tx) From fcb5513d6804b15c93a50c3281951cd44b606f5c Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Mon, 21 Oct 2024 18:56:52 +0400 Subject: [PATCH 035/106] Update README.md --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index e4515a47..ea26c0cc 100644 --- a/README.md +++ b/README.md @@ -169,3 +169,14 @@ To build GUI application: 2. Revise building script, comment out unwanted steps and run it: `utils/build_script_mac_osx.sh` 3. The application should be here: `/buid_mac_osx_64/release/src` +
+
+ +## Supporting project/donations + +ZANO @dev
+BTC bc1qpa8w8eaehlplfepmnzpd7v9j046899nktxnkxp
+BCH qqgq078vww5exd9kt3frx6krdyznmp80hcygzlgqzd
+ETH 0x206c52b78141498e74FF074301ea90888C40c178
+XMR 45gp9WTobeB5Km3kLQgVmPJkvm9rSmg4gdyHheXqXijXYMjUY48kLgL7QEz5Ar8z9vQioQ68WYDKsQsjAEonSeFX4UeLSiX
+ From aeb313a72c8e9fdf33bb0be3300a2d4a45e6a40f Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Mon, 21 Oct 2024 19:51:31 +0400 Subject: [PATCH 036/106] fixed bugs in ado transition --- src/currency_core/currency_basic_backward_comp.inl | 10 +++++----- src/wallet/wallet2.cpp | 13 +------------ 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/src/currency_core/currency_basic_backward_comp.inl b/src/currency_core/currency_basic_backward_comp.inl index ef024377..9dc2a903 100644 --- a/src/currency_core/currency_basic_backward_comp.inl +++ b/src/currency_core/currency_basic_backward_comp.inl @@ -123,13 +123,13 @@ struct asset_descriptor_operation_v1 asset_descriptor_base descriptor; crypto::public_key amount_commitment = currency::null_pkey; // premultiplied by 1/8 boost::optional opt_asset_id; // target asset_id - for update/emit - uint8_t verion = 1; + uint8_t verion = ASSET_DESCRIPTOR_OPERATION_HF4_VER; - BEGIN_VERSIONED_SERIALIZE(1, verion) + BEGIN_SERIALIZE() FIELD(operation_type) FIELD(descriptor) FIELD(amount_commitment) - END_VERSION_UNDER(1) + //END_VERSION_UNDER(1) FIELD(opt_asset_id) END_SERIALIZE() @@ -139,7 +139,7 @@ struct asset_descriptor_operation_v1 BOOST_SERIALIZE(operation_type) BOOST_SERIALIZE(descriptor) BOOST_SERIALIZE(amount_commitment) - BOOST_END_VERSION_UNDER(1) + //BOOST_END_VERSION_UNDER(1) BOOST_SERIALIZE(opt_asset_id) END_BOOST_SERIALIZATION() }; @@ -183,7 +183,7 @@ bool transition_convert(const asset_descriptor_operation_v1& from, asset_descrip to.operation_type = from.operation_type; to.opt_descriptor = from.descriptor; to.opt_amount_commitment = from.amount_commitment; - to.opt_asset_id = to.opt_asset_id; // target asset_id - for update/emit + to.opt_asset_id = from.opt_asset_id; // target asset_id - for update/emit to.version = from.verion; return true; } \ No newline at end of file diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 3b76a544..ce43b617 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -5640,18 +5640,7 @@ void wallet2::update_asset(const crypto::public_key& asset_id, const currency::a ctp.tx_meaning_for_logs = "asset eth update"; } - // old: - //this->transfer(ctp, ft, send_to_network, nullptr); - - //// test code -- TODO remove this -- sowle - this->transfer(ctp, ft, false, nullptr); - crypto::hash h1 = get_transaction_hash(ft.tx); - blobdata tx_blob = t_serializable_object_to_blob(ft.tx); - transaction tx{}; - parse_and_validate_tx_from_blob(tx_blob, tx); - crypto::hash h2 = get_transaction_hash(tx); - CHECK_AND_ASSERT_THROW_MES(h1 == h2, "h1 != h2"); - //// end of test code + this->transfer(ctp, ft, send_to_network, nullptr); } //---------------------------------------------------------------------------------------------------- void wallet2::update_asset(const crypto::public_key& asset_id, const currency::asset_descriptor_base new_descriptor, currency::transaction& result_tx) From e9e7bbf0c58ecfbd491eaa61316c365e583451c8 Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 21 Oct 2024 19:17:04 +0200 Subject: [PATCH 037/106] some fixes in assets handling --- src/currency_core/blockchain_storage.cpp | 69 ++++++++++++++++-------- 1 file changed, 46 insertions(+), 23 deletions(-) diff --git a/src/currency_core/blockchain_storage.cpp b/src/currency_core/blockchain_storage.cpp index 71d8f20c..e41c9ada 100644 --- a/src/currency_core/blockchain_storage.cpp +++ b/src/currency_core/blockchain_storage.cpp @@ -4110,23 +4110,31 @@ bool blockchain_storage::pop_asset_info(const asset_descriptor_operation& ado, c if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT) { // just change the most recent history record, don't pop - CHECK_AND_ASSERT_MES(last_ado.opt_amount_commitment.has_value() && ado.opt_amount_commitment.has_value(), false, "last_ado.opt_amount_commitment or ado.opt_amount_commitment is missing (emit)"); - last_ado.opt_amount_commitment.get() = (crypto::point_t(last_ado.opt_amount_commitment.get()) - crypto::point_t(ado.opt_amount_commitment.get())).to_public_key(); - if (!last_adb.hidden_supply) + if (last_adb.hidden_supply) { - CHECK_AND_ASSERT_MES(last_ado.opt_amount.has_value() && ado.opt_amount.has_value(), false, "last_ado.opt_amount or ado.opt_amount is missing (emit)"); - last_ado.opt_amount.get() -= ado.opt_amount.get(); + //CHECK_AND_ASSERT_MES(last_ado.opt_amount_commitment.has_value() && ado.opt_amount_commitment.has_value(), false, "last_ado.opt_amount_commitment or ado.opt_amount_commitment is missing (emit)"); + //last_ado.opt_amount_commitment.get() = (crypto::point_t(last_ado.opt_amount_commitment.get()) - crypto::point_t(ado.opt_amount_commitment.get())).to_public_key(); + return false; // not supported atm + } + else + { + CHECK_AND_ASSERT_MES(ado.opt_amount.has_value(), false, "last_ado.opt_amount or ado.opt_amount is missing (emit)"); + last_adb.current_supply -= ado.opt_amount.get(); } } else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN) { // just change the most recent history record, don't pop - CHECK_AND_ASSERT_MES(last_ado.opt_amount_commitment.has_value() && ado.opt_amount_commitment.has_value(), false, "last_ado.opt_amount_commitment or ado.opt_amount_commitment is missing (burn)"); - last_ado.opt_amount_commitment.get() = (crypto::point_t(last_ado.opt_amount_commitment.get()) + crypto::point_t(ado.opt_amount_commitment.get())).to_public_key(); - if (!last_adb.hidden_supply) + if (last_adb.hidden_supply) { - CHECK_AND_ASSERT_MES(last_ado.opt_amount.has_value() && ado.opt_amount.has_value(), false, "last_ado.opt_amount or ado.opt_amount is missing (burn)"); - last_ado.opt_amount.get() += ado.opt_amount.get(); + //CHECK_AND_ASSERT_MES(last_ado.opt_amount_commitment.has_value() && ado.opt_amount_commitment.has_value(), false, "last_ado.opt_amount_commitment or ado.opt_amount_commitment is missing (burn)"); + //last_ado.opt_amount_commitment.get() = (crypto::point_t(last_ado.opt_amount_commitment.get()) + crypto::point_t(ado.opt_amount_commitment.get())).to_public_key(); + return false; // not supported atm + } + else + { + CHECK_AND_ASSERT_MES(ado.opt_amount.has_value(), false, "last_ado.opt_amount or ado.opt_amount is missing (burn)"); + last_adb.current_supply += ado.opt_amount.get(); } } else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER || ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE) @@ -4323,7 +4331,11 @@ bool blockchain_storage::validate_asset_operation_hf5(asset_op_verification_cont if (!last_adb.hidden_supply) { CHECK_AND_ASSERT_MES(ado.opt_amount.has_value(), false, "opt_amount is missing (emit)"); - avc.amount_to_validate = ado.opt_amount.get(); + uint64_t amount = ado.opt_amount.get(); + + CHECK_AND_ASSERT_MES(last_adb.current_supply + amount >= last_adb.current_supply, false, "current_supply overflow: " << last_adb.current_supply << ", amount: " << amount << " (emit)"); + CHECK_AND_ASSERT_MES(last_adb.current_supply + amount <= last_adb.total_max_supply, false, "current_supply overflow: " << last_adb.current_supply << ", amount: " << amount << ", max supply: " << last_adb.total_max_supply << " (emit)"); + avc.amount_to_validate = amount; } } else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN) @@ -4335,7 +4347,10 @@ bool blockchain_storage::validate_asset_operation_hf5(asset_op_verification_cont if (!last_adb.hidden_supply) { CHECK_AND_ASSERT_MES(ado.opt_amount.has_value(), false, "opt_amount is missing (burn)"); - avc.amount_to_validate = ado.opt_amount.get(); + uint64_t amount = ado.opt_amount.get(); + + CHECK_AND_ASSERT_MES(last_adb.current_supply - amount <= last_adb.current_supply, false, "current_supply overflow: " << last_adb.current_supply << ", amount: " << amount << " (burn)"); + avc.amount_to_validate = amount; } } else @@ -4371,7 +4386,7 @@ bool blockchain_storage::put_asset_info(const transaction& tx, const crypto::has CRITICAL_REGION_LOCAL(m_read_lock); asset_op_verification_context avc = { tx, tx_id, ado, height }; - CHECK_AND_ASSERT_MES(validate_asset_operation(avc, height), false, "asset operation validation failed (HF5)"); + CHECK_AND_ASSERT_MES(validate_asset_operation(avc, height), false, "asset operation validation failed"); if (is_hardfork_active_for_height(ZANO_HARDFORK_05, height)) { @@ -4403,24 +4418,32 @@ bool blockchain_storage::put_asset_info(const transaction& tx, const crypto::has else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT) { // just change the most recent history record, don't push - CHECK_AND_ASSERT_MES(last_ado.opt_amount_commitment.has_value() && ado.opt_amount_commitment.has_value(), false, "last_ado.opt_amount_commitment or ado.opt_amount_commitment is missing (emit)"); - last_ado.opt_amount_commitment.get() = (crypto::point_t(last_ado.opt_amount_commitment.get()) + crypto::point_t(ado.opt_amount_commitment.get())).to_public_key(); - if (!last_adb.hidden_supply) + if (last_adb.hidden_supply) { - CHECK_AND_ASSERT_MES(last_ado.opt_amount.has_value() && ado.opt_amount.has_value(), false, "last_ado.opt_amount or ado.opt_amount is missing (emit)"); - last_ado.opt_amount.get() += ado.opt_amount.get(); + //CHECK_AND_ASSERT_MES(last_ado.opt_amount_commitment.has_value() && ado.opt_amount_commitment.has_value(), false, "last_ado.opt_amount_commitment or ado.opt_amount_commitment is missing (emit)"); + //last_ado.opt_amount_commitment.get() = (crypto::point_t(last_ado.opt_amount_commitment.get()) + crypto::point_t(ado.opt_amount_commitment.get())).to_public_key(); + return false; // not supported atm + } + else + { + CHECK_AND_ASSERT_MES(ado.opt_amount.has_value(), false, "last_ado.opt_amount or ado.opt_amount is missing (emit)"); + last_adb.current_supply += ado.opt_amount.get(); } LOG_PRINT_MAGENTA("[ASSET_EMITTED]: " << print_money_brief(avc.amount_to_validate, last_adb.decimal_point) << ", " << avc.asset_id << ": " << last_adb.ticker << ", \"" << last_adb.full_name << "\"", LOG_LEVEL_1); } else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN) { // just change the most recent history record, don't push - CHECK_AND_ASSERT_MES(last_ado.opt_amount_commitment.has_value() && ado.opt_amount_commitment.has_value(), false, "last_ado.opt_amount_commitment or ado.opt_amount_commitment is missing (burn)"); - last_ado.opt_amount_commitment.get() = (crypto::point_t(last_ado.opt_amount_commitment.get()) - crypto::point_t(ado.opt_amount_commitment.get())).to_public_key(); - if (!last_adb.hidden_supply) + if (last_adb.hidden_supply) { - CHECK_AND_ASSERT_MES(last_ado.opt_amount.has_value() && ado.opt_amount.has_value(), false, "last_ado.opt_amount or ado.opt_amount is missing (burn)"); - last_ado.opt_amount.get() -= ado.opt_amount.get(); + //CHECK_AND_ASSERT_MES(last_ado.opt_amount_commitment.has_value() && ado.opt_amount_commitment.has_value(), false, "last_ado.opt_amount_commitment or ado.opt_amount_commitment is missing (burn)"); + //last_ado.opt_amount_commitment.get() = (crypto::point_t(last_ado.opt_amount_commitment.get()) - crypto::point_t(ado.opt_amount_commitment.get())).to_public_key(); + return false; // not supported atm + } + else + { + CHECK_AND_ASSERT_MES(ado.opt_amount.has_value(), false, "last_ado.opt_amount or ado.opt_amount is missing (burn)"); + last_adb.current_supply -= ado.opt_amount.get(); } LOG_PRINT_MAGENTA("[ASSET_BURNT]: " << print_money_brief(avc.amount_to_validate, last_adb.decimal_point) << ", " << avc.asset_id << ": " << last_adb.ticker << ", \"" << last_adb.full_name << "\"", LOG_LEVEL_1); } From 9d259f545584cf6f325874a3ff799ab2828ea025 Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 21 Oct 2024 20:19:49 +0200 Subject: [PATCH 038/106] unit_tests: get_or_calculate_asset_id_* made compilable (but still not runnable) --- tests/unit_tests/multiassets_test.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/unit_tests/multiassets_test.cpp b/tests/unit_tests/multiassets_test.cpp index a996acad..4f222541 100644 --- a/tests/unit_tests/multiassets_test.cpp +++ b/tests/unit_tests/multiassets_test.cpp @@ -92,9 +92,9 @@ struct currency::asset_descriptor_operation_v0 asset_descriptor_base_v0 descriptor; crypto::public_key amount_commitment; // premultiplied by 1/8 boost::optional opt_asset_id; // target asset_id - for update/emit - uint8_t verion = ASSET_DESCRIPTOR_OPERATION_STRUCTURE_VER; + uint8_t verion = 1; // ASSET_DESCRIPTOR_OPERATION_STRUCTURE_VER = 1 in 0c90262e8a1c4e5e5d052f8db84c60a36691414d - BEGIN_VERSIONED_SERIALIZE(ASSET_DESCRIPTOR_OPERATION_STRUCTURE_VER, verion) + BEGIN_VERSIONED_SERIALIZE(1, verion) // ASSET_DESCRIPTOR_OPERATION_STRUCTURE_VER = 1 in 0c90262e8a1c4e5e5d052f8db84c60a36691414d FIELD(operation_type) FIELD(descriptor) FIELD(amount_commitment) @@ -121,10 +121,10 @@ struct currency::asset_descriptor_operation_v0 { currency::asset_descriptor_operation operation_descriptor{}; operation_descriptor.operation_type = operation_type; - operation_descriptor.descriptor = descriptor; - operation_descriptor.amount_commitment = amount_commitment; + operation_descriptor.opt_descriptor = descriptor; + operation_descriptor.opt_amount_commitment = amount_commitment; operation_descriptor.opt_asset_id = opt_asset_id; - operation_descriptor.verion = verion; + operation_descriptor.version = verion; return operation_descriptor; } @@ -172,8 +172,8 @@ currency::asset_descriptor_operation get_asset_descriptor_operation_for_test( { currency::asset_descriptor_operation descriptor_operation{}; descriptor_operation.operation_type = operation; - descriptor_operation.descriptor = asset_descriptor; - descriptor_operation.amount_commitment = currency::null_pkey; + descriptor_operation.opt_descriptor = asset_descriptor; + descriptor_operation.opt_amount_commitment = currency::null_pkey; return descriptor_operation; } From 6a32f3f26dea018454a65e7c235307ca895f786e Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 21 Oct 2024 20:24:13 +0200 Subject: [PATCH 039/106] readme: Boost minimum version bumped to 1.75 because of boost::pfr --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ea26c0cc..31f4934d 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,10 @@ Be sure to clone the repository properly:\ |--|--|--|--| | gcc (Linux) | 5.4.0 | 9.4.0 | 12.3.0 | | llvm/clang (Linux) | UNKNOWN | 7.0.1 | 8.0.0 | -| [MSVC](https://visualstudio.microsoft.com/downloads/) (Windows) | 2017 (15.9.30) | 2019 (16.11.34) | 2022 (17.9.5) | +| [MSVC](https://visualstudio.microsoft.com/downloads/) (Windows) | 2017 (15.9.30) | 2019 (16.11.34) | 2022 (17.11.5) | | [XCode](https://developer.apple.com/downloads/) (macOS) | 12.3 | 14.3 | 15.2 | | [CMake](https://cmake.org/download/) | 3.15.5 | 3.26.3 | 3.29.0 | -| [Boost](https://www.boost.org/users/download/) | 1.70 | 1.70 | 1.84 | +| [Boost](https://www.boost.org/users/download/) | 1.75 | 1.75 | 1.84 | | [OpenSSL](https://www.openssl.org/source/) [(win)](https://slproweb.com/products/Win32OpenSSL.html) | 1.1.1n | 1.1.1w | 1.1.1w | | [Qt](https://download.qt.io/archive/qt/) (*only for GUI*) | 5.8.0 | 5.11.2 | 5.15.2 | From ac51ccbf8c54a634a43969edba37b74430b7d5fa Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 21 Oct 2024 20:51:27 +0200 Subject: [PATCH 040/106] gcc compilation fixed --- src/currency_core/currency_basic.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/currency_core/currency_basic.h b/src/currency_core/currency_basic.h index b56b6385..d0630089 100644 --- a/src/currency_core/currency_basic.h +++ b/src/currency_core/currency_basic.h @@ -74,7 +74,9 @@ namespace currency /************************************************************************/ /* */ /************************************************************************/ - + + struct asset_descriptor_operation_v1; + //since structure used in blockchain as a key accessor, then be sure that there is no padding inside #pragma pack(push, 1) struct account_public_address_old From 196860801853d418916736addca081105b1afe0e Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Tue, 22 Oct 2024 14:46:28 +0400 Subject: [PATCH 041/106] fix for android --- src/currency_core/currency_format_utils.h | 2 ++ src/wallet/wallet2.cpp | 8 +++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/currency_core/currency_format_utils.h b/src/currency_core/currency_format_utils.h index 2fab77e1..c8fe36a1 100644 --- a/src/currency_core/currency_format_utils.h +++ b/src/currency_core/currency_format_utils.h @@ -950,6 +950,7 @@ namespace currency } } } + /* //--------------------------------------------------------------- template typename std::enable_if_t, std::ostream&> operator<<(std::ostream& o, invocable_t callee) @@ -957,6 +958,7 @@ namespace currency callee(o); return o; } + */ //--------------------------------------------------------------- std::ostream& operator <<(std::ostream& o, const ref_by_id& r); std::ostream& operator <<(std::ostream& o, const std::type_info& ti); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index f4b203ad..c9765b93 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -397,8 +397,10 @@ const crypto::public_key& wallet2::out_get_pub_key(const currency::tx_out_v& out //---------------------------------------------------------------------------------------------------- void wallet2::process_ado_in_new_transaction(const currency::asset_descriptor_operation& ado, process_transaction_context& ptc) { - auto print_ado_owner = [ado](std::ostream& o){ + auto print_ado_owner = [ado](/*std::ostream& o*/) { + std::stringstream o; ado.descriptor.owner_eth_pub_key.has_value() ? o << ado.descriptor.owner_eth_pub_key.value() << " (ETH)" : o << ado.descriptor.owner; + return o.str(); }; do @@ -424,7 +426,7 @@ void wallet2::process_ado_in_new_transaction(const currency::asset_descriptor_op std::stringstream ss; ss << "New Asset Registered:" << ENDL << "asset id: " << asset_id - << ENDL << "Owner: " << print_ado_owner + << ENDL << "Owner: " << print_ado_owner() << ENDL << "Name: " << asset_context.full_name << ENDL << "Ticker: " << asset_context.ticker << ENDL << "Total Max Supply: " << print_asset_money(asset_context.total_max_supply, asset_context.decimal_point) @@ -492,7 +494,7 @@ void wallet2::process_ado_in_new_transaction(const currency::asset_descriptor_op std::stringstream ss; ss << "Asset ownership lost:" << ENDL << "asset id: " << asset_id - << ENDL << "New owner: " << print_ado_owner + << ENDL << "New owner: " << print_ado_owner() << ENDL << "Name: " << ado.descriptor.full_name << ENDL << "Ticker: " << ado.descriptor.ticker << ENDL << "Total Max Supply: " << print_asset_money(ado.descriptor.total_max_supply, ado.descriptor.decimal_point) From fbf0d413a8229cf0ce2b9af168c4e31611e8b478 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=D1=91pa=20Dolgorukov?= Date: Tue, 22 Oct 2024 17:34:19 +0500 Subject: [PATCH 042/106] Get rid of the compiler warning "not all control paths return a value" (#472) --- tests/core_tests/tx_validation.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/core_tests/tx_validation.cpp b/tests/core_tests/tx_validation.cpp index ea5f6b38..138f8746 100644 --- a/tests/core_tests/tx_validation.cpp +++ b/tests/core_tests/tx_validation.cpp @@ -2076,6 +2076,8 @@ bool tx_pool_semantic_validation::generate(std::vector& events { return sum + boost::get(input).amount; } + + return sum; } }; @@ -2085,6 +2087,8 @@ bool tx_pool_semantic_validation::generate(std::vector& events { return sum + boost::get(output).amount; } + + return sum; } }; From 0cd719474b55647c6636a3c3436bd65c68201665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=D1=91pa=20Dolgorukov?= Date: Tue, 22 Oct 2024 17:47:36 +0500 Subject: [PATCH 043/106] unit_tests: reimplement "get_or_calculate_asset_id" tests, fix "get_or_calculate_asset_id" due to serialization changes (#467) --- tests/unit_tests/multiassets_test.cpp | 1790 ++++++++++--------------- 1 file changed, 711 insertions(+), 1079 deletions(-) diff --git a/tests/unit_tests/multiassets_test.cpp b/tests/unit_tests/multiassets_test.cpp index a996acad..aa577d17 100644 --- a/tests/unit_tests/multiassets_test.cpp +++ b/tests/unit_tests/multiassets_test.cpp @@ -4,7 +4,6 @@ #include -#include #include #include @@ -24,18 +23,18 @@ namespace currency struct asset_descriptor_operation_v0; } -// develop, commit 0c90262e8a1c4e5e5d052f8db84c60a36691414d +// branch = develop, HEAD = 0c90262e8a1c4e5e5d052f8db84c60a36691414d struct currency::asset_descriptor_base_v0 { - uint64_t total_max_supply = 0; - uint64_t current_supply = 0; - uint8_t decimal_point = 0; - std::string ticker; - std::string full_name; - std::string meta_info; - crypto::public_key owner = currency::null_pkey; // consider premultipling by 1/8 - bool hidden_supply = false; - uint8_t version = 0; + uint64_t total_max_supply; + uint64_t current_supply; + uint8_t decimal_point; + std::string ticker; + std::string full_name; + std::string meta_info; + crypto::public_key owner = currency::null_pkey; // consider premultipling by 1/8 + bool hidden_supply; + uint8_t version; BEGIN_VERSIONED_SERIALIZE(0, version) FIELD(total_max_supply) @@ -57,22 +56,24 @@ struct currency::asset_descriptor_base_v0 BOOST_SERIALIZE(meta_info) BOOST_SERIALIZE(owner) BOOST_SERIALIZE(hidden_supply) + BOOST_END_VERSION_UNDER(1) END_BOOST_SERIALIZATION() BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(total_max_supply) DOC_DSCR("Maximum possible supply for given asset, can't be changed after deployment") DOC_EXMP(1000000000000000000) DOC_END - KV_SERIALIZE(current_supply) DOC_DSCR("Currently emitted supply for given asset (ignored for REGISTER operation)") DOC_EXMP(500000000000000000) DOC_END - KV_SERIALIZE(decimal_point) DOC_DSCR("Decimal point") DOC_EXMP(12) DOC_END - KV_SERIALIZE(ticker) DOC_DSCR("Ticker associated with asset") DOC_EXMP("ZUSD") DOC_END - KV_SERIALIZE(full_name) DOC_DSCR("Full name of the asset") DOC_EXMP("Zano wrapped USD") DOC_END - KV_SERIALIZE(meta_info) DOC_DSCR("Any other information assetiaded with asset in a free form") DOC_EXMP("Stable and private") DOC_END - KV_SERIALIZE_POD_AS_HEX_STRING(owner) DOC_DSCR("Owner's key, used only for EMIT and UPDATE validation, could be changed by transferring asset ownership") DOC_EXMP("f74bb56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END - KV_SERIALIZE(hidden_supply) DOC_DSCR("This one reserved for future use, will be documented later") DOC_END + KV_SERIALIZE(total_max_supply) DOC_DSCR("Maximum possible supply for a given asset, cannot be changed after deployment.") DOC_EXMP(1000000000000000000) DOC_END + KV_SERIALIZE(current_supply) DOC_DSCR("Currently emitted supply for the given asset (ignored for REGISTER operation).") DOC_EXMP(500000000000000000) DOC_END + KV_SERIALIZE(decimal_point) DOC_DSCR("Decimal point.") DOC_EXMP(12) DOC_END + KV_SERIALIZE(ticker) DOC_DSCR("Ticker associated with the asset.") DOC_EXMP("ZABC") DOC_END + KV_SERIALIZE(full_name) DOC_DSCR("Full name of the asset.") DOC_EXMP("Zano wrapped ABC") DOC_END + KV_SERIALIZE(meta_info) DOC_DSCR("Any other information associated with the asset in free form.") DOC_EXMP("Stable and private") DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(owner) DOC_DSCR("Owner's key, used only for EMIT and UPDATE validation, can be changed by transferring asset ownership.") DOC_EXMP("f74bb56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END + KV_SERIALIZE(hidden_supply) DOC_DSCR("This field is reserved for future use and will be documented later.") DOC_END END_KV_SERIALIZE_MAP() operator currency::asset_descriptor_base() const { currency::asset_descriptor_base asset_descriptor{}; + asset_descriptor.total_max_supply = total_max_supply; asset_descriptor.current_supply = current_supply; asset_descriptor.decimal_point = decimal_point; @@ -82,10 +83,12 @@ struct currency::asset_descriptor_base_v0 asset_descriptor.owner = owner; asset_descriptor.hidden_supply = hidden_supply; asset_descriptor.version = version; + return asset_descriptor; } }; +// branch = develop, HEAD = 0c90262e8a1c4e5e5d052f8db84c60a36691414d struct currency::asset_descriptor_operation_v0 { uint8_t operation_type = ASSET_DESCRIPTOR_OPERATION_UNDEFINED; @@ -100,7 +103,7 @@ struct currency::asset_descriptor_operation_v0 FIELD(amount_commitment) END_VERSION_UNDER(1) FIELD(opt_asset_id) - END_SERIALIZE() + END_SERIALIZE() BEGIN_BOOST_SERIALIZATION() BOOST_SERIALIZE(operation_type) @@ -111,15 +114,16 @@ struct currency::asset_descriptor_operation_v0 END_BOOST_SERIALIZATION() BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(operation_type) DOC_DSCR("Asset operation type identifier") DOC_EXMP(1) DOC_END - KV_SERIALIZE(descriptor) DOC_DSCR("Asset descriptor") DOC_EXMP_AUTO() DOC_END - KV_SERIALIZE_POD_AS_HEX_STRING(amount_commitment) DOC_DSCR("Amount commitment") DOC_EXMP("f74bb56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END - KV_SERIALIZE_POD_AS_HEX_STRING(opt_asset_id) DOC_DSCR("ID of an asset.") DOC_EXMP("cc4e69455e63f4a581257382191de6856c2156630b3fba0db4bdd73ffcfb36b6") DOC_END + KV_SERIALIZE(operation_type) DOC_DSCR("Asset operation type identifier") DOC_EXMP(1) DOC_END + KV_SERIALIZE(descriptor) DOC_DSCR("Asset descriptor") DOC_EXMP_AUTO() DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(amount_commitment) DOC_DSCR("Amount commitment") DOC_EXMP("f74bb56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(opt_asset_id) DOC_DSCR("ID of an asset.") DOC_EXMP("cc4e69455e63f4a581257382191de6856c2156630b3fba0db4bdd73ffcfb36b6") DOC_END END_KV_SERIALIZE_MAP() operator currency::asset_descriptor_operation() const { currency::asset_descriptor_operation operation_descriptor{}; + operation_descriptor.operation_type = operation_type; operation_descriptor.descriptor = descriptor; operation_descriptor.amount_commitment = amount_commitment; @@ -130,1185 +134,813 @@ struct currency::asset_descriptor_operation_v0 } }; +BOOST_CLASS_VERSION(currency::asset_descriptor_base_v0, 0); BOOST_CLASS_VERSION(currency::asset_descriptor_operation_v0, 1); -currency::asset_descriptor_base get_asset_descriptor_for_test( - const crypto::public_key& owner = currency::null_pkey) +template +static asset_descriptor get_adb(const crypto::public_key& owner = currency::null_pkey) { - currency::asset_descriptor_base descriptor_base{}; - descriptor_base.total_max_supply = 100; - descriptor_base.current_supply = 50; - descriptor_base.decimal_point = 0; - descriptor_base.ticker = "HLO"; - descriptor_base.full_name = "HELLO_WORLD"; - descriptor_base.meta_info = "Hello, world!"; - descriptor_base.owner = owner; - descriptor_base.hidden_supply = false; - descriptor_base.version = 1; + if (const auto& id{typeid(asset_descriptor)}; id != typeid(currency::asset_descriptor_base) && id != typeid(currency::asset_descriptor_base_v0)) + { + throw "Unsupported type of an asset descriptor"; + } - return descriptor_base; + asset_descriptor descriptor{}; + + descriptor.total_max_supply = 100; + descriptor.current_supply = 50; + descriptor.decimal_point = 0; + descriptor.ticker = "HLO"; + descriptor.full_name = "HELLO_WORLD"; + descriptor.meta_info = "Hello, world!"; + descriptor.owner = owner; + descriptor.hidden_supply = false; + + if (typeid(asset_descriptor) == typeid(currency::asset_descriptor_base)) + { + descriptor.version = 1; + } + + return descriptor; } -currency::asset_descriptor_base_v0 get_asset_descriptor_v0_for_test( - const crypto::public_key& owner = currency::null_pkey) +template +static asset_operation_descriptor get_ado(const asset_base_descriptor& base_descriptor, std::uint8_t operation = ASSET_DESCRIPTOR_OPERATION_UNDEFINED, + std::optional asset_id = currency::null_pkey) { - currency::asset_descriptor_base_v0 descriptor_base{}; - descriptor_base.total_max_supply = 100; - descriptor_base.current_supply = 50; - descriptor_base.decimal_point = 0; - descriptor_base.ticker = "HLO"; - descriptor_base.full_name = "HELLO_WORLD"; - descriptor_base.meta_info = "Hello, world!"; - descriptor_base.owner = owner; - descriptor_base.hidden_supply = false; + if (const auto& id{typeid(asset_operation_descriptor)}; id != typeid(currency::asset_descriptor_operation) && id != typeid(currency::asset_descriptor_operation_v0)) + { + throw "Unsupported type of an asset operation descriptor"; + } - return descriptor_base; + if (const auto& id{typeid(asset_base_descriptor)}; id != typeid(currency::asset_descriptor_base) && id != typeid(currency::asset_descriptor_base_v0)) + { + throw "Unsupported type of an asset base descriptor"; + } + + asset_operation_descriptor descriptor{}; + descriptor.operation_type = operation; + descriptor.descriptor = base_descriptor; + descriptor.amount_commitment = currency::null_pkey; + + if (asset_id.has_value()) + { + descriptor.opt_asset_id = asset_id.value(); + } + + return descriptor; } -currency::asset_descriptor_operation get_asset_descriptor_operation_for_test( - currency::asset_descriptor_base asset_descriptor, - std::uint8_t operation = ASSET_DESCRIPTOR_OPERATION_UNDEFINED -) +enum class serialization_method : uint8_t { - currency::asset_descriptor_operation descriptor_operation{}; - descriptor_operation.operation_type = operation; - descriptor_operation.descriptor = asset_descriptor; - descriptor_operation.amount_commitment = currency::null_pkey; + native, + boost, + key_value +}; - return descriptor_operation; +template +static std::optional serialize(serialization_method method, const asset_operation_descriptor& descriptor) +{ + if (const auto& id{typeid(asset_operation_descriptor)}; id != typeid(currency::asset_descriptor_operation) && id != typeid(currency::asset_descriptor_operation_v0)) + { + throw "Unsupported type of an asset descriptor operation"; + } + + std::function serialization_function{}; + + switch (method) + { + case serialization_method::native: + serialization_function = static_cast(t_serializable_object_to_blob); + break; + + case serialization_method::boost: + serialization_function = static_cast(tools::serialize_obj_to_buff); + break; + + case serialization_method::key_value: + serialization_function = [](const asset_operation_descriptor& descriptor, std::string& presentation) -> bool + { + return epee::serialization::store_t_to_json(descriptor, presentation); + }; + break; + } + + if (std::string presentation{}; serialization_function.operator bool() && serialization_function(descriptor, presentation)) + { + return presentation; + } + + return {}; } -currency::asset_descriptor_operation_v0 -get_asset_descriptor_operation_v0_for_test( - currency::asset_descriptor_base_v0 asset_descriptor, - std::uint8_t operation = ASSET_DESCRIPTOR_OPERATION_UNDEFINED -) +template +static std::optional deserialize(serialization_method method, const std::string& presentation) { - currency::asset_descriptor_operation_v0 descriptor_operation{}; - descriptor_operation.operation_type = operation; - descriptor_operation.descriptor = asset_descriptor; - descriptor_operation.amount_commitment = currency::null_pkey; + if (const auto& id{typeid(asset_operation_descriptor)}; id != typeid(currency::asset_descriptor_operation) && id != typeid(currency::asset_descriptor_operation_v0)) + { + throw "Unsupported type of an asset descriptor operation"; + } - return descriptor_operation; + std::function deserialization_function{}; + + switch (method) + { + case serialization_method::native: + deserialization_function = t_unserializable_object_from_blob; + break; + + case serialization_method::boost: + deserialization_function = tools::unserialize_obj_from_buff; + break; + + case serialization_method::key_value: + deserialization_function = epee::serialization::load_t_from_json; + break; + } + + if (asset_operation_descriptor descriptor{}; deserialization_function.operator bool() && deserialization_function(descriptor, presentation)) + { + return descriptor; + } + + return {}; } -TEST(multiassets, get_or_calculate_asset_id_register) +static std::string get_string_presentation(const std::string& data) { - bool success{false}; + std::string presentation{}; - crypto::point_t pt_public_key{}; - success = pt_public_key.from_string( - "cf93bead4d2a8d6d174c1752237b2e5208a594b59618683ee50ef209cf1efb19"); + for (int position{}; position < data.size(); ++position) + { + const auto character{static_cast(data.at(position))}; - ASSERT_TRUE(success); + if (std::isprint(character)) + { + presentation += '\''; - crypto::public_key owner_public_key{}; - pt_public_key.to_public_key(owner_public_key); + if (character == '\'') + { + presentation += '\\'; + } - crypto::point_t calculated_asset_id_pt{}; - crypto::public_key calculated_asset_id_key{}; + presentation += character; + presentation += '\''; + } - const auto asset_descriptor{get_asset_descriptor_for_test(owner_public_key)}; + else + { + std::stringstream stream{}; - const auto asset_operation_descriptor{ - get_asset_descriptor_operation_for_test(asset_descriptor, - ASSET_DESCRIPTOR_OPERATION_REGISTER)}; + presentation += "\'\\x"; + stream << std::hex << std::setw(2) << std::setfill('0') << static_cast(character); + presentation += stream.str(); + presentation += '\''; + } - success = currency::get_or_calculate_asset_id(asset_operation_descriptor, - &calculated_asset_id_pt, - &calculated_asset_id_key); + if (position < data.size() - 1) + { + presentation += ", "; + } + } - ASSERT_TRUE(success); - - const std::string expected_asset_id_str{ - "6f46324faae448b9e3b96dac94da17be6ab7eaaba398de86d8743042c98bace0"}; - - crypto::point_t expected_asset_id_pt{}; - success = expected_asset_id_pt.from_string(expected_asset_id_str); - ASSERT_TRUE(success); - - const crypto::public_key expected_asset_id_key{ - expected_asset_id_pt.to_public_key()}; - - ASSERT_EQ(calculated_asset_id_pt, expected_asset_id_pt); - ASSERT_EQ(calculated_asset_id_key, expected_asset_id_key); -} - -TEST(multiassets, get_or_calculate_asset_id_emit) -{ - bool success{false}; - - crypto::point_t pt_public_key{}; - success = pt_public_key.from_string( - "edab571c4be9eabfea5e7883036d744c097382eb6f739a914db06f72ba35099d"); - - ASSERT_TRUE(success); - - crypto::public_key owner_public_key{}; - pt_public_key.to_public_key(owner_public_key); - - const auto asset_descriptor{ - get_asset_descriptor_for_test(owner_public_key)}; - - auto asset_operation_descriptor{ - get_asset_descriptor_operation_for_test( - asset_descriptor, ASSET_DESCRIPTOR_OPERATION_REGISTER)}; - - crypto::point_t calculated_asset_id_pt{}; - crypto::public_key calculated_asset_id_key{}; - - success = currency::get_or_calculate_asset_id(asset_operation_descriptor, - &calculated_asset_id_pt, - &calculated_asset_id_key); - - ASSERT_TRUE(success); - - const std::string expected_asset_id_str{ - "49a3d6652aaa0b3b77292c534e91ff80de9120aeb6fc1c5edc728047437d667e"}; - - crypto::point_t expected_asset_id_pt{}; - success = expected_asset_id_pt.from_string(expected_asset_id_str); - ASSERT_TRUE(success); - - const crypto::public_key expected_asset_id_key{ - expected_asset_id_pt.to_public_key()}; - - ASSERT_EQ(calculated_asset_id_pt, expected_asset_id_pt); - ASSERT_EQ(calculated_asset_id_key, expected_asset_id_key); - - asset_operation_descriptor.opt_asset_id = calculated_asset_id_key; - asset_operation_descriptor.operation_type = ASSET_DESCRIPTOR_OPERATION_EMIT; - - success = get_or_calculate_asset_id(asset_operation_descriptor, - &calculated_asset_id_pt, - &calculated_asset_id_key); - - ASSERT_TRUE(success); - ASSERT_EQ(calculated_asset_id_pt, expected_asset_id_pt); - ASSERT_EQ(calculated_asset_id_key, expected_asset_id_key); -} - -TEST(multiassets, get_or_calculate_asset_id_update) -{ - bool success{false}; - - crypto::point_t pt_public_key{}; - success = pt_public_key.from_string( - "8cb6349f51da6599feeae7c0077293436eb6a5000f0e6e706e77886bb540e2c1"); - - ASSERT_TRUE(success); - - crypto::public_key owner_public_key{}; - pt_public_key.to_public_key(owner_public_key); - - const auto asset_descriptor{ - get_asset_descriptor_for_test(owner_public_key)}; - - auto asset_operation_descriptor{ - get_asset_descriptor_operation_for_test( - asset_descriptor, ASSET_DESCRIPTOR_OPERATION_REGISTER)}; - - crypto::point_t calculated_asset_id_pt{}; - crypto::public_key calculated_asset_id_key{}; - - success = currency::get_or_calculate_asset_id(asset_operation_descriptor, - &calculated_asset_id_pt, - &calculated_asset_id_key); - - ASSERT_TRUE(success); - - const std::string expected_asset_id_str{ - "c371f60dd8333298c6aa746b71e1e20527b1ff5e1bed4ea9b5f592fadf90ed6b"}; - - crypto::point_t expected_asset_id_pt{}; - success = expected_asset_id_pt.from_string(expected_asset_id_str); - ASSERT_TRUE(success); - - const crypto::public_key expected_asset_id_key{ - expected_asset_id_pt.to_public_key()}; - - ASSERT_EQ(calculated_asset_id_pt, expected_asset_id_pt); - ASSERT_EQ(calculated_asset_id_key, expected_asset_id_key); - - asset_operation_descriptor.opt_asset_id = calculated_asset_id_key; - asset_operation_descriptor.operation_type = ASSET_DESCRIPTOR_OPERATION_UPDATE; - - success = get_or_calculate_asset_id(asset_operation_descriptor, - &calculated_asset_id_pt, - &calculated_asset_id_key); - - ASSERT_TRUE(success); - ASSERT_EQ(calculated_asset_id_pt, expected_asset_id_pt); - ASSERT_EQ(calculated_asset_id_key, expected_asset_id_key); -} - -TEST(multiassets, get_or_calculate_asset_id_public_burn) -{ - bool success{false}; - - crypto::point_t pt_public_key{}; - success = pt_public_key.from_string( - "0c408cf8b7fb808f40593d6eb75890e2ab3d0ccdc7014a7fc6b6ab05163be060"); - - ASSERT_TRUE(success); - - crypto::public_key owner_public_key{}; - pt_public_key.to_public_key(owner_public_key); - - const auto asset_descriptor{ - get_asset_descriptor_for_test(owner_public_key)}; - - auto asset_operation_descriptor{ - get_asset_descriptor_operation_for_test( - asset_descriptor, ASSET_DESCRIPTOR_OPERATION_REGISTER)}; - - crypto::point_t calculated_asset_id_pt{}; - crypto::public_key calculated_asset_id_key{}; - - success = currency::get_or_calculate_asset_id(asset_operation_descriptor, - &calculated_asset_id_pt, - &calculated_asset_id_key); - - ASSERT_TRUE(success); - - const std::string expected_asset_id_str{ - "54f3f72c72e5b014ad2b2b9001acef954fe82dd3ed56a38cd9ddc5db57673f8f"}; - - crypto::point_t expected_asset_id_pt{}; - success = expected_asset_id_pt.from_string(expected_asset_id_str); - ASSERT_TRUE(success); - - const crypto::public_key expected_asset_id_key{ - expected_asset_id_pt.to_public_key()}; - - ASSERT_EQ(calculated_asset_id_pt, expected_asset_id_pt); - ASSERT_EQ(calculated_asset_id_key, expected_asset_id_key); - - asset_operation_descriptor.opt_asset_id = calculated_asset_id_key; - asset_operation_descriptor.operation_type = - ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN; - - success = get_or_calculate_asset_id(asset_operation_descriptor, - &calculated_asset_id_pt, - &calculated_asset_id_key); - - ASSERT_TRUE(success); - ASSERT_EQ(calculated_asset_id_pt, expected_asset_id_pt); - ASSERT_EQ(calculated_asset_id_key, expected_asset_id_key); + return presentation; } TEST(multiassets, get_or_calculate_asset_id_undefined) { - bool success{false}; - - crypto::point_t pt_public_key{}; - success = pt_public_key.from_string( - "e91b9a73292d6ea46fb3d4f4cc79c34bfb7d14c2e684e58093a2471c92e51c16"); + bool success{}; + crypto::point_t point_owner{}; + crypto::public_key owner{}; + crypto::point_t calculated_point_asset_id{}; + crypto::public_key calculated_asset_id{}; + currency::asset_descriptor_base adb{}; + currency::asset_descriptor_operation ado{}; + crypto::point_t expected_point_asset_id{}; + crypto::public_key expected_asset_id{}; + success = point_owner.from_string("e91b9a73292d6ea46fb3d4f4cc79c34bfb7d14c2e684e58093a2471c92e51c16"); ASSERT_TRUE(success); - - crypto::public_key owner_public_key{}; - pt_public_key.to_public_key(owner_public_key); - - const auto asset_descriptor{ - get_asset_descriptor_for_test(owner_public_key)}; - - auto asset_operation_descriptor{ - get_asset_descriptor_operation_for_test( - asset_descriptor, ASSET_DESCRIPTOR_OPERATION_REGISTER)}; - - crypto::point_t calculated_asset_id_pt{}; - crypto::public_key calculated_asset_id_key{}; - - success = currency::get_or_calculate_asset_id(asset_operation_descriptor, - &calculated_asset_id_pt, - &calculated_asset_id_key); - + owner = point_owner.to_public_key(); + adb = get_adb(owner); + ado = get_ado(adb, ASSET_DESCRIPTOR_OPERATION_REGISTER); + success = currency::get_or_calculate_asset_id(ado, &calculated_point_asset_id, &calculated_asset_id); ASSERT_TRUE(success); - - const std::string expected_asset_id_str{ - "979eb706ace2eb83f9125658b23fb352208480cb3b90c43e2df0d298f9754ebc"}; - - crypto::point_t expected_asset_id_pt{}; - success = expected_asset_id_pt.from_string(expected_asset_id_str); + success = expected_point_asset_id.from_string("979eb706ace2eb83f9125658b23fb352208480cb3b90c43e2df0d298f9754ebc"); ASSERT_TRUE(success); - - const crypto::public_key expected_asset_id_key{ - expected_asset_id_pt.to_public_key()}; - - ASSERT_EQ(calculated_asset_id_pt, expected_asset_id_pt); - ASSERT_EQ(calculated_asset_id_key, expected_asset_id_key); - - asset_operation_descriptor.opt_asset_id = calculated_asset_id_key; - asset_operation_descriptor.operation_type = - ASSET_DESCRIPTOR_OPERATION_UNDEFINED; - - success = get_or_calculate_asset_id(asset_operation_descriptor, - &calculated_asset_id_pt, - &calculated_asset_id_key); - + expected_asset_id = expected_point_asset_id.to_public_key(); + ASSERT_EQ(calculated_point_asset_id, expected_point_asset_id); + ASSERT_EQ(calculated_asset_id, expected_asset_id); + ado = get_ado(adb, ASSET_DESCRIPTOR_OPERATION_UNDEFINED, calculated_asset_id); + success = currency::get_or_calculate_asset_id(ado, &calculated_point_asset_id, &calculated_asset_id); ASSERT_FALSE(success); } -TEST(multiassets, get_or_calculate_asset_id_register_serialization) +TEST(multiassets, get_or_calculate_asset_id_register) { - bool success{false}; - currency::asset_descriptor_operation asset_descriptor_operation{}; - const std::string serialized_asset_descriptor_operation{ - '\x01', '\x01', '\x00', 'd', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '2', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x03', 'H', 'L', 'O', '\x0b', 'H', 'E', - 'L', 'L', 'O', '_', 'W', 'O', 'R', 'L', 'D', - '\x0d', 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', - 'o', 'r', 'l', 'd', '!', '\xcf', '\x93', '\xbe', '\xad', - 'M', '*', '\x8d', 'm', '\x17', 'L', '\x17', 'R', '#', - '{', '.', 'R', '\x08', '\xa5', '\x94', '\xb5', '\x96', '\x18', - 'h', '>', '\xe5', '\x0e', '\xf2', '\x09', '\xcf', '\x1e', '\xfb', - '\x19', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'o', - 'F', '2', 'O', '\xaa', '\xe4', 'H', '\xb9', '\xe3', '\xb9', - 'm', '\xac', '\x94', '\xda', '\x17', '\xbe', 'j', '\xb7', '\xea', - '\xab', '\xa3', '\x98', '\xde', '\x86', '\xd8', 't', '0', 'B', - '\xc9', '\x8b', '\xac', '\xe0'}; + bool success{}; + crypto::point_t point_owner{}; + crypto::public_key owner{}; + crypto::point_t calculated_point_asset_id{}; + crypto::public_key calculated_asset_id{}; + currency::asset_descriptor_base adb{}; + currency::asset_descriptor_operation ado{}; + crypto::point_t expected_point_asset_id{}; + crypto::public_key expected_asset_id{}; - success = t_unserializable_object_from_blob(asset_descriptor_operation, - serialized_asset_descriptor_operation); + success = point_owner.from_string("cf93bead4d2a8d6d174c1752237b2e5208a594b59618683ee50ef209cf1efb19"); ASSERT_TRUE(success); - - crypto::point_t calculated_asset_id_pt{}; - crypto::public_key calculated_asset_id_key{}; - - success = currency::get_or_calculate_asset_id(asset_descriptor_operation, - &calculated_asset_id_pt, - &calculated_asset_id_key); + point_owner.to_public_key(owner); + adb = get_adb(owner); + ado = get_ado(adb, ASSET_DESCRIPTOR_OPERATION_REGISTER); + success = currency::get_or_calculate_asset_id(ado, &calculated_point_asset_id, &calculated_asset_id); ASSERT_TRUE(success); - - const std::string expected_asset_id_str{ - "6f46324faae448b9e3b96dac94da17be6ab7eaaba398de86d8743042c98bace0"}; - - crypto::point_t expected_asset_id_pt{}; - success = expected_asset_id_pt.from_string(expected_asset_id_str); + success = expected_point_asset_id.from_string("6f46324faae448b9e3b96dac94da17be6ab7eaaba398de86d8743042c98bace0"); ASSERT_TRUE(success); - - const crypto::public_key expected_asset_id_key{ - expected_asset_id_pt.to_public_key()}; - - ASSERT_EQ(calculated_asset_id_pt, expected_asset_id_pt); - ASSERT_EQ(calculated_asset_id_key, expected_asset_id_key); + ASSERT_EQ(calculated_point_asset_id, expected_point_asset_id); + expected_asset_id = expected_point_asset_id.to_public_key(); + ASSERT_EQ(calculated_asset_id, expected_asset_id); } -TEST(multiassets, get_or_calculate_asset_id_emit_serialization) +TEST(multiassets, get_or_calculate_asset_id_emit) { - bool success{false}; - currency::asset_descriptor_operation_v0 asset_descriptor_operation{}; - const std::string serialized_asset_descriptor_operation{ - '\x01', '\x02', '\x00', 'd', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '2', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x03', 'H', 'L', 'O', '\x0b', 'H', 'E', - 'L', 'L', 'O', '_', 'W', 'O', 'R', 'L', 'D', - '\x0d', 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', - 'o', 'r', 'l', 'd', '!', '\xed', '\xab', 'W', '\x1c', - 'K', '\xe9', '\xea', '\xbf', '\xea', '^', 'x', '\x83', '\x03', - 'm', 't', 'L', '\x09', 's', '\x82', '\xeb', 'o', 's', - '\x9a', '\x91', 'M', '\xb0', 'o', 'r', '\xba', '5', '\x09', - '\x9d', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'I', - '\xa3', '\xd6', 'e', '*', '\xaa', '\x0b', ';', 'w', ')', - ',', 'S', 'N', '\x91', '\xff', '\x80', '\xde', '\x91', ' ', - '\xae', '\xb6', '\xfc', '\x1c', '^', '\xdc', 'r', '\x80', 'G', - 'C', '}', 'f', '~'}; + bool success{}; + crypto::point_t point_owner{}; + crypto::public_key owner{}; + crypto::point_t calculated_point_asset_id{}; + crypto::public_key calculated_asset_id{}; + currency::asset_descriptor_base adb{}; + currency::asset_descriptor_operation ado{}; + crypto::point_t expected_point_asset_id{}; + crypto::public_key expected_asset_id{}; - success = t_unserializable_object_from_blob(asset_descriptor_operation, - serialized_asset_descriptor_operation); + success = point_owner.from_string("edab571c4be9eabfea5e7883036d744c097382eb6f739a914db06f72ba35099d"); ASSERT_TRUE(success); - - crypto::point_t calculated_asset_id_pt{}; - crypto::public_key calculated_asset_id_key{}; - - success = currency::get_or_calculate_asset_id(asset_descriptor_operation, - &calculated_asset_id_pt, - &calculated_asset_id_key); + owner = point_owner.to_public_key(); + adb = get_adb(owner); + ado = get_ado(adb, ASSET_DESCRIPTOR_OPERATION_REGISTER); + success = currency::get_or_calculate_asset_id(ado, &calculated_point_asset_id, &calculated_asset_id); ASSERT_TRUE(success); - - const std::string expected_asset_id_str{ - "49a3d6652aaa0b3b77292c534e91ff80de9120aeb6fc1c5edc728047437d667e"}; - - crypto::point_t expected_asset_id_pt{}; - success = expected_asset_id_pt.from_string(expected_asset_id_str); + success = expected_point_asset_id.from_string("49a3d6652aaa0b3b77292c534e91ff80de9120aeb6fc1c5edc728047437d667e"); ASSERT_TRUE(success); - - crypto::public_key expected_asset_id_key{}; - expected_asset_id_pt.to_public_key(expected_asset_id_key); - ASSERT_EQ(calculated_asset_id_pt, expected_asset_id_pt); - ASSERT_EQ(calculated_asset_id_key, expected_asset_id_key); + expected_asset_id = expected_point_asset_id.to_public_key(); + ASSERT_EQ(calculated_point_asset_id, expected_point_asset_id); + ASSERT_EQ(calculated_asset_id, expected_asset_id); + ado = get_ado(adb, ASSET_DESCRIPTOR_OPERATION_EMIT, calculated_asset_id); + success = currency::get_or_calculate_asset_id(ado, &calculated_point_asset_id, &calculated_asset_id); + ASSERT_TRUE(success); + ASSERT_EQ(calculated_point_asset_id, expected_point_asset_id); + ASSERT_EQ(calculated_asset_id, expected_asset_id); } -TEST(multiassets, get_or_calculate_asset_id_update_serialization) +TEST(multiassets, get_or_calculate_asset_id_update) { - bool success{false}; - currency::asset_descriptor_operation_v0 asset_descriptor_operation{}; - const std::string serialized_asset_descriptor_operation{ - '\x01', '\x03', '\x00', 'd', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '2', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x03', 'H', 'L', 'O', '\x0b', 'H', 'E', - 'L', 'L', 'O', '_', 'W', 'O', 'R', 'L', 'D', - '\x0d', 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', - 'o', 'r', 'l', 'd', '!', '\x8c', '\xb6', '4', '\x9f', - 'Q', '\xda', 'e', '\x99', '\xfe', '\xea', '\xe7', '\xc0', '\x07', - 'r', '\x93', 'C', 'n', '\xb6', '\xa5', '\x00', '\x0f', '\x0e', - 'n', 'p', 'n', 'w', '\x88', 'k', '\xb5', '@', '\xe2', - '\xc1', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\xc3', - 'q', '\xf6', '\x0d', '\xd8', '3', '2', '\x98', '\xc6', '\xaa', - 't', 'k', 'q', '\xe1', '\xe2', '\x05', '\'', '\xb1', '\xff', - '^', '\x1b', '\xed', 'N', '\xa9', '\xb5', '\xf5', '\x92', '\xfa', - '\xdf', '\x90', '\xed', 'k'}; + bool success{}; + crypto::point_t point_owner{}; + crypto::public_key owner{}; + crypto::point_t calculated_point_asset_id{}; + crypto::public_key calculated_asset_id{}; + currency::asset_descriptor_base adb{}; + currency::asset_descriptor_operation ado{}; + crypto::point_t expected_point_asset_id{}; + crypto::public_key expected_asset_id{}; - success = t_unserializable_object_from_blob(asset_descriptor_operation, - serialized_asset_descriptor_operation); + success = point_owner.from_string("8cb6349f51da6599feeae7c0077293436eb6a5000f0e6e706e77886bb540e2c1"); ASSERT_TRUE(success); - - crypto::point_t calculated_asset_id_pt{}; - crypto::public_key calculated_asset_id_key{}; - - success = currency::get_or_calculate_asset_id(asset_descriptor_operation, - &calculated_asset_id_pt, - &calculated_asset_id_key); + owner = point_owner.to_public_key(); + adb = get_adb(owner); + ado = get_ado(adb, ASSET_DESCRIPTOR_OPERATION_REGISTER); + success = currency::get_or_calculate_asset_id(ado, &calculated_point_asset_id, &calculated_asset_id); ASSERT_TRUE(success); - - const std::string expected_asset_id_str{ - "c371f60dd8333298c6aa746b71e1e20527b1ff5e1bed4ea9b5f592fadf90ed6b"}; - - crypto::point_t expected_asset_id_pt{}; - success = expected_asset_id_pt.from_string(expected_asset_id_str); + success = expected_point_asset_id.from_string("c371f60dd8333298c6aa746b71e1e20527b1ff5e1bed4ea9b5f592fadf90ed6b"); ASSERT_TRUE(success); - - crypto::public_key expected_asset_id_key{}; - expected_asset_id_pt.to_public_key(expected_asset_id_key); - ASSERT_EQ(calculated_asset_id_pt, expected_asset_id_pt); - ASSERT_EQ(calculated_asset_id_key, expected_asset_id_key); + expected_asset_id = expected_point_asset_id.to_public_key(); + ASSERT_EQ(calculated_point_asset_id, expected_point_asset_id); + ASSERT_EQ(calculated_asset_id, expected_asset_id); + ado = get_ado(adb, ASSET_DESCRIPTOR_OPERATION_UPDATE, calculated_asset_id); + success = currency::get_or_calculate_asset_id(ado, &calculated_point_asset_id, &calculated_asset_id); + ASSERT_TRUE(success); + ASSERT_EQ(calculated_point_asset_id, expected_point_asset_id); + ASSERT_EQ(calculated_asset_id, expected_asset_id); } -TEST(multiassets, get_or_calculate_asset_id_public_burn_serialization) +TEST(multiassets, get_or_calculate_asset_id_public_burn) { - bool success{false}; - currency::asset_descriptor_operation_v0 asset_descriptor_operation{}; - const std::string serialized_asset_descriptor_operation{ - '\x01', '\x04', '\x00', 'd', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '2', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x03', 'H', 'L', 'O', '\x0b', 'H', 'E', - 'L', 'L', 'O', '_', 'W', 'O', 'R', 'L', 'D', - '\x0d', 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', - 'o', 'r', 'l', 'd', '!', '\x0c', '@', '\x8c', '\xf8', - '\xb7', '\xfb', '\x80', '\x8f', '@', 'Y', '=', 'n', '\xb7', - 'X', '\x90', '\xe2', '\xab', '=', '\x0c', '\xcd', '\xc7', '\x01', - 'J', '\x7f', '\xc6', '\xb6', '\xab', '\x05', '\x16', ';', '\xe0', - '`', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'T', - '\xf3', '\xf7', ',', 'r', '\xe5', '\xb0', '\x14', '\xad', '+', - '+', '\x90', '\x01', '\xac', '\xef', '\x95', 'O', '\xe8', '-', - '\xd3', '\xed', 'V', '\xa3', '\x8c', '\xd9', '\xdd', '\xc5', '\xdb', - 'W', 'g', '?', '\x8f'}; + bool success{}; + crypto::point_t point_owner{}; + crypto::public_key owner{}; + crypto::point_t calculated_point_asset_id{}; + crypto::public_key calculated_asset_id{}; + currency::asset_descriptor_base adb{}; + currency::asset_descriptor_operation ado{}; + crypto::point_t expected_point_asset_id{}; + crypto::public_key expected_asset_id{}; - success = t_unserializable_object_from_blob(asset_descriptor_operation, - serialized_asset_descriptor_operation); + success = point_owner.from_string("0c408cf8b7fb808f40593d6eb75890e2ab3d0ccdc7014a7fc6b6ab05163be060"); ASSERT_TRUE(success); - - crypto::point_t calculated_asset_id_pt{}; - crypto::public_key calculated_asset_id_key{}; - - success = currency::get_or_calculate_asset_id(asset_descriptor_operation, - &calculated_asset_id_pt, - &calculated_asset_id_key); + owner = point_owner.to_public_key(); + adb = get_adb(owner); + ado = get_ado(adb, ASSET_DESCRIPTOR_OPERATION_REGISTER); + success = currency::get_or_calculate_asset_id(ado, &calculated_point_asset_id, &calculated_asset_id); ASSERT_TRUE(success); - - const std::string expected_asset_id_str{ - "54f3f72c72e5b014ad2b2b9001acef954fe82dd3ed56a38cd9ddc5db57673f8f"}; - - crypto::point_t expected_asset_id_pt{}; - success = expected_asset_id_pt.from_string(expected_asset_id_str); + success = expected_point_asset_id.from_string("54f3f72c72e5b014ad2b2b9001acef954fe82dd3ed56a38cd9ddc5db57673f8f"); ASSERT_TRUE(success); - - crypto::public_key expected_asset_id_key{}; - expected_asset_id_pt.to_public_key(expected_asset_id_key); - ASSERT_EQ(calculated_asset_id_pt, expected_asset_id_pt); - ASSERT_EQ(calculated_asset_id_key, expected_asset_id_key); + expected_asset_id = expected_point_asset_id.to_public_key(); + ASSERT_EQ(calculated_point_asset_id, expected_point_asset_id); + ASSERT_EQ(calculated_asset_id, expected_asset_id); + ado = get_ado(adb, ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN, calculated_asset_id); + success = currency::get_or_calculate_asset_id(ado, &calculated_point_asset_id, &calculated_asset_id); + ASSERT_TRUE(success); + ASSERT_EQ(calculated_point_asset_id, expected_point_asset_id); + ASSERT_EQ(calculated_asset_id, expected_asset_id); } -TEST(multiassets, get_or_calculate_asset_id_undefined_serialization) +TEST(multiassets, native_serialization_get_or_calculate_asset_id_undefined) { - bool success{false}; - currency::asset_descriptor_operation_v0 asset_descriptor_operation{}; - const std::string serialized_asset_descriptor_operation{ - '\x01', '\x00', '\x00', 'd', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '2', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x03', 'H', 'L', 'O', '\x0b', 'H', 'E', - 'L', 'L', 'O', '_', 'W', 'O', 'R', 'L', 'D', - '\x0d', 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', - 'o', 'r', 'l', 'd', '!', '\xe9', '\x1b', '\x9a', 's', - ')', '-', 'n', '\xa4', 'o', '\xb3', '\xd4', '\xf4', '\xcc', - 'y', '\xc3', 'K', '\xfb', '}', '\x14', '\xc2', '\xe6', '\x84', - '\xe5', '\x80', '\x93', '\xa2', 'G', '\x1c', '\x92', '\xe5', '\x1c', - '\x16', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x97', - '\x9e', '\xb7', '\x06', '\xac', '\xe2', '\xeb', '\x83', '\xf9', '\x12', - 'V', 'X', '\xb2', '?', '\xb3', 'R', ' ', '\x84', '\x80', - '\xcb', ';', '\x90', '\xc4', '>', '-', '\xf0', '\xd2', '\x98', - '\xf9', 'u', 'N', '\xbc'}; + bool success{}; + const std::string serialized_ado{'\x01', '\x00', '\x00', 'd', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '2', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x03', + 'H', 'L', 'O', '\x0b', 'H', 'E', 'L', 'L', 'O', '_', 'W', 'O', 'R', 'L', 'D', '\x0d', 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\xe9', '\x1b', '\x9a', 's', ')', '-', 'n', + '\xa4', 'o', '\xb3', '\xd4', '\xf4', '\xcc', 'y', '\xc3', 'K', '\xfb', '}', '\x14', '\xc2', '\xe6', '\x84', '\xe5', '\x80', '\x93', '\xa2', 'G', '\x1c', '\x92', '\xe5', '\x1c', '\x16', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x97', '\x9e', '\xb7', '\x06', '\xac', '\xe2', '\xeb', '\x83', '\xf9', '\x12', 'V', 'X', '\xb2', '?', '\xb3', 'R', ' ', + '\x84', '\x80', '\xcb', ';', '\x90', '\xc4', '>', '-', '\xf0', '\xd2', '\x98', '\xf9', 'u', 'N', '\xbc'}; - success = t_unserializable_object_from_blob( - asset_descriptor_operation, - serialized_asset_descriptor_operation); - ASSERT_TRUE(success); + crypto::point_t expected_point_asset_id{}; + crypto::public_key expected_asset_id{}; + crypto::point_t calculated_point_asset_id{}; + crypto::public_key calculated_asset_id{}; + const std::optional ado{deserialize(serialization_method::native, serialized_ado)}; - crypto::point_t calculated_asset_id_pt{}; - crypto::public_key calculated_asset_id_key{}; - - success = currency::get_or_calculate_asset_id(asset_descriptor_operation, - &calculated_asset_id_pt, - &calculated_asset_id_key); + ASSERT_TRUE(ado.has_value()); + ASSERT_EQ(ado.value().verion, 1); + ASSERT_EQ(ado.value().descriptor.version, 0); + ASSERT_EQ(ado.value().operation_type, ASSET_DESCRIPTOR_OPERATION_UNDEFINED); + success = currency::get_or_calculate_asset_id(ado.value(), &calculated_point_asset_id, &calculated_asset_id); ASSERT_FALSE(success); } -TEST(multiassets, get_or_calculate_asset_id_register_boost_serialization) +TEST(multiassets, native_serialization_get_or_calculate_asset_id_register) { - bool success{false}; - currency::asset_descriptor_operation asset_descriptor_operation{}; - const std::string serialized_asset_descriptor_operation{ - '\x16', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 's', - 'e', 'r', 'i', 'a', 'l', 'i', 'z', 'a', 't', - 'i', 'o', 'n', ':', ':', 'a', 'r', 'c', 'h', - 'i', 'v', 'e', '\x11', '\x00', '\x04', '\x08', '\x04', '\x08', - '\x01', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x01', '\x00', '\x00', '\x00', '\x00', '\x00', 'd', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '2', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x03', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', 'H', 'L', 'O', '\x0b', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'H', 'E', 'L', - 'L', 'O', '_', 'W', 'O', 'R', 'L', 'D', '\x0d', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'H', 'e', - 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', - 'd', '!', '\x00', '\x00', '\x00', '\x00', '\x00', ' ', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\xcf', '\x93', '\xbe', - '\xad', 'M', '*', '\x8d', 'm', '\x17', 'L', '\x17', 'R', - '#', '{', '.', 'R', '\x08', '\xa5', '\x94', '\xb5', '\x96', - '\x18', 'h', '>', '\xe5', '\x0e', '\xf2', '\x09', '\xcf', '\x1e', - '\xfb', '\x19', '\x00', ' ', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}; + bool success{}; + const std::string serialized_ado{'\x01', '\x01', '\x00', 'd', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '2', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x03', + 'H', 'L', 'O', '\x0b', 'H', 'E', 'L', 'L', 'O', '_', 'W', 'O', 'R', 'L', 'D', '\x0d', 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\xcf', '\x93', '\xbe', '\xad', 'M', '*', + '\x8d', 'm', '\x17', 'L', '\x17', 'R', '#', '{', '.', 'R', '\x08', '\xa5', '\x94', '\xb5', '\x96', '\x18', 'h', '>', '\xe5', '\x0e', '\xf2', '\x09', '\xcf', '\x1e', '\xfb', '\x19', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}; - success = tools::unserialize_obj_from_buff( - asset_descriptor_operation, - serialized_asset_descriptor_operation); + crypto::point_t expected_point_asset_id{}; + crypto::public_key expected_asset_id{}; + crypto::point_t calculated_point_asset_id{}; + crypto::public_key calculated_asset_id{}; + const std::optional ado{deserialize(serialization_method::native, serialized_ado)}; + + ASSERT_TRUE(ado.has_value()); + ASSERT_EQ(ado.value().verion, 1); + ASSERT_EQ(ado.value().descriptor.version, 0); + ASSERT_EQ(ado.value().operation_type, ASSET_DESCRIPTOR_OPERATION_REGISTER); + success = currency::get_or_calculate_asset_id(ado.value(), &calculated_point_asset_id, &calculated_asset_id); ASSERT_TRUE(success); - - crypto::point_t calculated_asset_id_pt{}; - crypto::public_key calculated_asset_id_key{}; - - success = currency::get_or_calculate_asset_id(asset_descriptor_operation, - &calculated_asset_id_pt, - &calculated_asset_id_key); + success = expected_point_asset_id.from_string("6f46324faae448b9e3b96dac94da17be6ab7eaaba398de86d8743042c98bace0"); ASSERT_TRUE(success); - - const std::string expected_asset_id_str{ - "6f46324faae448b9e3b96dac94da17be6ab7eaaba398de86d8743042c98bace0"}; - - crypto::point_t expected_asset_id_pt{}; - success = expected_asset_id_pt.from_string(expected_asset_id_str); - ASSERT_TRUE(success); - - const crypto::public_key expected_asset_id_key{ - expected_asset_id_pt.to_public_key()}; - - ASSERT_EQ(calculated_asset_id_pt, expected_asset_id_pt); - ASSERT_EQ(calculated_asset_id_key, expected_asset_id_key); + ASSERT_EQ(calculated_point_asset_id, expected_point_asset_id); + expected_asset_id = expected_point_asset_id.to_public_key(); + ASSERT_EQ(calculated_asset_id, expected_asset_id); } -TEST(multiassets, get_or_calculate_asset_id_emit_boost_serialization) +TEST(multiassets, native_serialization_get_or_calculate_asset_id_emit) { - bool success{false}; - currency::asset_descriptor_operation asset_descriptor_operation{}; - const std::string serialized_asset_descriptor_operation{ - '\x16', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 's', - 'e', 'r', 'i', 'a', 'l', 'i', 'z', 'a', 't', - 'i', 'o', 'n', ':', ':', 'a', 'r', 'c', 'h', - 'i', 'v', 'e', '\x11', '\x00', '\x04', '\x08', '\x04', '\x08', - '\x01', '\x00', '\x00', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00', - '\x02', '\x00', '\x00', '\x00', '\x00', '\x00', 'd', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '2', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x03', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', 'H', 'L', 'O', '\x0b', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'H', 'E', 'L', - 'L', 'O', '_', 'W', 'O', 'R', 'L', 'D', '\x0d', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'H', 'e', - 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', - 'd', '!', '\x00', '\x00', '\x00', '\x00', '\x00', ' ', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\xed', '\xab', 'W', - '\x1c', 'K', '\xe9', '\xea', '\xbf', '\xea', '^', 'x', '\x83', - '\x03', 'm', 't', 'L', '\x09', 's', '\x82', '\xeb', 'o', - 's', '\x9a', '\x91', 'M', '\xb0', 'o', 'r', '\xba', '5', - '\x09', '\x9d', '\x00', ' ', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x01', - '\x00', '\x00', '\x00', '\x01', ' ', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', 'I', '\xa3', '\xd6', 'e', '*', '\xaa', - '\x0b', ';', 'w', ')', ',', 'S', 'N', '\x91', '\xff', - '\x80', '\xde', '\x91', ' ', '\xae', '\xb6', '\xfc', '\x1c', '^', - '\xdc', 'r', '\x80', 'G', 'C', '}', 'f', '~'}; + bool success{}; + const std::string serialized_ado{'\x01', '\x02', '\x00', 'd', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '2', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x03', + 'H', 'L', 'O', '\x0b', 'H', 'E', 'L', 'L', 'O', '_', 'W', 'O', 'R', 'L', 'D', '\x0d', 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\xed', '\xab', 'W', '\x1c', 'K', '\xe9', + '\xea', '\xbf', '\xea', '^', 'x', '\x83', '\x03', 'm', 't', 'L', '\x09', 's', '\x82', '\xeb', 'o', 's', '\x9a', '\x91', 'M', '\xb0', 'o', 'r', '\xba', '5', '\x09', '\x9d', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'I', '\xa3', '\xd6', 'e', '*', '\xaa', '\x0b', ';', 'w', ')', ',', 'S', 'N', '\x91', '\xff', '\x80', '\xde', '\x91', ' ', '\xae', '\xb6', + '\xfc', '\x1c', '^', '\xdc', 'r', '\x80', 'G', 'C', '}', 'f', '~'}; - success = tools::unserialize_obj_from_buff( - asset_descriptor_operation, - serialized_asset_descriptor_operation); + crypto::point_t expected_point_asset_id{}; + crypto::public_key expected_asset_id{}; + crypto::point_t calculated_point_asset_id{}; + crypto::public_key calculated_asset_id{}; + const std::optional ado{deserialize(serialization_method::native, serialized_ado)}; + + ASSERT_TRUE(ado.has_value()); + ASSERT_EQ(ado.value().verion, 1); + ASSERT_EQ(ado.value().descriptor.version, 0); + ASSERT_EQ(ado.value().operation_type, ASSET_DESCRIPTOR_OPERATION_EMIT); + success = currency::get_or_calculate_asset_id(ado.value(), &calculated_point_asset_id, &calculated_asset_id); ASSERT_TRUE(success); - - crypto::point_t calculated_asset_id_pt{}; - crypto::public_key calculated_asset_id_key{}; - - success = currency::get_or_calculate_asset_id(asset_descriptor_operation, - &calculated_asset_id_pt, - &calculated_asset_id_key); + success = expected_point_asset_id.from_string("49a3d6652aaa0b3b77292c534e91ff80de9120aeb6fc1c5edc728047437d667e"); ASSERT_TRUE(success); - - const std::string expected_asset_id_str{ - "49a3d6652aaa0b3b77292c534e91ff80de9120aeb6fc1c5edc728047437d667e"}; - - crypto::point_t expected_asset_id_pt{}; - success = expected_asset_id_pt.from_string(expected_asset_id_str); - ASSERT_TRUE(success); - - crypto::public_key expected_asset_id_key{}; - expected_asset_id_pt.to_public_key(expected_asset_id_key); - ASSERT_EQ(calculated_asset_id_pt, expected_asset_id_pt); - ASSERT_EQ(calculated_asset_id_key, expected_asset_id_key); + ASSERT_EQ(calculated_point_asset_id, expected_point_asset_id); + expected_asset_id = expected_point_asset_id.to_public_key(); + ASSERT_EQ(calculated_asset_id, expected_asset_id); } -TEST(multiassets, get_or_calculate_asset_id_update_boost_serialization) +TEST(multiassets, native_serialization_get_or_calculate_asset_id_update) { - bool success{false}; - currency::asset_descriptor_operation_v0 asset_descriptor_operation{}; - const std::string serialized_asset_descriptor_operation{ - '\x16', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 's', - 'e', 'r', 'i', 'a', 'l', 'i', 'z', 'a', 't', - 'i', 'o', 'n', ':', ':', 'a', 'r', 'c', 'h', - 'i', 'v', 'e', '\x11', '\x00', '\x04', '\x08', '\x04', '\x08', - '\x01', '\x00', '\x00', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00', - '\x03', '\x00', '\x00', '\x00', '\x00', '\x00', 'd', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '2', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x03', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', 'H', 'L', 'O', '\x0b', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'H', 'E', 'L', - 'L', 'O', '_', 'W', 'O', 'R', 'L', 'D', '\x0d', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'H', 'e', - 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', - 'd', '!', '\x00', '\x00', '\x00', '\x00', '\x00', ' ', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x8c', '\xb6', '4', - '\x9f', 'Q', '\xda', 'e', '\x99', '\xfe', '\xea', '\xe7', '\xc0', - '\x07', 'r', '\x93', 'C', 'n', '\xb6', '\xa5', '\x00', '\x0f', - '\x0e', 'n', 'p', 'n', 'w', '\x88', 'k', '\xb5', '@', - '\xe2', '\xc1', '\x00', ' ', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x01', - '\x00', '\x00', '\x00', '\x01', ' ', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\xc3', 'q', '\xf6', '\x0d', '\xd8', '3', - '2', '\x98', '\xc6', '\xaa', 't', 'k', 'q', '\xe1', '\xe2', - '\x05', '\'', '\xb1', '\xff', '^', '\x1b', '\xed', 'N', '\xa9', - '\xb5', '\xf5', '\x92', '\xfa', '\xdf', '\x90', '\xed', 'k'}; + bool success{}; + const std::string serialized_ado{'\x01', '\x03', '\x00', 'd', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '2', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x03', + 'H', 'L', 'O', '\x0b', 'H', 'E', 'L', 'L', 'O', '_', 'W', 'O', 'R', 'L', 'D', '\x0d', 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\x8c', '\xb6', '4', '\x9f', 'Q', '\xda', + 'e', '\x99', '\xfe', '\xea', '\xe7', '\xc0', '\x07', 'r', '\x93', 'C', 'n', '\xb6', '\xa5', '\x00', '\x0f', '\x0e', 'n', 'p', 'n', 'w', '\x88', 'k', '\xb5', '@', '\xe2', '\xc1', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\xc3', 'q', '\xf6', '\x0d', '\xd8', '3', '2', '\x98', '\xc6', '\xaa', 't', 'k', 'q', '\xe1', '\xe2', '\x05', '\'', '\xb1', '\xff', + '^', '\x1b', '\xed', 'N', '\xa9', '\xb5', '\xf5', '\x92', '\xfa', '\xdf', '\x90', '\xed', 'k'}; - success = tools::unserialize_obj_from_buff( - asset_descriptor_operation, - serialized_asset_descriptor_operation); + crypto::point_t expected_point_asset_id{}; + crypto::public_key expected_asset_id{}; + crypto::point_t calculated_point_asset_id{}; + crypto::public_key calculated_asset_id{}; + const std::optional ado{deserialize(serialization_method::native, serialized_ado)}; + + ASSERT_TRUE(ado.has_value()); + ASSERT_EQ(ado.value().verion, 1); + ASSERT_EQ(ado.value().descriptor.version, 0); + ASSERT_EQ(ado.value().operation_type, ASSET_DESCRIPTOR_OPERATION_UPDATE); + success = currency::get_or_calculate_asset_id(ado.value(), &calculated_point_asset_id, &calculated_asset_id); ASSERT_TRUE(success); - - crypto::point_t calculated_asset_id_pt{}; - crypto::public_key calculated_asset_id_key{}; - - success = currency::get_or_calculate_asset_id(asset_descriptor_operation, - &calculated_asset_id_pt, - &calculated_asset_id_key); + success = expected_point_asset_id.from_string("c371f60dd8333298c6aa746b71e1e20527b1ff5e1bed4ea9b5f592fadf90ed6b"); ASSERT_TRUE(success); - - const std::string expected_asset_id_str{ - "c371f60dd8333298c6aa746b71e1e20527b1ff5e1bed4ea9b5f592fadf90ed6b"}; - - crypto::point_t expected_asset_id_pt{}; - success = expected_asset_id_pt.from_string(expected_asset_id_str); - ASSERT_TRUE(success); - - crypto::public_key expected_asset_id_key{}; - expected_asset_id_pt.to_public_key(expected_asset_id_key); - ASSERT_EQ(calculated_asset_id_pt, expected_asset_id_pt); - ASSERT_EQ(calculated_asset_id_key, expected_asset_id_key); + ASSERT_EQ(calculated_point_asset_id, expected_point_asset_id); + expected_asset_id = expected_point_asset_id.to_public_key(); + ASSERT_EQ(calculated_asset_id, expected_asset_id); } -TEST(multiassets, get_or_calculate_asset_id_public_burn_boost_serialization) +TEST(multiassets, native_serialization_get_or_calculate_asset_id_public_burn) { - bool success{false}; - currency::asset_descriptor_operation_v0 asset_descriptor_operation{}; - const std::string serialized_asset_descriptor_operation{ - '\x16', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 's', - 'e', 'r', 'i', 'a', 'l', 'i', 'z', 'a', 't', - 'i', 'o', 'n', ':', ':', 'a', 'r', 'c', 'h', - 'i', 'v', 'e', '\x11', '\x00', '\x04', '\x08', '\x04', '\x08', - '\x01', '\x00', '\x00', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00', - '\x04', '\x00', '\x00', '\x00', '\x00', '\x00', 'd', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '2', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x03', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', 'H', 'L', 'O', '\x0b', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'H', 'E', 'L', - 'L', 'O', '_', 'W', 'O', 'R', 'L', 'D', '\x0d', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'H', 'e', - 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', - 'd', '!', '\x00', '\x00', '\x00', '\x00', '\x00', ' ', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x0c', '@', '\x8c', - '\xf8', '\xb7', '\xfb', '\x80', '\x8f', '@', 'Y', '=', 'n', - '\xb7', 'X', '\x90', '\xe2', '\xab', '=', '\x0c', '\xcd', '\xc7', - '\x01', 'J', '\x7f', '\xc6', '\xb6', '\xab', '\x05', '\x16', ';', - '\xe0', '`', '\x00', ' ', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x01', - '\x00', '\x00', '\x00', '\x01', ' ', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', 'T', '\xf3', '\xf7', ',', 'r', '\xe5', - '\xb0', '\x14', '\xad', '+', '+', '\x90', '\x01', '\xac', '\xef', - '\x95', 'O', '\xe8', '-', '\xd3', '\xed', 'V', '\xa3', '\x8c', - '\xd9', '\xdd', '\xc5', '\xdb', 'W', 'g', '?', '\x8f'}; + bool success{}; + const std::string serialized_ado{'\x01', '\x04', '\x00', 'd', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '2', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x03', + 'H', 'L', 'O', '\x0b', 'H', 'E', 'L', 'L', 'O', '_', 'W', 'O', 'R', 'L', 'D', '\x0d', 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\x0c', '@', '\x8c', '\xf8', '\xb7', '\xfb', + '\x80', '\x8f', '@', 'Y', '=', 'n', '\xb7', 'X', '\x90', '\xe2', '\xab', '=', '\x0c', '\xcd', '\xc7', '\x01', 'J', '\x7f', '\xc6', '\xb6', '\xab', '\x05', '\x16', ';', '\xe0', '`', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'T', '\xf3', '\xf7', ',', 'r', '\xe5', '\xb0', '\x14', '\xad', '+', '+', '\x90', '\x01', '\xac', '\xef', '\x95', 'O', '\xe8', '-', + '\xd3', '\xed', 'V', '\xa3', '\x8c', '\xd9', '\xdd', '\xc5', '\xdb', 'W', 'g', '?', '\x8f'}; - success = tools::unserialize_obj_from_buff( - asset_descriptor_operation, - serialized_asset_descriptor_operation); + crypto::point_t expected_point_asset_id{}; + crypto::public_key expected_asset_id{}; + crypto::point_t calculated_point_asset_id{}; + crypto::public_key calculated_asset_id{}; + const std::optional ado{deserialize(serialization_method::native, serialized_ado)}; + + ASSERT_TRUE(ado.has_value()); + ASSERT_EQ(ado.value().verion, 1); + ASSERT_EQ(ado.value().descriptor.version, 0); + ASSERT_EQ(ado.value().operation_type, ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN); + success = currency::get_or_calculate_asset_id(ado.value(), &calculated_point_asset_id, &calculated_asset_id); ASSERT_TRUE(success); - - crypto::point_t calculated_asset_id_pt{}; - crypto::public_key calculated_asset_id_key{}; - - success = currency::get_or_calculate_asset_id(asset_descriptor_operation, - &calculated_asset_id_pt, - &calculated_asset_id_key); + success = expected_point_asset_id.from_string("54f3f72c72e5b014ad2b2b9001acef954fe82dd3ed56a38cd9ddc5db57673f8f"); ASSERT_TRUE(success); - - const std::string expected_asset_id_str{ - "54f3f72c72e5b014ad2b2b9001acef954fe82dd3ed56a38cd9ddc5db57673f8f"}; - - crypto::point_t expected_asset_id_pt{}; - success = expected_asset_id_pt.from_string(expected_asset_id_str); - ASSERT_TRUE(success); - - crypto::public_key expected_asset_id_key{}; - expected_asset_id_pt.to_public_key(expected_asset_id_key); - ASSERT_EQ(calculated_asset_id_pt, expected_asset_id_pt); - ASSERT_EQ(calculated_asset_id_key, expected_asset_id_key); + ASSERT_EQ(calculated_point_asset_id, expected_point_asset_id); + expected_asset_id = expected_point_asset_id.to_public_key(); + ASSERT_EQ(calculated_asset_id, expected_asset_id); } -TEST(multiassets, get_or_calculate_asset_id_undefined_boost_serialization) +TEST(multiassets, boost_serialization_get_or_calculate_asset_id_undefined) { - bool success{false}; - currency::asset_descriptor_operation_v0 asset_descriptor_operation{}; - const std::string serialized_asset_descriptor_operation{ - '\x16', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 's', - 'e', 'r', 'i', 'a', 'l', 'i', 'z', 'a', 't', - 'i', 'o', 'n', ':', ':', 'a', 'r', 'c', 'h', - 'i', 'v', 'e', '\x11', '\x00', '\x04', '\x08', '\x04', '\x08', - '\x01', '\x00', '\x00', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'd', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '2', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x03', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', 'H', 'L', 'O', '\x0b', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'H', 'E', 'L', - 'L', 'O', '_', 'W', 'O', 'R', 'L', 'D', '\x0d', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'H', 'e', - 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', - 'd', '!', '\x00', '\x00', '\x00', '\x00', '\x00', ' ', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\xe9', '\x1b', '\x9a', - 's', ')', '-', 'n', '\xa4', 'o', '\xb3', '\xd4', '\xf4', - '\xcc', 'y', '\xc3', 'K', '\xfb', '}', '\x14', '\xc2', '\xe6', - '\x84', '\xe5', '\x80', '\x93', '\xa2', 'G', '\x1c', '\x92', '\xe5', - '\x1c', '\x16', '\x00', ' ', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x01', - '\x00', '\x00', '\x00', '\x01', ' ', '\x00', '\x00', '\x00', '\x00', - '\x00', '\x00', '\x00', '\x97', '\x9e', '\xb7', '\x06', '\xac', '\xe2', - '\xeb', '\x83', '\xf9', '\x12', 'V', 'X', '\xb2', '?', '\xb3', - 'R', ' ', '\x84', '\x80', '\xcb', ';', '\x90', '\xc4', '>', - '-', '\xf0', '\xd2', '\x98', '\xf9', 'u', 'N', '\xbc'}; + bool success{}; + const std::string serialized_ado{'\x16', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 's', 'e', 'r', 'i', 'a', 'l', 'i', 'z', 'a', 't', 'i', 'o', 'n', ':', ':', 'a', 'r', 'c', 'h', 'i', + 'v', 'e', '\x14', '\x00', '\x04', '\x04', '\x04', '\x08', '\x01', '\x00', '\x00', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'd', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '2', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x03', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'H', 'L', 'O', + '\x0b', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'H', 'E', 'L', 'L', 'O', '_', 'W', 'O', 'R', 'L', 'D', '\x0d', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'H', 'e', + 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\x00', '\x00', '\x00', '\x00', '\x00', ' ', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\xe9', '\x1b', '\x9a', 's', ')', '-', + 'n', '\xa4', 'o', '\xb3', '\xd4', '\xf4', '\xcc', 'y', '\xc3', 'K', '\xfb', '}', '\x14', '\xc2', '\xe6', '\x84', '\xe5', '\x80', '\x93', '\xa2', 'G', '\x1c', '\x92', '\xe5', '\x1c', '\x16', + '\x00', ' ', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00', '\x01', ' ', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x97', '\x9e', '\xb7', '\x06', '\xac', '\xe2', '\xeb', '\x83', '\xf9', '\x12', 'V', 'X', '\xb2', '?', '\xb3', 'R', ' ', '\x84', '\x80', '\xcb', ';', + '\x90', '\xc4', '>', '-', '\xf0', '\xd2', '\x98', '\xf9', 'u', 'N', '\xbc'}; - success = tools::unserialize_obj_from_buff( - asset_descriptor_operation, - serialized_asset_descriptor_operation); - ASSERT_TRUE(success); + crypto::point_t expected_point_asset_id{}; + crypto::public_key expected_asset_id{}; + crypto::point_t calculated_point_asset_id{}; + crypto::public_key calculated_asset_id{}; + const std::optional ado{deserialize(serialization_method::boost, serialized_ado)}; - crypto::point_t calculated_asset_id_pt{}; - crypto::public_key calculated_asset_id_key{}; - - success = currency::get_or_calculate_asset_id(asset_descriptor_operation, - &calculated_asset_id_pt, - &calculated_asset_id_key); + ASSERT_TRUE(ado.has_value()); + ASSERT_EQ(ado.value().verion, 1); + /* TODO: fix the boost serialization of the asset_descriptor_operation_v0 objects: .description.version must be equals to 0. + ASSERT_EQ(ado.value().descriptor.version, 0); */ + ASSERT_EQ(ado.value().operation_type, ASSET_DESCRIPTOR_OPERATION_UNDEFINED); + success = currency::get_or_calculate_asset_id(ado.value(), &calculated_point_asset_id, &calculated_asset_id); ASSERT_FALSE(success); } -TEST(multiassets, get_or_calculate_asset_id_register_key_value_serialization) +TEST(multiassets, boost_serialization_get_or_calculate_asset_id_register) { - bool success{false}; - currency::asset_descriptor_operation asset_descriptor_operation{}; - const std::string serialized_asset_descriptor_operation{ - '{', '\x0d', '\x0a', ' ', ' ', '"', 'a', 'm', 'o', - 'u', 'n', 't', '_', 'c', 'o', 'm', 'm', 'i', - 't', 'm', 'e', 'n', 't', '"', ':', ' ', '"', - '0', '0', '0', '0', '0', '0', '0', '0', '0', - '0', '0', '0', '0', '0', '0', '0', '0', '0', - '0', '0', '0', '0', '0', '0', '0', '0', '0', - '0', '0', '0', '0', '0', '0', '0', '0', '0', - '0', '0', '0', '0', '0', '0', '0', '0', '0', - '0', '0', '0', '0', '0', '0', '0', '0', '0', - '0', '0', '0', '0', '0', '0', '0', '0', '0', - '0', '"', ',', '\x0d', '\x0a', ' ', ' ', '"', 'd', - 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', - '"', ':', ' ', '{', '\x0d', '\x0a', ' ', ' ', ' ', - ' ', '"', 'c', 'u', 'r', 'r', 'e', 'n', 't', - '_', 's', 'u', 'p', 'p', 'l', 'y', '"', ':', - ' ', '5', '0', ',', '\x0d', '\x0a', ' ', ' ', ' ', - ' ', '"', 'd', 'e', 'c', 'i', 'm', 'a', 'l', - '_', 'p', 'o', 'i', 'n', 't', '"', ':', ' ', - '0', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', - 'f', 'u', 'l', 'l', '_', 'n', 'a', 'm', 'e', - '"', ':', ' ', '"', 'H', 'E', 'L', 'L', 'O', - '_', 'W', 'O', 'R', 'L', 'D', '"', ',', '\x0d', - '\x0a', ' ', ' ', ' ', ' ', '"', 'h', 'i', 'd', - 'd', 'e', 'n', '_', 's', 'u', 'p', 'p', 'l', - 'y', '"', ':', ' ', 'f', 'a', 'l', 's', 'e', - ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'm', - 'e', 't', 'a', '_', 'i', 'n', 'f', 'o', '"', - ':', ' ', '"', 'H', 'e', 'l', 'l', 'o', ',', - ' ', 'w', 'o', 'r', 'l', 'd', '!', '"', ',', - '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'o', 'w', - 'n', 'e', 'r', '"', ':', ' ', '"', 'c', 'f', - '9', '3', 'b', 'e', 'a', 'd', '4', 'd', '2', - 'a', '8', 'd', '6', 'd', '1', '7', '4', 'c', - '1', '7', '5', '2', '2', '3', '7', 'b', '2', - 'e', '5', '2', '0', '8', 'a', '5', '9', '4', - 'b', '5', '9', '6', '1', '8', '6', '8', '3', - 'e', 'e', '5', '0', 'e', 'f', '2', '0', '9', - 'c', 'f', '1', 'e', 'f', 'b', '1', '9', '"', - ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 't', - 'i', 'c', 'k', 'e', 'r', '"', ':', ' ', '"', - 'H', 'L', 'O', '"', ',', '\x0d', '\x0a', ' ', ' ', - ' ', ' ', '"', 't', 'o', 't', 'a', 'l', '_', - 'm', 'a', 'x', '_', 's', 'u', 'p', 'p', 'l', - 'y', '"', ':', ' ', '1', '0', '0', '\x0d', '\x0a', - ' ', ' ', '}', ',', '\x0d', '\x0a', ' ', ' ', '"', - 'o', 'p', 'e', 'r', 'a', 't', 'i', 'o', 'n', - '_', 't', 'y', 'p', 'e', '"', ':', ' ', '1', - ',', '\x0d', '\x0a', ' ', ' ', '"', 'o', 'p', 't', - '_', 'a', 's', 's', 'e', 't', '_', 'i', 'd', - '"', ':', ' ', '"', '0', '1', '6', 'f', '4', - '6', '3', '2', '4', 'f', 'a', 'a', 'e', '4', - '4', '8', 'b', '9', 'e', '3', 'b', '9', '6', - 'd', 'a', 'c', '9', '4', 'd', 'a', '1', '7', - 'b', 'e', '6', 'a', 'b', '7', 'e', 'a', 'a', - 'b', 'a', '3', '9', '8', 'd', 'e', '8', '6', - 'd', '8', '7', '4', '3', '0', '4', '2', 'c', - '9', '8', 'b', 'a', 'c', 'e', '0', '"', '\x0d', - '\x0a', '}'}; + bool success{}; + const std::string serialized_ado{'\x16', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 's', 'e', 'r', 'i', 'a', 'l', 'i', 'z', 'a', 't', 'i', 'o', 'n', ':', ':', 'a', 'r', 'c', 'h', 'i', + 'v', 'e', '\x14', '\x00', '\x04', '\x04', '\x04', '\x08', '\x01', '\x00', '\x00', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00', '\x00', '\x00', 'd', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '2', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x03', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'H', 'L', 'O', + '\x0b', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'H', 'E', 'L', 'L', 'O', '_', 'W', 'O', 'R', 'L', 'D', '\x0d', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'H', 'e', + 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\x00', '\x00', '\x00', '\x00', '\x00', ' ', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\xcf', '\x93', '\xbe', '\xad', 'M', + '*', '\x8d', 'm', '\x17', 'L', '\x17', 'R', '#', '{', '.', 'R', '\x08', '\xa5', '\x94', '\xb5', '\x96', '\x18', 'h', '>', '\xe5', '\x0e', '\xf2', '\x09', '\xcf', '\x1e', '\xfb', '\x19', '\x00', + ' ', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00', '\x01', ' ', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}; - success = epee::serialization::load_t_from_json( - asset_descriptor_operation, - serialized_asset_descriptor_operation); + crypto::point_t expected_point_asset_id{}; + crypto::public_key expected_asset_id{}; + crypto::point_t calculated_point_asset_id{}; + crypto::public_key calculated_asset_id{}; + const std::optional ado{deserialize(serialization_method::boost, serialized_ado)}; + + ASSERT_TRUE(ado.has_value()); + ASSERT_EQ(ado.value().verion, 1); + /* TODO: fix the boost serialization of the asset_descriptor_operation_v0 objects: .description.version must be equals to 0. + ASSERT_EQ(ado.value().descriptor.version, 0); */ + ASSERT_EQ(ado.value().operation_type, ASSET_DESCRIPTOR_OPERATION_REGISTER); + success = currency::get_or_calculate_asset_id(ado.value(), &calculated_point_asset_id, &calculated_asset_id); ASSERT_TRUE(success); - - crypto::point_t calculated_asset_id_pt{}; - crypto::public_key calculated_asset_id_key{}; - - success = currency::get_or_calculate_asset_id(asset_descriptor_operation, - &calculated_asset_id_pt, - &calculated_asset_id_key); + success = expected_point_asset_id.from_string("6f46324faae448b9e3b96dac94da17be6ab7eaaba398de86d8743042c98bace0"); ASSERT_TRUE(success); - - const std::string expected_asset_id_str{ - "6f46324faae448b9e3b96dac94da17be6ab7eaaba398de86d8743042c98bace0"}; - - crypto::point_t expected_asset_id_pt{}; - success = expected_asset_id_pt.from_string(expected_asset_id_str); - ASSERT_TRUE(success); - - const crypto::public_key expected_asset_id_key{ - expected_asset_id_pt.to_public_key()}; - - ASSERT_EQ(calculated_asset_id_pt, expected_asset_id_pt); - ASSERT_EQ(calculated_asset_id_key, expected_asset_id_key); + ASSERT_EQ(calculated_point_asset_id, expected_point_asset_id); + expected_asset_id = expected_point_asset_id.to_public_key(); + ASSERT_EQ(calculated_asset_id, expected_asset_id); } -TEST(multiassets, get_or_calculate_asset_id_emit_key_value_serialization) +TEST(multiassets, boost_serialization_get_or_calculate_asset_id_emit) { - bool success{false}; - currency::asset_descriptor_operation asset_descriptor_operation{}; - const std::string serialized_asset_descriptor_operation{ - '{', '\x0d', '\x0a', ' ', ' ', '"', 'a', 'm', 'o', - 'u', 'n', 't', '_', 'c', 'o', 'm', 'm', 'i', - 't', 'm', 'e', 'n', 't', '"', ':', ' ', '"', - '0', '0', '0', '0', '0', '0', '0', '0', '0', - '0', '0', '0', '0', '0', '0', '0', '0', '0', - '0', '0', '0', '0', '0', '0', '0', '0', '0', - '0', '0', '0', '0', '0', '0', '0', '0', '0', - '0', '0', '0', '0', '0', '0', '0', '0', '0', - '0', '0', '0', '0', '0', '0', '0', '0', '0', - '0', '0', '0', '0', '0', '0', '0', '0', '0', - '0', '"', ',', '\x0d', '\x0a', ' ', ' ', '"', 'd', - 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', - '"', ':', ' ', '{', '\x0d', '\x0a', ' ', ' ', ' ', - ' ', '"', 'c', 'u', 'r', 'r', 'e', 'n', 't', - '_', 's', 'u', 'p', 'p', 'l', 'y', '"', ':', - ' ', '5', '0', ',', '\x0d', '\x0a', ' ', ' ', ' ', - ' ', '"', 'd', 'e', 'c', 'i', 'm', 'a', 'l', - '_', 'p', 'o', 'i', 'n', 't', '"', ':', ' ', - '0', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', - 'f', 'u', 'l', 'l', '_', 'n', 'a', 'm', 'e', - '"', ':', ' ', '"', 'H', 'E', 'L', 'L', 'O', - '_', 'W', 'O', 'R', 'L', 'D', '"', ',', '\x0d', - '\x0a', ' ', ' ', ' ', ' ', '"', 'h', 'i', 'd', - 'd', 'e', 'n', '_', 's', 'u', 'p', 'p', 'l', - 'y', '"', ':', ' ', 'f', 'a', 'l', 's', 'e', - ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'm', - 'e', 't', 'a', '_', 'i', 'n', 'f', 'o', '"', - ':', ' ', '"', 'H', 'e', 'l', 'l', 'o', ',', - ' ', 'w', 'o', 'r', 'l', 'd', '!', '"', ',', - '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'o', 'w', - 'n', 'e', 'r', '"', ':', ' ', '"', 'e', 'd', - 'a', 'b', '5', '7', '1', 'c', '4', 'b', 'e', - '9', 'e', 'a', 'b', 'f', 'e', 'a', '5', 'e', - '7', '8', '8', '3', '0', '3', '6', 'd', '7', - '4', '4', 'c', '0', '9', '7', '3', '8', '2', - 'e', 'b', '6', 'f', '7', '3', '9', 'a', '9', - '1', '4', 'd', 'b', '0', '6', 'f', '7', '2', - 'b', 'a', '3', '5', '0', '9', '9', 'd', '"', - ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 't', - 'i', 'c', 'k', 'e', 'r', '"', ':', ' ', '"', - 'H', 'L', 'O', '"', ',', '\x0d', '\x0a', ' ', ' ', - ' ', ' ', '"', 't', 'o', 't', 'a', 'l', '_', - 'm', 'a', 'x', '_', 's', 'u', 'p', 'p', 'l', - 'y', '"', ':', ' ', '1', '0', '0', '\x0d', '\x0a', - ' ', ' ', '}', ',', '\x0d', '\x0a', ' ', ' ', '"', - 'o', 'p', 'e', 'r', 'a', 't', 'i', 'o', 'n', - '_', 't', 'y', 'p', 'e', '"', ':', ' ', '2', - ',', '\x0d', '\x0a', ' ', ' ', '"', 'o', 'p', 't', - '_', 'a', 's', 's', 'e', 't', '_', 'i', 'd', - '"', ':', ' ', '"', '0', '1', '4', '9', 'a', - '3', 'd', '6', '6', '5', '2', 'a', 'a', 'a', - '0', 'b', '3', 'b', '7', '7', '2', '9', '2', - 'c', '5', '3', '4', 'e', '9', '1', 'f', 'f', - '8', '0', 'd', 'e', '9', '1', '2', '0', 'a', - 'e', 'b', '6', 'f', 'c', '1', 'c', '5', 'e', - 'd', 'c', '7', '2', '8', '0', '4', '7', '4', - '3', '7', 'd', '6', '6', '7', 'e', '"', '\x0d', - '\x0a', '}'}; + bool success{}; + const std::string serialized_ado{'\x16', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 's', 'e', 'r', 'i', 'a', 'l', 'i', 'z', 'a', 't', 'i', 'o', 'n', ':', ':', 'a', 'r', 'c', 'h', 'i', + 'v', 'e', '\x14', '\x00', '\x04', '\x04', '\x04', '\x08', '\x01', '\x00', '\x00', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00', '\x02', '\x00', '\x00', '\x00', '\x00', '\x00', 'd', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '2', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x03', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'H', 'L', 'O', + '\x0b', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'H', 'E', 'L', 'L', 'O', '_', 'W', 'O', 'R', 'L', 'D', '\x0d', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'H', 'e', + 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\x00', '\x00', '\x00', '\x00', '\x00', ' ', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\xed', '\xab', 'W', '\x1c', 'K', + '\xe9', '\xea', '\xbf', '\xea', '^', 'x', '\x83', '\x03', 'm', 't', 'L', '\x09', 's', '\x82', '\xeb', 'o', 's', '\x9a', '\x91', 'M', '\xb0', 'o', 'r', '\xba', '5', '\x09', '\x9d', '\x00', ' ', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00', '\x01', ' ', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', 'I', '\xa3', '\xd6', 'e', '*', '\xaa', '\x0b', ';', 'w', ')', ',', 'S', 'N', '\x91', '\xff', '\x80', '\xde', '\x91', ' ', '\xae', '\xb6', '\xfc', '\x1c', '^', + '\xdc', 'r', '\x80', 'G', 'C', '}', 'f', '~'}; - success = epee::serialization::load_t_from_json( - asset_descriptor_operation, - serialized_asset_descriptor_operation); + crypto::point_t expected_point_asset_id{}; + crypto::public_key expected_asset_id{}; + crypto::point_t calculated_point_asset_id{}; + crypto::public_key calculated_asset_id{}; + const std::optional ado{deserialize(serialization_method::boost, serialized_ado)}; + + ASSERT_TRUE(ado.has_value()); + ASSERT_EQ(ado.value().verion, 1); + /* TODO: fix the boost serialization of the asset_descriptor_operation_v0 objects: .description.version must be equals to 0. + ASSERT_EQ(ado.value().descriptor.version, 0); */ + ASSERT_EQ(ado.value().operation_type, ASSET_DESCRIPTOR_OPERATION_EMIT); + success = currency::get_or_calculate_asset_id(ado.value(), &calculated_point_asset_id, &calculated_asset_id); ASSERT_TRUE(success); - - crypto::point_t calculated_asset_id_pt{}; - crypto::public_key calculated_asset_id_key{}; - - success = currency::get_or_calculate_asset_id(asset_descriptor_operation, - &calculated_asset_id_pt, - &calculated_asset_id_key); + success = expected_point_asset_id.from_string("49a3d6652aaa0b3b77292c534e91ff80de9120aeb6fc1c5edc728047437d667e"); ASSERT_TRUE(success); - - const std::string expected_asset_id_str{ - "49a3d6652aaa0b3b77292c534e91ff80de9120aeb6fc1c5edc728047437d667e"}; - - crypto::point_t expected_asset_id_pt{}; - success = expected_asset_id_pt.from_string(expected_asset_id_str); - ASSERT_TRUE(success); - - crypto::public_key expected_asset_id_key{}; - expected_asset_id_pt.to_public_key(expected_asset_id_key); - ASSERT_EQ(calculated_asset_id_pt, expected_asset_id_pt); - ASSERT_EQ(calculated_asset_id_key, expected_asset_id_key); + ASSERT_EQ(calculated_point_asset_id, expected_point_asset_id); + expected_asset_id = expected_point_asset_id.to_public_key(); + ASSERT_EQ(calculated_asset_id, expected_asset_id); } -TEST(multiassets, get_or_calculate_asset_id_update_key_value_serialization) +TEST(multiassets, boost_serialization_get_or_calculate_asset_id_update) { - bool success{false}; - currency::asset_descriptor_operation_v0 asset_descriptor_operation{}; - const std::string serialized_asset_descriptor_operation{ - '{', '\x0d', '\x0a', ' ', ' ', '"', 'a', 'm', 'o', - 'u', 'n', 't', '_', 'c', 'o', 'm', 'm', 'i', - 't', 'm', 'e', 'n', 't', '"', ':', ' ', '"', - '0', '0', '0', '0', '0', '0', '0', '0', '0', - '0', '0', '0', '0', '0', '0', '0', '0', '0', - '0', '0', '0', '0', '0', '0', '0', '0', '0', - '0', '0', '0', '0', '0', '0', '0', '0', '0', - '0', '0', '0', '0', '0', '0', '0', '0', '0', - '0', '0', '0', '0', '0', '0', '0', '0', '0', - '0', '0', '0', '0', '0', '0', '0', '0', '0', - '0', '"', ',', '\x0d', '\x0a', ' ', ' ', '"', 'd', - 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', - '"', ':', ' ', '{', '\x0d', '\x0a', ' ', ' ', ' ', - ' ', '"', 'c', 'u', 'r', 'r', 'e', 'n', 't', - '_', 's', 'u', 'p', 'p', 'l', 'y', '"', ':', - ' ', '5', '0', ',', '\x0d', '\x0a', ' ', ' ', ' ', - ' ', '"', 'd', 'e', 'c', 'i', 'm', 'a', 'l', - '_', 'p', 'o', 'i', 'n', 't', '"', ':', ' ', - '0', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', - 'f', 'u', 'l', 'l', '_', 'n', 'a', 'm', 'e', - '"', ':', ' ', '"', 'H', 'E', 'L', 'L', 'O', - '_', 'W', 'O', 'R', 'L', 'D', '"', ',', '\x0d', - '\x0a', ' ', ' ', ' ', ' ', '"', 'h', 'i', 'd', - 'd', 'e', 'n', '_', 's', 'u', 'p', 'p', 'l', - 'y', '"', ':', ' ', 'f', 'a', 'l', 's', 'e', - ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'm', - 'e', 't', 'a', '_', 'i', 'n', 'f', 'o', '"', - ':', ' ', '"', 'H', 'e', 'l', 'l', 'o', ',', - ' ', 'w', 'o', 'r', 'l', 'd', '!', '"', ',', - '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'o', 'w', - 'n', 'e', 'r', '"', ':', ' ', '"', '8', 'c', - 'b', '6', '3', '4', '9', 'f', '5', '1', 'd', - 'a', '6', '5', '9', '9', 'f', 'e', 'e', 'a', - 'e', '7', 'c', '0', '0', '7', '7', '2', '9', - '3', '4', '3', '6', 'e', 'b', '6', 'a', '5', - '0', '0', '0', 'f', '0', 'e', '6', 'e', '7', - '0', '6', 'e', '7', '7', '8', '8', '6', 'b', - 'b', '5', '4', '0', 'e', '2', 'c', '1', '"', - ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 't', - 'i', 'c', 'k', 'e', 'r', '"', ':', ' ', '"', - 'H', 'L', 'O', '"', ',', '\x0d', '\x0a', ' ', ' ', - ' ', ' ', '"', 't', 'o', 't', 'a', 'l', '_', - 'm', 'a', 'x', '_', 's', 'u', 'p', 'p', 'l', - 'y', '"', ':', ' ', '1', '0', '0', '\x0d', '\x0a', - ' ', ' ', '}', ',', '\x0d', '\x0a', ' ', ' ', '"', - 'o', 'p', 'e', 'r', 'a', 't', 'i', 'o', 'n', - '_', 't', 'y', 'p', 'e', '"', ':', ' ', '3', - ',', '\x0d', '\x0a', ' ', ' ', '"', 'o', 'p', 't', - '_', 'a', 's', 's', 'e', 't', '_', 'i', 'd', - '"', ':', ' ', '"', '0', '1', 'c', '3', '7', - '1', 'f', '6', '0', 'd', 'd', '8', '3', '3', - '3', '2', '9', '8', 'c', '6', 'a', 'a', '7', - '4', '6', 'b', '7', '1', 'e', '1', 'e', '2', - '0', '5', '2', '7', 'b', '1', 'f', 'f', '5', - 'e', '1', 'b', 'e', 'd', '4', 'e', 'a', '9', - 'b', '5', 'f', '5', '9', '2', 'f', 'a', 'd', - 'f', '9', '0', 'e', 'd', '6', 'b', '"', '\x0d', - '\x0a', '}'}; + bool success{}; + const std::string serialized_ado{'\x16', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 's', 'e', 'r', 'i', 'a', 'l', 'i', 'z', 'a', 't', 'i', 'o', 'n', ':', ':', 'a', 'r', 'c', 'h', 'i', + 'v', 'e', '\x14', '\x00', '\x04', '\x04', '\x04', '\x08', '\x01', '\x00', '\x00', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00', '\x03', '\x00', '\x00', '\x00', '\x00', '\x00', 'd', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '2', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x03', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'H', 'L', 'O', + '\x0b', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'H', 'E', 'L', 'L', 'O', '_', 'W', 'O', 'R', 'L', 'D', '\x0d', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'H', 'e', + 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\x00', '\x00', '\x00', '\x00', '\x00', ' ', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x8c', '\xb6', '4', '\x9f', 'Q', + '\xda', 'e', '\x99', '\xfe', '\xea', '\xe7', '\xc0', '\x07', 'r', '\x93', 'C', 'n', '\xb6', '\xa5', '\x00', '\x0f', '\x0e', 'n', 'p', 'n', 'w', '\x88', 'k', '\xb5', '@', '\xe2', '\xc1', '\x00', + ' ', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00', '\x01', ' ', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\xc3', 'q', '\xf6', '\x0d', '\xd8', '3', '2', '\x98', '\xc6', '\xaa', 't', 'k', 'q', '\xe1', '\xe2', '\x05', '\'', '\xb1', '\xff', '^', '\x1b', '\xed', 'N', + '\xa9', '\xb5', '\xf5', '\x92', '\xfa', '\xdf', '\x90', '\xed', 'k'}; - success = epee::serialization::load_t_from_json( - asset_descriptor_operation, - serialized_asset_descriptor_operation); + crypto::point_t expected_point_asset_id{}; + crypto::public_key expected_asset_id{}; + crypto::point_t calculated_point_asset_id{}; + crypto::public_key calculated_asset_id{}; + const std::optional ado{deserialize(serialization_method::boost, serialized_ado)}; + + ASSERT_TRUE(ado.has_value()); + ASSERT_EQ(ado.value().verion, 1); + /* TODO: fix the boost serialization of the asset_descriptor_operation_v0 objects: .description.version must be equals to 0. + ASSERT_EQ(ado.value().descriptor.version, 0); */ + ASSERT_EQ(ado.value().operation_type, ASSET_DESCRIPTOR_OPERATION_UPDATE); + success = currency::get_or_calculate_asset_id(ado.value(), &calculated_point_asset_id, &calculated_asset_id); ASSERT_TRUE(success); - - crypto::point_t calculated_asset_id_pt{}; - crypto::public_key calculated_asset_id_key{}; - - success = currency::get_or_calculate_asset_id(asset_descriptor_operation, - &calculated_asset_id_pt, - &calculated_asset_id_key); + success = expected_point_asset_id.from_string("c371f60dd8333298c6aa746b71e1e20527b1ff5e1bed4ea9b5f592fadf90ed6b"); ASSERT_TRUE(success); - - const std::string expected_asset_id_str{ - "c371f60dd8333298c6aa746b71e1e20527b1ff5e1bed4ea9b5f592fadf90ed6b"}; - - crypto::point_t expected_asset_id_pt{}; - success = expected_asset_id_pt.from_string(expected_asset_id_str); - ASSERT_TRUE(success); - - crypto::public_key expected_asset_id_key{}; - expected_asset_id_pt.to_public_key(expected_asset_id_key); - ASSERT_EQ(calculated_asset_id_pt, expected_asset_id_pt); - ASSERT_EQ(calculated_asset_id_key, expected_asset_id_key); + ASSERT_EQ(calculated_point_asset_id, expected_point_asset_id); + expected_asset_id = expected_point_asset_id.to_public_key(); + ASSERT_EQ(calculated_asset_id, expected_asset_id); } -TEST(multiassets, get_or_calculate_asset_id_undefined_key_value_serialization) +TEST(multiassets, boost_serialization_get_or_calculate_asset_id_public_burn) { - bool success{false}; - currency::asset_descriptor_operation_v0 asset_descriptor_operation{}; - const std::string serialized_asset_descriptor_operation{ - '{', '\x0d', '\x0a', ' ', ' ', '"', 'a', 'm', 'o', - 'u', 'n', 't', '_', 'c', 'o', 'm', 'm', 'i', - 't', 'm', 'e', 'n', 't', '"', ':', ' ', '"', - '0', '0', '0', '0', '0', '0', '0', '0', '0', - '0', '0', '0', '0', '0', '0', '0', '0', '0', - '0', '0', '0', '0', '0', '0', '0', '0', '0', - '0', '0', '0', '0', '0', '0', '0', '0', '0', - '0', '0', '0', '0', '0', '0', '0', '0', '0', - '0', '0', '0', '0', '0', '0', '0', '0', '0', - '0', '0', '0', '0', '0', '0', '0', '0', '0', - '0', '"', ',', '\x0d', '\x0a', ' ', ' ', '"', 'd', - 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', - '"', ':', ' ', '{', '\x0d', '\x0a', ' ', ' ', ' ', - ' ', '"', 'c', 'u', 'r', 'r', 'e', 'n', 't', - '_', 's', 'u', 'p', 'p', 'l', 'y', '"', ':', - ' ', '5', '0', ',', '\x0d', '\x0a', ' ', ' ', ' ', - ' ', '"', 'd', 'e', 'c', 'i', 'm', 'a', 'l', - '_', 'p', 'o', 'i', 'n', 't', '"', ':', ' ', - '0', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', - 'f', 'u', 'l', 'l', '_', 'n', 'a', 'm', 'e', - '"', ':', ' ', '"', 'H', 'E', 'L', 'L', 'O', - '_', 'W', 'O', 'R', 'L', 'D', '"', ',', '\x0d', - '\x0a', ' ', ' ', ' ', ' ', '"', 'h', 'i', 'd', - 'd', 'e', 'n', '_', 's', 'u', 'p', 'p', 'l', - 'y', '"', ':', ' ', 'f', 'a', 'l', 's', 'e', - ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'm', - 'e', 't', 'a', '_', 'i', 'n', 'f', 'o', '"', - ':', ' ', '"', 'H', 'e', 'l', 'l', 'o', ',', - ' ', 'w', 'o', 'r', 'l', 'd', '!', '"', ',', - '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'o', 'w', - 'n', 'e', 'r', '"', ':', ' ', '"', 'e', '9', - '1', 'b', '9', 'a', '7', '3', '2', '9', '2', - 'd', '6', 'e', 'a', '4', '6', 'f', 'b', '3', - 'd', '4', 'f', '4', 'c', 'c', '7', '9', 'c', - '3', '4', 'b', 'f', 'b', '7', 'd', '1', '4', - 'c', '2', 'e', '6', '8', '4', 'e', '5', '8', - '0', '9', '3', 'a', '2', '4', '7', '1', 'c', - '9', '2', 'e', '5', '1', 'c', '1', '6', '"', - ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 't', - 'i', 'c', 'k', 'e', 'r', '"', ':', ' ', '"', - 'H', 'L', 'O', '"', ',', '\x0d', '\x0a', ' ', ' ', - ' ', ' ', '"', 't', 'o', 't', 'a', 'l', '_', - 'm', 'a', 'x', '_', 's', 'u', 'p', 'p', 'l', - 'y', '"', ':', ' ', '1', '0', '0', '\x0d', '\x0a', - ' ', ' ', '}', ',', '\x0d', '\x0a', ' ', ' ', '"', - 'o', 'p', 'e', 'r', 'a', 't', 'i', 'o', 'n', - '_', 't', 'y', 'p', 'e', '"', ':', ' ', '0', - ',', '\x0d', '\x0a', ' ', ' ', '"', 'o', 'p', 't', - '_', 'a', 's', 's', 'e', 't', '_', 'i', 'd', - '"', ':', ' ', '"', '0', '1', '9', '7', '9', - 'e', 'b', '7', '0', '6', 'a', 'c', 'e', '2', - 'e', 'b', '8', '3', 'f', '9', '1', '2', '5', - '6', '5', '8', 'b', '2', '3', 'f', 'b', '3', - '5', '2', '2', '0', '8', '4', '8', '0', 'c', - 'b', '3', 'b', '9', '0', 'c', '4', '3', 'e', - '2', 'd', 'f', '0', 'd', '2', '9', '8', 'f', - '9', '7', '5', '4', 'e', 'b', 'c', '"', '\x0d', - '\x0a', '}'}; + bool success{}; + const std::string serialized_ado{'\x16', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 's', 'e', 'r', 'i', 'a', 'l', 'i', 'z', 'a', 't', 'i', 'o', 'n', ':', ':', 'a', 'r', 'c', 'h', 'i', + 'v', 'e', '\x14', '\x00', '\x04', '\x04', '\x04', '\x08', '\x01', '\x00', '\x00', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00', '\x04', '\x00', '\x00', '\x00', '\x00', '\x00', 'd', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '2', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x03', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'H', 'L', 'O', + '\x0b', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'H', 'E', 'L', 'L', 'O', '_', 'W', 'O', 'R', 'L', 'D', '\x0d', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', 'H', 'e', + 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\x00', '\x00', '\x00', '\x00', '\x00', ' ', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x0c', '@', '\x8c', '\xf8', '\xb7', + '\xfb', '\x80', '\x8f', '@', 'Y', '=', 'n', '\xb7', 'X', '\x90', '\xe2', '\xab', '=', '\x0c', '\xcd', '\xc7', '\x01', 'J', '\x7f', '\xc6', '\xb6', '\xab', '\x05', '\x16', ';', '\xe0', '`', '\x00', + ' ', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x01', '\x00', '\x00', '\x00', '\x01', ' ', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', 'T', '\xf3', '\xf7', ',', 'r', '\xe5', '\xb0', '\x14', '\xad', '+', '+', '\x90', '\x01', '\xac', '\xef', '\x95', 'O', '\xe8', '-', '\xd3', '\xed', 'V', '\xa3', + '\x8c', '\xd9', '\xdd', '\xc5', '\xdb', 'W', 'g', '?', '\x8f'}; - success = epee::serialization::load_t_from_json( - asset_descriptor_operation, - serialized_asset_descriptor_operation); + crypto::point_t expected_point_asset_id{}; + crypto::public_key expected_asset_id{}; + crypto::point_t calculated_point_asset_id{}; + crypto::public_key calculated_asset_id{}; + const std::optional ado{deserialize(serialization_method::boost, serialized_ado)}; + + ASSERT_TRUE(ado.has_value()); + ASSERT_EQ(ado.value().verion, 1); + /* TODO: fix the boost serialization of the asset_descriptor_operation_v0 objects: .description.version must be equals to 0. + ASSERT_EQ(ado.value().descriptor.version, 0); */ + ASSERT_EQ(ado.value().operation_type, ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN); + success = currency::get_or_calculate_asset_id(ado.value(), &calculated_point_asset_id, &calculated_asset_id); ASSERT_TRUE(success); + success = expected_point_asset_id.from_string("54f3f72c72e5b014ad2b2b9001acef954fe82dd3ed56a38cd9ddc5db57673f8f"); + ASSERT_TRUE(success); + ASSERT_EQ(calculated_point_asset_id, expected_point_asset_id); + expected_asset_id = expected_point_asset_id.to_public_key(); + ASSERT_EQ(calculated_asset_id, expected_asset_id); +} - crypto::point_t calculated_asset_id_pt{}; - crypto::public_key calculated_asset_id_key{}; +TEST(multiassets, key_value_serialization_get_or_calculate_asset_id_undefined) +{ + bool success{}; + const std::string serialized_ado{'{', '\x0d', '\x0a', ' ', ' ', '"', 'a', 'm', 'o', 'u', 'n', 't', '_', 'c', 'o', 'm', 'm', 'i', 't', 'm', 'e', 'n', 't', '"', ':', ' ', '"', '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '"', ',', '\x0d', '\x0a', ' ', ' ', '"', 'd', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', '"', + ':', ' ', '{', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'c', 'u', 'r', 'r', 'e', 'n', 't', '_', 's', 'u', 'p', 'p', 'l', 'y', '"', ':', ' ', '5', '0', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', + 'd', 'e', 'c', 'i', 'm', 'a', 'l', '_', 'p', 'o', 'i', 'n', 't', '"', ':', ' ', '0', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'f', 'u', 'l', 'l', '_', 'n', 'a', 'm', 'e', '"', ':', ' ', '"', + 'H', 'E', 'L', 'L', 'O', '_', 'W', 'O', 'R', 'L', 'D', '"', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'h', 'i', 'd', 'd', 'e', 'n', '_', 's', 'u', 'p', 'p', 'l', 'y', '"', ':', ' ', 'f', 'a', + 'l', 's', 'e', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'm', 'e', 't', 'a', '_', 'i', 'n', 'f', 'o', '"', ':', ' ', '"', 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '"', + ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'o', 'w', 'n', 'e', 'r', '"', ':', ' ', '"', 'e', '9', '1', 'b', '9', 'a', '7', '3', '2', '9', '2', 'd', '6', 'e', 'a', '4', '6', 'f', 'b', '3', 'd', + '4', 'f', '4', 'c', 'c', '7', '9', 'c', '3', '4', 'b', 'f', 'b', '7', 'd', '1', '4', 'c', '2', 'e', '6', '8', '4', 'e', '5', '8', '0', '9', '3', 'a', '2', '4', '7', '1', 'c', '9', '2', 'e', '5', + '1', 'c', '1', '6', '"', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 't', 'i', 'c', 'k', 'e', 'r', '"', ':', ' ', '"', 'H', 'L', 'O', '"', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 't', 'o', + 't', 'a', 'l', '_', 'm', 'a', 'x', '_', 's', 'u', 'p', 'p', 'l', 'y', '"', ':', ' ', '1', '0', '0', '\x0d', '\x0a', ' ', ' ', '}', ',', '\x0d', '\x0a', ' ', ' ', '"', 'o', 'p', 'e', 'r', 'a', 't', + 'i', 'o', 'n', '_', 't', 'y', 'p', 'e', '"', ':', ' ', '0', ',', '\x0d', '\x0a', ' ', ' ', '"', 'o', 'p', 't', '_', 'a', 's', 's', 'e', 't', '_', 'i', 'd', '"', ':', ' ', '"', '9', '7', '9', 'e', + 'b', '7', '0', '6', 'a', 'c', 'e', '2', 'e', 'b', '8', '3', 'f', '9', '1', '2', '5', '6', '5', '8', 'b', '2', '3', 'f', 'b', '3', '5', '2', '2', '0', '8', '4', '8', '0', 'c', 'b', '3', 'b', '9', + '0', 'c', '4', '3', 'e', '2', 'd', 'f', '0', 'd', '2', '9', '8', 'f', '9', '7', '5', '4', 'e', 'b', 'c', '"', '\x0d', '\x0a', '}'}; - success = currency::get_or_calculate_asset_id(asset_descriptor_operation, - &calculated_asset_id_pt, - &calculated_asset_id_key); + crypto::point_t expected_point_asset_id{}; + crypto::public_key expected_asset_id{}; + crypto::point_t calculated_point_asset_id{}; + crypto::public_key calculated_asset_id{}; + const std::optional ado{deserialize(serialization_method::key_value, serialized_ado)}; + + ASSERT_TRUE(ado.has_value()); + ASSERT_EQ(ado.value().verion, 1); + /* TODO: fix the key-value serialization of the asset_descriptor_operation_v0 objects: .description.version must be equals to 0. + ASSERT_EQ(ado.value().descriptor.version, 0); */ + ASSERT_EQ(ado.value().operation_type, ASSET_DESCRIPTOR_OPERATION_UNDEFINED); + success = currency::get_or_calculate_asset_id(ado.value(), &calculated_point_asset_id, &calculated_asset_id); ASSERT_FALSE(success); } + +TEST(multiassets, key_value_serialization_get_or_calculate_asset_id_register) +{ + bool success{}; + const std::string serialized_ado{'{', '\x0d', '\x0a', ' ', ' ', '"', 'a', 'm', 'o', 'u', 'n', 't', '_', 'c', 'o', 'm', 'm', 'i', 't', 'm', 'e', 'n', 't', '"', ':', ' ', '"', '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '"', ',', '\x0d', '\x0a', ' ', ' ', '"', 'd', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', '"', + ':', ' ', '{', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'c', 'u', 'r', 'r', 'e', 'n', 't', '_', 's', 'u', 'p', 'p', 'l', 'y', '"', ':', ' ', '5', '0', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', + 'd', 'e', 'c', 'i', 'm', 'a', 'l', '_', 'p', 'o', 'i', 'n', 't', '"', ':', ' ', '0', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'f', 'u', 'l', 'l', '_', 'n', 'a', 'm', 'e', '"', ':', ' ', '"', + 'H', 'E', 'L', 'L', 'O', '_', 'W', 'O', 'R', 'L', 'D', '"', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'h', 'i', 'd', 'd', 'e', 'n', '_', 's', 'u', 'p', 'p', 'l', 'y', '"', ':', ' ', 'f', 'a', + 'l', 's', 'e', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'm', 'e', 't', 'a', '_', 'i', 'n', 'f', 'o', '"', ':', ' ', '"', 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '"', + ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'o', 'w', 'n', 'e', 'r', '"', ':', ' ', '"', 'c', 'f', '9', '3', 'b', 'e', 'a', 'd', '4', 'd', '2', 'a', '8', 'd', '6', 'd', '1', '7', '4', 'c', '1', + '7', '5', '2', '2', '3', '7', 'b', '2', 'e', '5', '2', '0', '8', 'a', '5', '9', '4', 'b', '5', '9', '6', '1', '8', '6', '8', '3', 'e', 'e', '5', '0', 'e', 'f', '2', '0', '9', 'c', 'f', '1', 'e', + 'f', 'b', '1', '9', '"', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 't', 'i', 'c', 'k', 'e', 'r', '"', ':', ' ', '"', 'H', 'L', 'O', '"', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 't', 'o', + 't', 'a', 'l', '_', 'm', 'a', 'x', '_', 's', 'u', 'p', 'p', 'l', 'y', '"', ':', ' ', '1', '0', '0', '\x0d', '\x0a', ' ', ' ', '}', ',', '\x0d', '\x0a', ' ', ' ', '"', 'o', 'p', 'e', 'r', 'a', 't', + 'i', 'o', 'n', '_', 't', 'y', 'p', 'e', '"', ':', ' ', '1', ',', '\x0d', '\x0a', ' ', ' ', '"', 'o', 'p', 't', '_', 'a', 's', 's', 'e', 't', '_', 'i', 'd', '"', ':', ' ', '"', '0', '0', '0', '0', + '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '"', '\x0d', '\x0a', '}'}; + + crypto::point_t expected_point_asset_id{}; + crypto::public_key expected_asset_id{}; + crypto::point_t calculated_point_asset_id{}; + crypto::public_key calculated_asset_id{}; + const std::optional ado{deserialize(serialization_method::key_value, serialized_ado)}; + + ASSERT_TRUE(ado.has_value()); + ASSERT_EQ(ado.value().verion, 1); + /* TODO: fix the key-value serialization of the asset_descriptor_operation_v0 objects: .description.version must be equals to 0. + ASSERT_EQ(ado.value().descriptor.version, 0); */ + ASSERT_EQ(ado.value().operation_type, ASSET_DESCRIPTOR_OPERATION_REGISTER); + success = currency::get_or_calculate_asset_id(ado.value(), &calculated_point_asset_id, &calculated_asset_id); + ASSERT_TRUE(success); + success = expected_point_asset_id.from_string("6f46324faae448b9e3b96dac94da17be6ab7eaaba398de86d8743042c98bace0"); + ASSERT_TRUE(success); + ASSERT_EQ(calculated_point_asset_id, expected_point_asset_id); + expected_asset_id = expected_point_asset_id.to_public_key(); + ASSERT_EQ(calculated_asset_id, expected_asset_id); +} + +TEST(multiassets, key_value_serialization_get_or_calculate_asset_id_emit) +{ + bool success{}; + const std::string serialized_ado{'{', '\x0d', '\x0a', ' ', ' ', '"', 'a', 'm', 'o', 'u', 'n', 't', '_', 'c', 'o', 'm', 'm', 'i', 't', 'm', 'e', 'n', 't', '"', ':', ' ', '"', '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '"', ',', '\x0d', '\x0a', ' ', ' ', '"', 'd', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', '"', + ':', ' ', '{', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'c', 'u', 'r', 'r', 'e', 'n', 't', '_', 's', 'u', 'p', 'p', 'l', 'y', '"', ':', ' ', '5', '0', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', + 'd', 'e', 'c', 'i', 'm', 'a', 'l', '_', 'p', 'o', 'i', 'n', 't', '"', ':', ' ', '0', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'f', 'u', 'l', 'l', '_', 'n', 'a', 'm', 'e', '"', ':', ' ', '"', + 'H', 'E', 'L', 'L', 'O', '_', 'W', 'O', 'R', 'L', 'D', '"', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'h', 'i', 'd', 'd', 'e', 'n', '_', 's', 'u', 'p', 'p', 'l', 'y', '"', ':', ' ', 'f', 'a', + 'l', 's', 'e', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'm', 'e', 't', 'a', '_', 'i', 'n', 'f', 'o', '"', ':', ' ', '"', 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '"', + ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'o', 'w', 'n', 'e', 'r', '"', ':', ' ', '"', 'e', 'd', 'a', 'b', '5', '7', '1', 'c', '4', 'b', 'e', '9', 'e', 'a', 'b', 'f', 'e', 'a', '5', 'e', '7', + '8', '8', '3', '0', '3', '6', 'd', '7', '4', '4', 'c', '0', '9', '7', '3', '8', '2', 'e', 'b', '6', 'f', '7', '3', '9', 'a', '9', '1', '4', 'd', 'b', '0', '6', 'f', '7', '2', 'b', 'a', '3', '5', + '0', '9', '9', 'd', '"', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 't', 'i', 'c', 'k', 'e', 'r', '"', ':', ' ', '"', 'H', 'L', 'O', '"', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 't', 'o', + 't', 'a', 'l', '_', 'm', 'a', 'x', '_', 's', 'u', 'p', 'p', 'l', 'y', '"', ':', ' ', '1', '0', '0', '\x0d', '\x0a', ' ', ' ', '}', ',', '\x0d', '\x0a', ' ', ' ', '"', 'o', 'p', 'e', 'r', 'a', 't', + 'i', 'o', 'n', '_', 't', 'y', 'p', 'e', '"', ':', ' ', '2', ',', '\x0d', '\x0a', ' ', ' ', '"', 'o', 'p', 't', '_', 'a', 's', 's', 'e', 't', '_', 'i', 'd', '"', ':', ' ', '"', '4', '9', 'a', '3', + 'd', '6', '6', '5', '2', 'a', 'a', 'a', '0', 'b', '3', 'b', '7', '7', '2', '9', '2', 'c', '5', '3', '4', 'e', '9', '1', 'f', 'f', '8', '0', 'd', 'e', '9', '1', '2', '0', 'a', 'e', 'b', '6', 'f', + 'c', '1', 'c', '5', 'e', 'd', 'c', '7', '2', '8', '0', '4', '7', '4', '3', '7', 'd', '6', '6', '7', 'e', '"', '\x0d', '\x0a', '}'}; + + crypto::point_t expected_point_asset_id{}; + crypto::public_key expected_asset_id{}; + crypto::point_t calculated_point_asset_id{}; + crypto::public_key calculated_asset_id{}; + const std::optional ado{deserialize(serialization_method::key_value, serialized_ado)}; + + ASSERT_TRUE(ado.has_value()); + ASSERT_EQ(ado.value().verion, 1); + /* TODO: fix the key-value serialization of the asset_descriptor_operation_v0 objects: .description.version must be equals to 0. + ASSERT_EQ(ado.value().descriptor.version, 0); */ + ASSERT_EQ(ado.value().operation_type, ASSET_DESCRIPTOR_OPERATION_EMIT); + success = currency::get_or_calculate_asset_id(ado.value(), &calculated_point_asset_id, &calculated_asset_id); + ASSERT_TRUE(success); + success = expected_point_asset_id.from_string("49a3d6652aaa0b3b77292c534e91ff80de9120aeb6fc1c5edc728047437d667e"); + ASSERT_TRUE(success); + ASSERT_EQ(calculated_point_asset_id, expected_point_asset_id); + expected_asset_id = expected_point_asset_id.to_public_key(); + ASSERT_EQ(calculated_asset_id, expected_asset_id); +} + +TEST(multiassets, key_value_serialization_get_or_calculate_asset_id_update) +{ + bool success{}; + const std::string serialized_ado{'{', '\x0d', '\x0a', ' ', ' ', '"', 'a', 'm', 'o', 'u', 'n', 't', '_', 'c', 'o', 'm', 'm', 'i', 't', 'm', 'e', 'n', 't', '"', ':', ' ', '"', '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '"', ',', '\x0d', '\x0a', ' ', ' ', '"', 'd', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', '"', + ':', ' ', '{', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'c', 'u', 'r', 'r', 'e', 'n', 't', '_', 's', 'u', 'p', 'p', 'l', 'y', '"', ':', ' ', '5', '0', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', + 'd', 'e', 'c', 'i', 'm', 'a', 'l', '_', 'p', 'o', 'i', 'n', 't', '"', ':', ' ', '0', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'f', 'u', 'l', 'l', '_', 'n', 'a', 'm', 'e', '"', ':', ' ', '"', + 'H', 'E', 'L', 'L', 'O', '_', 'W', 'O', 'R', 'L', 'D', '"', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'h', 'i', 'd', 'd', 'e', 'n', '_', 's', 'u', 'p', 'p', 'l', 'y', '"', ':', ' ', 'f', 'a', + 'l', 's', 'e', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'm', 'e', 't', 'a', '_', 'i', 'n', 'f', 'o', '"', ':', ' ', '"', 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '"', + ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'o', 'w', 'n', 'e', 'r', '"', ':', ' ', '"', '8', 'c', 'b', '6', '3', '4', '9', 'f', '5', '1', 'd', 'a', '6', '5', '9', '9', 'f', 'e', 'e', 'a', 'e', + '7', 'c', '0', '0', '7', '7', '2', '9', '3', '4', '3', '6', 'e', 'b', '6', 'a', '5', '0', '0', '0', 'f', '0', 'e', '6', 'e', '7', '0', '6', 'e', '7', '7', '8', '8', '6', 'b', 'b', '5', '4', '0', + 'e', '2', 'c', '1', '"', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 't', 'i', 'c', 'k', 'e', 'r', '"', ':', ' ', '"', 'H', 'L', 'O', '"', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 't', 'o', + 't', 'a', 'l', '_', 'm', 'a', 'x', '_', 's', 'u', 'p', 'p', 'l', 'y', '"', ':', ' ', '1', '0', '0', '\x0d', '\x0a', ' ', ' ', '}', ',', '\x0d', '\x0a', ' ', ' ', '"', 'o', 'p', 'e', 'r', 'a', 't', + 'i', 'o', 'n', '_', 't', 'y', 'p', 'e', '"', ':', ' ', '3', ',', '\x0d', '\x0a', ' ', ' ', '"', 'o', 'p', 't', '_', 'a', 's', 's', 'e', 't', '_', 'i', 'd', '"', ':', ' ', '"', 'c', '3', '7', '1', + 'f', '6', '0', 'd', 'd', '8', '3', '3', '3', '2', '9', '8', 'c', '6', 'a', 'a', '7', '4', '6', 'b', '7', '1', 'e', '1', 'e', '2', '0', '5', '2', '7', 'b', '1', 'f', 'f', '5', 'e', '1', 'b', 'e', + 'd', '4', 'e', 'a', '9', 'b', '5', 'f', '5', '9', '2', 'f', 'a', 'd', 'f', '9', '0', 'e', 'd', '6', 'b', '"', '\x0d', '\x0a', '}'}; + + crypto::point_t expected_point_asset_id{}; + crypto::public_key expected_asset_id{}; + crypto::point_t calculated_point_asset_id{}; + crypto::public_key calculated_asset_id{}; + const std::optional ado{deserialize(serialization_method::key_value, serialized_ado)}; + + ASSERT_TRUE(ado.has_value()); + ASSERT_EQ(ado.value().verion, 1); + /* TODO: fix the key-value serialization of the asset_descriptor_operation_v0 objects: .description.version must be equals to 0. + ASSERT_EQ(ado.value().descriptor.version, 0); */ + ASSERT_EQ(ado.value().operation_type, ASSET_DESCRIPTOR_OPERATION_UPDATE); + success = currency::get_or_calculate_asset_id(ado.value(), &calculated_point_asset_id, &calculated_asset_id); + ASSERT_TRUE(success); + success = expected_point_asset_id.from_string("c371f60dd8333298c6aa746b71e1e20527b1ff5e1bed4ea9b5f592fadf90ed6b"); + ASSERT_TRUE(success); + ASSERT_EQ(calculated_point_asset_id, expected_point_asset_id); + expected_asset_id = expected_point_asset_id.to_public_key(); + ASSERT_EQ(calculated_asset_id, expected_asset_id); +} + +TEST(multiassets, key_value_serialization_get_or_calculate_asset_id_public_burn) +{ + bool success{}; + const std::string serialized_ado{'{', '\x0d', '\x0a', ' ', ' ', '"', 'a', 'm', 'o', 'u', 'n', 't', '_', 'c', 'o', 'm', 'm', 'i', 't', 'm', 'e', 'n', 't', '"', ':', ' ', '"', '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', + '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '"', ',', '\x0d', '\x0a', ' ', ' ', '"', 'd', 'e', 's', 'c', 'r', 'i', 'p', 't', 'o', 'r', '"', + ':', ' ', '{', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'c', 'u', 'r', 'r', 'e', 'n', 't', '_', 's', 'u', 'p', 'p', 'l', 'y', '"', ':', ' ', '5', '0', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', + 'd', 'e', 'c', 'i', 'm', 'a', 'l', '_', 'p', 'o', 'i', 'n', 't', '"', ':', ' ', '0', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'f', 'u', 'l', 'l', '_', 'n', 'a', 'm', 'e', '"', ':', ' ', '"', + 'H', 'E', 'L', 'L', 'O', '_', 'W', 'O', 'R', 'L', 'D', '"', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'h', 'i', 'd', 'd', 'e', 'n', '_', 's', 'u', 'p', 'p', 'l', 'y', '"', ':', ' ', 'f', 'a', + 'l', 's', 'e', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'm', 'e', 't', 'a', '_', 'i', 'n', 'f', 'o', '"', ':', ' ', '"', 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '"', + ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 'o', 'w', 'n', 'e', 'r', '"', ':', ' ', '"', '0', 'c', '4', '0', '8', 'c', 'f', '8', 'b', '7', 'f', 'b', '8', '0', '8', 'f', '4', '0', '5', '9', '3', + 'd', '6', 'e', 'b', '7', '5', '8', '9', '0', 'e', '2', 'a', 'b', '3', 'd', '0', 'c', 'c', 'd', 'c', '7', '0', '1', '4', 'a', '7', 'f', 'c', '6', 'b', '6', 'a', 'b', '0', '5', '1', '6', '3', 'b', + 'e', '0', '6', '0', '"', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 't', 'i', 'c', 'k', 'e', 'r', '"', ':', ' ', '"', 'H', 'L', 'O', '"', ',', '\x0d', '\x0a', ' ', ' ', ' ', ' ', '"', 't', 'o', + 't', 'a', 'l', '_', 'm', 'a', 'x', '_', 's', 'u', 'p', 'p', 'l', 'y', '"', ':', ' ', '1', '0', '0', '\x0d', '\x0a', ' ', ' ', '}', ',', '\x0d', '\x0a', ' ', ' ', '"', 'o', 'p', 'e', 'r', 'a', 't', + 'i', 'o', 'n', '_', 't', 'y', 'p', 'e', '"', ':', ' ', '4', ',', '\x0d', '\x0a', ' ', ' ', '"', 'o', 'p', 't', '_', 'a', 's', 's', 'e', 't', '_', 'i', 'd', '"', ':', ' ', '"', '5', '4', 'f', '3', + 'f', '7', '2', 'c', '7', '2', 'e', '5', 'b', '0', '1', '4', 'a', 'd', '2', 'b', '2', 'b', '9', '0', '0', '1', 'a', 'c', 'e', 'f', '9', '5', '4', 'f', 'e', '8', '2', 'd', 'd', '3', 'e', 'd', '5', + '6', 'a', '3', '8', 'c', 'd', '9', 'd', 'd', 'c', '5', 'd', 'b', '5', '7', '6', '7', '3', 'f', '8', 'f', '"', '\x0d', '\x0a', '}'}; + + crypto::point_t expected_point_asset_id{}; + crypto::public_key expected_asset_id{}; + crypto::point_t calculated_point_asset_id{}; + crypto::public_key calculated_asset_id{}; + const std::optional ado{deserialize(serialization_method::key_value, serialized_ado)}; + + ASSERT_TRUE(ado.has_value()); + ASSERT_EQ(ado.value().verion, 1); + /* TODO: fix the key-value serialization of the asset_descriptor_operation_v0 objects: .description.version must be equals to 0. + ASSERT_EQ(ado.value().descriptor.version, 0); */ + ASSERT_EQ(ado.value().operation_type, ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN); + success = currency::get_or_calculate_asset_id(ado.value(), &calculated_point_asset_id, &calculated_asset_id); + ASSERT_TRUE(success); + success = expected_point_asset_id.from_string("54f3f72c72e5b014ad2b2b9001acef954fe82dd3ed56a38cd9ddc5db57673f8f"); + ASSERT_TRUE(success); + ASSERT_EQ(calculated_point_asset_id, expected_point_asset_id); + expected_asset_id = expected_point_asset_id.to_public_key(); + ASSERT_EQ(calculated_asset_id, expected_asset_id); +} From 8cc826f5b35018afa5b70d11997138c18d83cd2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=D1=91pa=20Dolgorukov?= Date: Tue, 22 Oct 2024 17:48:29 +0500 Subject: [PATCH 044/106] unit_tests: edit the "p2p_client_version.test0" (#471) * Unit tests: implement the "p2p_client_version.test0" * Unit tests: edit the "p2p_client_version.test_0" * Change a default value for a commit identifier on a non empty expected value * Format the source text * Fix the test * Comment on the cases where results differ on Windows, GNU/Linux --- tests/unit_tests/p2p_client_version.cpp | 101 +++++++++++------------- 1 file changed, 46 insertions(+), 55 deletions(-) diff --git a/tests/unit_tests/p2p_client_version.cpp b/tests/unit_tests/p2p_client_version.cpp index 398cb623..c7b5f528 100644 --- a/tests/unit_tests/p2p_client_version.cpp +++ b/tests/unit_tests/p2p_client_version.cpp @@ -12,63 +12,57 @@ enum class reponse_check_parse_client_version : uint8_t parsed_unexpect }; -reponse_check_parse_client_version check_parse_client_version(const std::string& str, const std::optional& expected_major, const std::optional& expected_minor, - const std::optional& expected_revision, const std::optional& expected_build_number, - const std::optional& expected_commit_id, const std::optional& expected_dirty) +static reponse_check_parse_client_version check_parse_client_version(const std::string& str, const std::optional& expected_major, const std::optional& expected_minor, + const std::optional& expected_revision, const std::optional& expected_build_number, + const std::optional& expected_commit_id, const std::optional& expected_dirty) { enum class version_integer_component : uint8_t { major, minor, revision, build_number }; - // 3 not in {0; 1} and low-order bit not equsl to 0. - constexpr uint8_t out_of_logicals_value{3}; - std::array out_of_int32_bounds_values{}; + std::array values_on_not_written{INT32_MIN, INT32_MIN, INT32_MIN, INT32_MIN}; + int32_t major{}, minor{}, revision{}, build_number{}; + std::string commit_id{}; + bool dirty{}; + if (expected_major.has_value() && expected_major.value() == INT32_MIN) { - // (1 ** 32) > INT32_MAX - constexpr auto out_of_int32_bounds_value{static_cast(1) << 32}; - - if (expected_major.has_value() && expected_major.value() == 0) - { - ++out_of_int32_bounds_values.at(static_cast(version_integer_component::major)); - } - - if (expected_minor.has_value() && expected_minor.value() == 0) - { - ++out_of_int32_bounds_values.at(static_cast(version_integer_component::minor)); - } - - if (expected_revision.has_value() && expected_revision.value() == 0) - { - ++out_of_int32_bounds_values.at(static_cast(version_integer_component::revision)); - } - - if (expected_build_number.has_value() && expected_build_number.value() == 0) - { - ++out_of_int32_bounds_values.at(static_cast(version_integer_component::build_number)); - } + values_on_not_written.at(static_cast(version_integer_component::major)) = INT32_MAX; } - int64_t major_pass{out_of_int32_bounds_values.at(static_cast(version_integer_component::major))}, - minor_pass{out_of_int32_bounds_values.at(static_cast(version_integer_component::minor))}, - revision_pass{out_of_int32_bounds_values.at(static_cast(version_integer_component::revision))}, - build_number_pass{out_of_int32_bounds_values.at(static_cast(version_integer_component::build_number))}; - std::string commit_id{}; - uint8_t dirty_pass{out_of_logicals_value}; + if (expected_minor.has_value() && expected_minor.value() == INT32_MIN) + { + values_on_not_written.at(static_cast(version_integer_component::minor)) = INT32_MAX; + } - if (!tools::parse_client_version(str, reinterpret_cast(major_pass), reinterpret_cast(minor_pass), reinterpret_cast(revision_pass), - reinterpret_cast(build_number_pass), commit_id, reinterpret_cast(dirty_pass))) + if (expected_revision.has_value() && expected_revision.value() == INT32_MIN) + { + values_on_not_written.at(static_cast(version_integer_component::revision)) = INT32_MAX; + } + + if (expected_build_number.has_value() && expected_build_number.value() == INT32_MIN) + { + values_on_not_written.at(static_cast(version_integer_component::build_number)) = INT32_MAX; + } + + major = values_on_not_written.at(static_cast(version_integer_component::major)); + minor = values_on_not_written.at(static_cast(version_integer_component::minor)); + revision = values_on_not_written.at(static_cast(version_integer_component::revision)); + build_number = values_on_not_written.at(static_cast(version_integer_component::build_number)); + + if (expected_commit_id.has_value() && !expected_commit_id.value().empty()) + { + const auto length{expected_commit_id.value().length()}; + + assert(length + 1 > length); + commit_id = std::string(length + 1, '\0'); + } + + if (!tools::parse_client_version(str, major, minor, revision, build_number, commit_id, dirty)) { return reponse_check_parse_client_version::not_parsed; } - constexpr uint64_t mask_to_fit_value_int32{0x00000000FFFFFFFF}; - const auto major{static_cast(major_pass & mask_to_fit_value_int32)}; - const auto minor{static_cast(minor_pass & mask_to_fit_value_int32)}; - const auto revision{static_cast(revision_pass & mask_to_fit_value_int32)}; - const auto build_number{static_cast(build_number_pass & mask_to_fit_value_int32)}; - const bool dirty{dirty_pass != 2 && dirty_pass != out_of_logicals_value}; - if (expected_major.has_value()) { - if (major_pass == out_of_int32_bounds_values.at(static_cast(version_integer_component::major)) || major != expected_major.value()) + if (major == values_on_not_written.at(static_cast(version_integer_component::major)) || major != expected_major.value()) { return reponse_check_parse_client_version::parsed_unexpect; } @@ -76,7 +70,7 @@ reponse_check_parse_client_version check_parse_client_version(const std::string& else { - if (major_pass != out_of_int32_bounds_values.at(static_cast(version_integer_component::major))) + if (major != values_on_not_written.at(static_cast(version_integer_component::major))) { return reponse_check_parse_client_version::parsed_unexpect; } @@ -84,7 +78,7 @@ reponse_check_parse_client_version check_parse_client_version(const std::string& if (expected_minor.has_value()) { - if (minor_pass == out_of_int32_bounds_values.at(static_cast(version_integer_component::minor)) || minor != expected_minor.value()) + if (minor == values_on_not_written.at(static_cast(version_integer_component::minor)) || minor != expected_minor.value()) { return reponse_check_parse_client_version::parsed_unexpect; } @@ -92,7 +86,7 @@ reponse_check_parse_client_version check_parse_client_version(const std::string& else { - if (minor_pass != out_of_int32_bounds_values.at(static_cast(version_integer_component::minor))) + if (minor != values_on_not_written.at(static_cast(version_integer_component::minor))) { return reponse_check_parse_client_version::parsed_unexpect; } @@ -100,7 +94,7 @@ reponse_check_parse_client_version check_parse_client_version(const std::string& if (expected_revision.has_value()) { - if (revision_pass == out_of_int32_bounds_values.at(static_cast(version_integer_component::revision)) || revision != expected_revision.value()) + if (revision == values_on_not_written.at(static_cast(version_integer_component::revision)) || revision != expected_revision.value()) { return reponse_check_parse_client_version::parsed_unexpect; } @@ -108,7 +102,7 @@ reponse_check_parse_client_version check_parse_client_version(const std::string& else { - if (revision_pass != out_of_int32_bounds_values.at(static_cast(version_integer_component::revision))) + if (revision != values_on_not_written.at(static_cast(version_integer_component::revision))) { return reponse_check_parse_client_version::parsed_unexpect; } @@ -140,10 +134,7 @@ reponse_check_parse_client_version check_parse_client_version(const std::string& else { - if (dirty_pass != out_of_logicals_value) - { - return reponse_check_parse_client_version::parsed_unexpect; - } + return reponse_check_parse_client_version::parsed_unexpect; } return reponse_check_parse_client_version::parsed; @@ -165,8 +156,8 @@ TEST(p2p_client_version, test_0) ASSERT_EQ(check_parse_client_version("27 . 33 . -59 . 47", 27, 33, -59, 47, "", false), reponse_check_parse_client_version::parsed); ASSERT_EQ(check_parse_client_version("-2147483648.-2147483648.-2147483648.-2147483648", INT32_MIN, INT32_MIN, INT32_MIN, INT32_MIN, "", false), reponse_check_parse_client_version::parsed); ASSERT_EQ(check_parse_client_version("2147483647.2147483647.2147483647.2147483647", INT32_MAX, INT32_MAX, INT32_MAX, INT32_MAX, "", false), reponse_check_parse_client_version::parsed); - ASSERT_EQ(check_parse_client_version("2147483648.2147483648.2147483648.2147483648", INT32_MAX, INT32_MAX, INT32_MAX, INT32_MAX, "", false), reponse_check_parse_client_version::parsed); - ASSERT_EQ(check_parse_client_version("-2147483649.-2147483649.-2147483649.-2147483649", INT32_MIN, INT32_MIN, INT32_MIN, INT32_MIN, "", false), reponse_check_parse_client_version::parsed); + //ASSERT_EQ(check_parse_client_version("2147483648.2147483648.2147483648.2147483648", INT32_MAX, INT32_MAX, INT32_MAX, INT32_MAX, "", false), reponse_check_parse_client_version::parsed); + //ASSERT_EQ(check_parse_client_version("-2147483649.-2147483649.-2147483649.-2147483649", INT32_MIN, INT32_MIN, INT32_MIN, INT32_MIN, "", false), reponse_check_parse_client_version::parsed); ASSERT_EQ(check_parse_client_version("0098.+0096.0081.-0056", 98, 96, 81, -56, "", false), reponse_check_parse_client_version::parsed); ASSERT_EQ(check_parse_client_version("\0" "38.67.31.-24", 38, 67, 31, -24, "", false), reponse_check_parse_client_version::not_parsed); ASSERT_EQ(check_parse_client_version({'-', '6', '8', '.', '\0', '2', '9', '.', '5', '9', '.', '-', '7', '9'}, {}, {}, {}, {}, {}, {}), reponse_check_parse_client_version::not_parsed); From 05d8c27577a360366592e58b0ebd0892b9184d70 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Tue, 22 Oct 2024 20:06:44 +0400 Subject: [PATCH 045/106] cake wallet fixes in cmake --- CMakeLists.txt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e320fafc..4e665230 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -240,8 +240,12 @@ if(CMAKE_SYSTEM_NAME STREQUAL "iOS") #set(CMAKE_XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] "${__iphonesimulator_archs}") #set(CMAKE_XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphonesimulator*] "${__iphonesimulator_archs}") elseif(CMAKE_SYSTEM_NAME STREQUAL "Android") - set(Boost_LIBRARY_DIRS "${Boost_LIBRARY_DIRS}/${CMAKE_ANDROID_ARCH_ABI}/") - set(Boost_LIBRARIES "${Boost_LIBRARY_DIRS}libboost_system.a;${Boost_LIBRARY_DIRS}libboost_filesystem.a;${Boost_LIBRARY_DIRS}libboost_thread.a;${Boost_LIBRARY_DIRS}libboost_timer.a;${Boost_LIBRARY_DIRS}libboost_date_time.a;${Boost_LIBRARY_DIRS}libboost_chrono.a;${Boost_LIBRARY_DIRS}libboost_regex.a;${Boost_LIBRARY_DIRS}libboost_serialization.a;${Boost_LIBRARY_DIRS}libboost_atomic.a;${Boost_LIBRARY_DIRS}libboost_program_options.a") + if(CAKEWALLET) + find_package(Boost 1.71 REQUIRED COMPONENTS system filesystem thread timer date_time chrono regex serialization atomic program_options locale) + else() + set(Boost_LIBRARY_DIRS "${Boost_LIBRARY_DIRS}/${CMAKE_ANDROID_ARCH_ABI}/") + set(Boost_LIBRARIES "${Boost_LIBRARY_DIRS}libboost_system.a;${Boost_LIBRARY_DIRS}libboost_filesystem.a;${Boost_LIBRARY_DIRS}libboost_thread.a;${Boost_LIBRARY_DIRS}libboost_timer.a;${Boost_LIBRARY_DIRS}libboost_date_time.a;${Boost_LIBRARY_DIRS}libboost_chrono.a;${Boost_LIBRARY_DIRS}libboost_regex.a;${Boost_LIBRARY_DIRS}libboost_serialization.a;${Boost_LIBRARY_DIRS}libboost_atomic.a;${Boost_LIBRARY_DIRS}libboost_program_options.a") + endif() set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fPIC") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fPIC") elseif(APPLE) @@ -259,7 +263,7 @@ include_directories(SYSTEM ${Boost_INCLUDE_DIRS} ${PROJECT_SOURCE_DIR}/contrib/e if(MINGW) set(Boost_LIBRARIES "${Boost_LIBRARIES};ws2_32;mswsock") elseif(NOT MSVC) - if(NOT APPLE) + if(NOT APPLE AND NOT CAKEWALLET) set(Boost_LIBRARIES "${Boost_LIBRARIES};") if(STATIC) message("NOTICE: Including static ICU libraries") From d23fa1911e41ef01a2e2121d56f81eb6805c71f7 Mon Sep 17 00:00:00 2001 From: sowle Date: Tue, 22 Oct 2024 18:27:17 +0200 Subject: [PATCH 046/106] unit_tests: asset_id serializations test: WIP --- tests/unit_tests/multiassets_test.cpp | 264 ++++++++++++-------------- 1 file changed, 126 insertions(+), 138 deletions(-) diff --git a/tests/unit_tests/multiassets_test.cpp b/tests/unit_tests/multiassets_test.cpp index aa577d17..ad637b01 100644 --- a/tests/unit_tests/multiassets_test.cpp +++ b/tests/unit_tests/multiassets_test.cpp @@ -19,123 +19,101 @@ namespace currency { - struct asset_descriptor_base_v0; - struct asset_descriptor_operation_v0; -} -// branch = develop, HEAD = 0c90262e8a1c4e5e5d052f8db84c60a36691414d -struct currency::asset_descriptor_base_v0 -{ - uint64_t total_max_supply; - uint64_t current_supply; - uint8_t decimal_point; - std::string ticker; - std::string full_name; - std::string meta_info; - crypto::public_key owner = currency::null_pkey; // consider premultipling by 1/8 - bool hidden_supply; - uint8_t version; - - BEGIN_VERSIONED_SERIALIZE(0, version) - FIELD(total_max_supply) - FIELD(current_supply) - FIELD(decimal_point) - FIELD(ticker) - FIELD(full_name) - FIELD(meta_info) - FIELD(owner) - FIELD(hidden_supply) - END_SERIALIZE() - - BEGIN_BOOST_SERIALIZATION() - BOOST_SERIALIZE(total_max_supply) - BOOST_SERIALIZE(current_supply) - BOOST_SERIALIZE(decimal_point) - BOOST_SERIALIZE(ticker) - BOOST_SERIALIZE(full_name) - BOOST_SERIALIZE(meta_info) - BOOST_SERIALIZE(owner) - BOOST_SERIALIZE(hidden_supply) - BOOST_END_VERSION_UNDER(1) - END_BOOST_SERIALIZATION() - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(total_max_supply) DOC_DSCR("Maximum possible supply for a given asset, cannot be changed after deployment.") DOC_EXMP(1000000000000000000) DOC_END - KV_SERIALIZE(current_supply) DOC_DSCR("Currently emitted supply for the given asset (ignored for REGISTER operation).") DOC_EXMP(500000000000000000) DOC_END - KV_SERIALIZE(decimal_point) DOC_DSCR("Decimal point.") DOC_EXMP(12) DOC_END - KV_SERIALIZE(ticker) DOC_DSCR("Ticker associated with the asset.") DOC_EXMP("ZABC") DOC_END - KV_SERIALIZE(full_name) DOC_DSCR("Full name of the asset.") DOC_EXMP("Zano wrapped ABC") DOC_END - KV_SERIALIZE(meta_info) DOC_DSCR("Any other information associated with the asset in free form.") DOC_EXMP("Stable and private") DOC_END - KV_SERIALIZE_POD_AS_HEX_STRING(owner) DOC_DSCR("Owner's key, used only for EMIT and UPDATE validation, can be changed by transferring asset ownership.") DOC_EXMP("f74bb56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END - KV_SERIALIZE(hidden_supply) DOC_DSCR("This field is reserved for future use and will be documented later.") DOC_END - END_KV_SERIALIZE_MAP() - - operator currency::asset_descriptor_base() const + // + // The following structures are taken as-id from branch develop, commit 0c90262e8a1c4e5e5d052f8db84c60a36691414d, for backward compatibility serialization test. + // DO NOT EDIT THEM without a very good reason + // + struct asset_descriptor_base_v0 // EDITED: was asset_descriptor_base originally, changed to asset_descriptor_base_v0 { - currency::asset_descriptor_base asset_descriptor{}; + uint64_t total_max_supply = 0; + uint64_t current_supply = 0; + uint8_t decimal_point = 0; + std::string ticker; + std::string full_name; + std::string meta_info; + crypto::public_key owner = currency::null_pkey; // consider premultipling by 1/8 + bool hidden_supply = false; + uint8_t version = 0; - asset_descriptor.total_max_supply = total_max_supply; - asset_descriptor.current_supply = current_supply; - asset_descriptor.decimal_point = decimal_point; - asset_descriptor.ticker = ticker; - asset_descriptor.full_name = full_name; - asset_descriptor.meta_info = meta_info; - asset_descriptor.owner = owner; - asset_descriptor.hidden_supply = hidden_supply; - asset_descriptor.version = version; + BEGIN_VERSIONED_SERIALIZE(0, version) + FIELD(total_max_supply) + FIELD(current_supply) + FIELD(decimal_point) + FIELD(ticker) + FIELD(full_name) + FIELD(meta_info) + FIELD(owner) + FIELD(hidden_supply) + END_SERIALIZE() - return asset_descriptor; - } -}; -// branch = develop, HEAD = 0c90262e8a1c4e5e5d052f8db84c60a36691414d -struct currency::asset_descriptor_operation_v0 -{ - uint8_t operation_type = ASSET_DESCRIPTOR_OPERATION_UNDEFINED; - asset_descriptor_base_v0 descriptor; - crypto::public_key amount_commitment; // premultiplied by 1/8 - boost::optional opt_asset_id; // target asset_id - for update/emit - uint8_t verion = ASSET_DESCRIPTOR_OPERATION_STRUCTURE_VER; + BEGIN_BOOST_SERIALIZATION() + BOOST_SERIALIZE(total_max_supply) + BOOST_SERIALIZE(current_supply) + BOOST_SERIALIZE(decimal_point) + BOOST_SERIALIZE(ticker) + BOOST_SERIALIZE(full_name) + BOOST_SERIALIZE(meta_info) + BOOST_SERIALIZE(owner) + BOOST_SERIALIZE(hidden_supply) + END_BOOST_SERIALIZATION() - BEGIN_VERSIONED_SERIALIZE(ASSET_DESCRIPTOR_OPERATION_STRUCTURE_VER, verion) - FIELD(operation_type) - FIELD(descriptor) - FIELD(amount_commitment) - END_VERSION_UNDER(1) - FIELD(opt_asset_id) - END_SERIALIZE() + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(total_max_supply) DOC_DSCR("Maximum possible supply for given asset, can't be changed after deployment") DOC_EXMP(1000000000000000000) DOC_END + KV_SERIALIZE(current_supply) DOC_DSCR("Currently emitted supply for given asset (ignored for REGISTER operation)") DOC_EXMP(500000000000000000) DOC_END + KV_SERIALIZE(decimal_point) DOC_DSCR("Decimal point") DOC_EXMP(12) DOC_END + KV_SERIALIZE(ticker) DOC_DSCR("Ticker associated with asset") DOC_EXMP("ZUSD") DOC_END + KV_SERIALIZE(full_name) DOC_DSCR("Full name of the asset") DOC_EXMP("Zano wrapped USD") DOC_END + KV_SERIALIZE(meta_info) DOC_DSCR("Any other information assetiaded with asset in a free form") DOC_EXMP("Stable and private") DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(owner) DOC_DSCR("Owner's key, used only for EMIT and UPDATE validation, could be changed by transferring asset ownership") DOC_EXMP("f74bb56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END + KV_SERIALIZE(hidden_supply) DOC_DSCR("This one reserved for future use, will be documented later") DOC_END + END_KV_SERIALIZE_MAP() + }; - BEGIN_BOOST_SERIALIZATION() - BOOST_SERIALIZE(operation_type) - BOOST_SERIALIZE(descriptor) - BOOST_SERIALIZE(amount_commitment) - BOOST_END_VERSION_UNDER(1) - BOOST_SERIALIZE(opt_asset_id) - END_BOOST_SERIALIZATION() + #define ASSET_DESCRIPTOR_OPERATION_STRUCTURE_VER_V0 1 - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(operation_type) DOC_DSCR("Asset operation type identifier") DOC_EXMP(1) DOC_END - KV_SERIALIZE(descriptor) DOC_DSCR("Asset descriptor") DOC_EXMP_AUTO() DOC_END - KV_SERIALIZE_POD_AS_HEX_STRING(amount_commitment) DOC_DSCR("Amount commitment") DOC_EXMP("f74bb56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END - KV_SERIALIZE_POD_AS_HEX_STRING(opt_asset_id) DOC_DSCR("ID of an asset.") DOC_EXMP("cc4e69455e63f4a581257382191de6856c2156630b3fba0db4bdd73ffcfb36b6") DOC_END - END_KV_SERIALIZE_MAP() - - operator currency::asset_descriptor_operation() const + struct asset_descriptor_operation_v0 // EDITED: was asset_descriptor_operation originally, changed to asset_descriptor_operation_v0 { - currency::asset_descriptor_operation operation_descriptor{}; + uint8_t operation_type = ASSET_DESCRIPTOR_OPERATION_UNDEFINED; + asset_descriptor_base_v0 descriptor; // EDITED: was asset_descriptor_base originally, changed to asset_descriptor_base_v0 + crypto::public_key amount_commitment; // premultiplied by 1/8 + boost::optional opt_asset_id; // target asset_id - for update/emit + uint8_t verion = ASSET_DESCRIPTOR_OPERATION_STRUCTURE_VER_V0; // EDITED: was ASSET_DESCRIPTOR_OPERATION_STRUCTURE_VER originally, changed to ASSET_DESCRIPTOR_OPERATION_STRUCTURE_VER_V0 - operation_descriptor.operation_type = operation_type; - operation_descriptor.descriptor = descriptor; - operation_descriptor.amount_commitment = amount_commitment; - operation_descriptor.opt_asset_id = opt_asset_id; - operation_descriptor.verion = verion; + BEGIN_VERSIONED_SERIALIZE(ASSET_DESCRIPTOR_OPERATION_STRUCTURE_VER_V0, verion) // EDITED: was ASSET_DESCRIPTOR_OPERATION_STRUCTURE_VER originally, changed to ASSET_DESCRIPTOR_OPERATION_STRUCTURE_VER_V0 + FIELD(operation_type) + FIELD(descriptor) + FIELD(amount_commitment) + END_VERSION_UNDER(1) + FIELD(opt_asset_id) + END_SERIALIZE() - return operation_descriptor; - } -}; + BEGIN_BOOST_SERIALIZATION() + BOOST_SERIALIZE(operation_type) + BOOST_SERIALIZE(descriptor) + BOOST_SERIALIZE(amount_commitment) + BOOST_END_VERSION_UNDER(1) + BOOST_SERIALIZE(opt_asset_id) + END_BOOST_SERIALIZATION() + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(operation_type) DOC_DSCR("Asset operation type identifier") DOC_EXMP(1) DOC_END + KV_SERIALIZE(descriptor) DOC_DSCR("Asset descriptor") DOC_EXMP_AUTO() DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(amount_commitment) DOC_DSCR("Amount commitment") DOC_EXMP("f74bb56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END + KV_SERIALIZE_POD_AS_HEX_STRING(opt_asset_id) DOC_DSCR("ID of an asset.") DOC_EXMP("cc4e69455e63f4a581257382191de6856c2156630b3fba0db4bdd73ffcfb36b6") DOC_END + END_KV_SERIALIZE_MAP() + }; + + // + // End: Code insertion from commit 0c90262e8a1c4e5e5d052f8db84c60a36691414d + // + +} // namespace currency -BOOST_CLASS_VERSION(currency::asset_descriptor_base_v0, 0); BOOST_CLASS_VERSION(currency::asset_descriptor_operation_v0, 1); +BOOST_CLASS_VERSION(currency::asset_descriptor_base_v0, 0); + template static asset_descriptor get_adb(const crypto::public_key& owner = currency::null_pkey) @@ -180,8 +158,8 @@ static asset_operation_descriptor get_ado(const asset_base_descriptor& base_desc asset_operation_descriptor descriptor{}; descriptor.operation_type = operation; - descriptor.descriptor = base_descriptor; - descriptor.amount_commitment = currency::null_pkey; + descriptor.opt_descriptor = base_descriptor; + descriptor.opt_amount_commitment = currency::null_pkey; if (asset_id.has_value()) { @@ -471,12 +449,15 @@ TEST(multiassets, native_serialization_get_or_calculate_asset_id_undefined) crypto::public_key calculated_asset_id{}; const std::optional ado{deserialize(serialization_method::native, serialized_ado)}; - ASSERT_TRUE(ado.has_value()); - ASSERT_EQ(ado.value().verion, 1); - ASSERT_EQ(ado.value().descriptor.version, 0); - ASSERT_EQ(ado.value().operation_type, ASSET_DESCRIPTOR_OPERATION_UNDEFINED); + ASSERT_TRUE(ado.has_value() && ado->opt_descriptor.has_value()); + ASSERT_EQ(ado->version, 1); + ASSERT_EQ(ado->opt_descriptor->version, 0); + ASSERT_EQ(ado->operation_type, ASSET_DESCRIPTOR_OPERATION_UNDEFINED); success = currency::get_or_calculate_asset_id(ado.value(), &calculated_point_asset_id, &calculated_asset_id); ASSERT_FALSE(success); + + std::string reserialized_ado = t_serializable_object_to_blob(ado.value()); + ASSERT_EQ(serialized_ado, reserialized_ado); } TEST(multiassets, native_serialization_get_or_calculate_asset_id_register) @@ -490,22 +471,23 @@ TEST(multiassets, native_serialization_get_or_calculate_asset_id_register) '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'}; crypto::point_t expected_point_asset_id{}; - crypto::public_key expected_asset_id{}; crypto::point_t calculated_point_asset_id{}; crypto::public_key calculated_asset_id{}; const std::optional ado{deserialize(serialization_method::native, serialized_ado)}; - ASSERT_TRUE(ado.has_value()); - ASSERT_EQ(ado.value().verion, 1); - ASSERT_EQ(ado.value().descriptor.version, 0); - ASSERT_EQ(ado.value().operation_type, ASSET_DESCRIPTOR_OPERATION_REGISTER); + ASSERT_TRUE(ado.has_value() && ado->opt_descriptor.has_value()); + ASSERT_EQ(ado->version, 1); + ASSERT_EQ(ado->opt_descriptor->version, 0); + ASSERT_EQ(ado->operation_type, ASSET_DESCRIPTOR_OPERATION_REGISTER); success = currency::get_or_calculate_asset_id(ado.value(), &calculated_point_asset_id, &calculated_asset_id); ASSERT_TRUE(success); success = expected_point_asset_id.from_string("6f46324faae448b9e3b96dac94da17be6ab7eaaba398de86d8743042c98bace0"); ASSERT_TRUE(success); ASSERT_EQ(calculated_point_asset_id, expected_point_asset_id); - expected_asset_id = expected_point_asset_id.to_public_key(); - ASSERT_EQ(calculated_asset_id, expected_asset_id); + ASSERT_EQ(calculated_asset_id, expected_point_asset_id.to_public_key()); + + std::string reserialized_ado = t_serializable_object_to_blob(ado.value()); + ASSERT_EQ(serialized_ado, reserialized_ado); } TEST(multiassets, native_serialization_get_or_calculate_asset_id_emit) @@ -519,22 +501,23 @@ TEST(multiassets, native_serialization_get_or_calculate_asset_id_emit) '\xfc', '\x1c', '^', '\xdc', 'r', '\x80', 'G', 'C', '}', 'f', '~'}; crypto::point_t expected_point_asset_id{}; - crypto::public_key expected_asset_id{}; crypto::point_t calculated_point_asset_id{}; crypto::public_key calculated_asset_id{}; const std::optional ado{deserialize(serialization_method::native, serialized_ado)}; - ASSERT_TRUE(ado.has_value()); - ASSERT_EQ(ado.value().verion, 1); - ASSERT_EQ(ado.value().descriptor.version, 0); - ASSERT_EQ(ado.value().operation_type, ASSET_DESCRIPTOR_OPERATION_EMIT); + ASSERT_TRUE(ado.has_value() && ado->opt_descriptor.has_value()); + ASSERT_EQ(ado->version, 1); + ASSERT_EQ(ado->opt_descriptor->version, 0); + ASSERT_EQ(ado->operation_type, ASSET_DESCRIPTOR_OPERATION_EMIT); success = currency::get_or_calculate_asset_id(ado.value(), &calculated_point_asset_id, &calculated_asset_id); ASSERT_TRUE(success); success = expected_point_asset_id.from_string("49a3d6652aaa0b3b77292c534e91ff80de9120aeb6fc1c5edc728047437d667e"); ASSERT_TRUE(success); ASSERT_EQ(calculated_point_asset_id, expected_point_asset_id); - expected_asset_id = expected_point_asset_id.to_public_key(); - ASSERT_EQ(calculated_asset_id, expected_asset_id); + ASSERT_EQ(calculated_asset_id, expected_point_asset_id.to_public_key()); + + std::string reserialized_ado = t_serializable_object_to_blob(ado.value()); + ASSERT_EQ(serialized_ado, reserialized_ado); } TEST(multiassets, native_serialization_get_or_calculate_asset_id_update) @@ -548,22 +531,23 @@ TEST(multiassets, native_serialization_get_or_calculate_asset_id_update) '^', '\x1b', '\xed', 'N', '\xa9', '\xb5', '\xf5', '\x92', '\xfa', '\xdf', '\x90', '\xed', 'k'}; crypto::point_t expected_point_asset_id{}; - crypto::public_key expected_asset_id{}; crypto::point_t calculated_point_asset_id{}; crypto::public_key calculated_asset_id{}; const std::optional ado{deserialize(serialization_method::native, serialized_ado)}; - ASSERT_TRUE(ado.has_value()); - ASSERT_EQ(ado.value().verion, 1); - ASSERT_EQ(ado.value().descriptor.version, 0); - ASSERT_EQ(ado.value().operation_type, ASSET_DESCRIPTOR_OPERATION_UPDATE); + ASSERT_TRUE(ado.has_value() && ado->opt_descriptor.has_value()); + ASSERT_EQ(ado->version, 1); + ASSERT_EQ(ado->opt_descriptor->version, 0); + ASSERT_EQ(ado->operation_type, ASSET_DESCRIPTOR_OPERATION_UPDATE); success = currency::get_or_calculate_asset_id(ado.value(), &calculated_point_asset_id, &calculated_asset_id); ASSERT_TRUE(success); success = expected_point_asset_id.from_string("c371f60dd8333298c6aa746b71e1e20527b1ff5e1bed4ea9b5f592fadf90ed6b"); ASSERT_TRUE(success); ASSERT_EQ(calculated_point_asset_id, expected_point_asset_id); - expected_asset_id = expected_point_asset_id.to_public_key(); - ASSERT_EQ(calculated_asset_id, expected_asset_id); + ASSERT_EQ(calculated_asset_id, expected_point_asset_id.to_public_key()); + + std::string reserialized_ado = t_serializable_object_to_blob(ado.value()); + ASSERT_EQ(serialized_ado, reserialized_ado); } TEST(multiassets, native_serialization_get_or_calculate_asset_id_public_burn) @@ -577,24 +561,27 @@ TEST(multiassets, native_serialization_get_or_calculate_asset_id_public_burn) '\xd3', '\xed', 'V', '\xa3', '\x8c', '\xd9', '\xdd', '\xc5', '\xdb', 'W', 'g', '?', '\x8f'}; crypto::point_t expected_point_asset_id{}; - crypto::public_key expected_asset_id{}; crypto::point_t calculated_point_asset_id{}; crypto::public_key calculated_asset_id{}; const std::optional ado{deserialize(serialization_method::native, serialized_ado)}; - ASSERT_TRUE(ado.has_value()); - ASSERT_EQ(ado.value().verion, 1); - ASSERT_EQ(ado.value().descriptor.version, 0); - ASSERT_EQ(ado.value().operation_type, ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN); + ASSERT_TRUE(ado.has_value() && ado->opt_descriptor.has_value()); + ASSERT_EQ(ado->version, 1); + ASSERT_EQ(ado->opt_descriptor->version, 0); + ASSERT_EQ(ado->operation_type, ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN); success = currency::get_or_calculate_asset_id(ado.value(), &calculated_point_asset_id, &calculated_asset_id); ASSERT_TRUE(success); success = expected_point_asset_id.from_string("54f3f72c72e5b014ad2b2b9001acef954fe82dd3ed56a38cd9ddc5db57673f8f"); ASSERT_TRUE(success); ASSERT_EQ(calculated_point_asset_id, expected_point_asset_id); - expected_asset_id = expected_point_asset_id.to_public_key(); - ASSERT_EQ(calculated_asset_id, expected_asset_id); + ASSERT_EQ(calculated_asset_id, expected_point_asset_id.to_public_key()); + + std::string reserialized_ado = t_serializable_object_to_blob(ado.value()); + ASSERT_EQ(serialized_ado, reserialized_ado); } + +#if 0 TEST(multiassets, boost_serialization_get_or_calculate_asset_id_undefined) { bool success{}; @@ -944,3 +931,4 @@ TEST(multiassets, key_value_serialization_get_or_calculate_asset_id_public_burn) expected_asset_id = expected_point_asset_id.to_public_key(); ASSERT_EQ(calculated_asset_id, expected_asset_id); } +#endif From 61ac4dd356198ef49f1caf10e639eb96e666d763 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Tue, 22 Oct 2024 20:45:17 +0400 Subject: [PATCH 047/106] some clarity on opt_amount --- src/currency_core/currency_basic.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/currency_core/currency_basic.h b/src/currency_core/currency_basic.h index d0630089..6cfc085d 100644 --- a/src/currency_core/currency_basic.h +++ b/src/currency_core/currency_basic.h @@ -815,7 +815,7 @@ namespace currency boost::optional opt_amount_commitment; // + + + - (premultiplied by 1/8) boost::optional opt_asset_id; // - + + + boost::optional opt_descriptor; // + - - + - boost::optional opt_amount; // ? ? ? - (only for non-hidden supply) + boost::optional opt_amount; // ? + + - (only for non-hidden supply) boost::optional opt_asset_id_salt; // ? - - - (optional) std::vector etc; // (reserved for future use) From 7d93211b9fb218890f189c542c2790ccc7dbb038 Mon Sep 17 00:00:00 2001 From: sowle Date: Wed, 23 Oct 2024 02:00:10 +0200 Subject: [PATCH 048/106] testnet relaunch (currency formation version: 98 -> 99) --- src/currency_core/currency_config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/currency_core/currency_config.h b/src/currency_core/currency_config.h index f6a31839..03ce9616 100644 --- a/src/currency_core/currency_config.h +++ b/src/currency_core/currency_config.h @@ -10,7 +10,7 @@ #ifndef TESTNET #define CURRENCY_FORMATION_VERSION 84 #else -#define CURRENCY_FORMATION_VERSION 98 +#define CURRENCY_FORMATION_VERSION 99 #endif #define CURRENCY_GENESIS_NONCE (CURRENCY_FORMATION_VERSION + 101011010121) //bender's nightmare From fd1ddf5ffc197df2c437647aedbfc96e0397056a Mon Sep 17 00:00:00 2001 From: sowle Date: Wed, 23 Oct 2024 02:01:20 +0200 Subject: [PATCH 049/106] === version bump: 2.0.2.352 -> 2.1.0.353 === --- src/version.h.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/version.h.in b/src/version.h.in index 703cc37b..ea0fbd13 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -4,10 +4,10 @@ #define BUILD_COMMIT_ID "@VERSION@" #define PROJECT_MAJOR_VERSION "2" -#define PROJECT_MINOR_VERSION "0" -#define PROJECT_REVISION "2" +#define PROJECT_MINOR_VERSION "1" +#define PROJECT_REVISION "0" #define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION -#define PROJECT_VERSION_BUILD_NO 351 +#define PROJECT_VERSION_BUILD_NO 353 #define PROJECT_VERSION_BUILD_NO_STR STRINGIFY_EXPAND(PROJECT_VERSION_BUILD_NO) #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO_STR "[" BUILD_COMMIT_ID "]" From 109b18742672a79cf2b310d261ca33b161e46e1f Mon Sep 17 00:00:00 2001 From: sowle Date: Wed, 23 Oct 2024 03:33:23 +0200 Subject: [PATCH 050/106] daemon_commands_handler fixed --- src/daemon/daemon_commands_handler.h | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/daemon/daemon_commands_handler.h b/src/daemon/daemon_commands_handler.h index c960a39c..3de2d295 100644 --- a/src/daemon/daemon_commands_handler.h +++ b/src/daemon/daemon_commands_handler.h @@ -806,17 +806,21 @@ private: size_t idx = 0; for(auto& aop: asset_history) { - ss << "[" << std::setw(2) << idx << "] operation: " << currency::get_asset_operation_type_string(aop.operation_type) << " (" << (int)aop.operation_type << ")" << ENDL << - " ticker: \"" << aop.descriptor.ticker << "\"" << ENDL << - " full name: \"" << aop.descriptor.full_name << "\"" << ENDL << - " meta info: \"" << aop.descriptor.meta_info << "\"" << ENDL << - " current supply: " << currency::print_money_brief(aop.descriptor.current_supply, aop.descriptor.decimal_point) << ENDL << - " max supply: " << currency::print_money_brief(aop.descriptor.total_max_supply, aop.descriptor.decimal_point) << ENDL << - " decimal point: " << (int)aop.descriptor.decimal_point << ENDL << - " owner * 1/8: " << aop.descriptor.owner << ENDL << - " amount cmt * 1/8: " << aop.amount_commitment << ENDL << - " hidden supply: " << (aop.descriptor.hidden_supply ? "yes" : "no") << ENDL << - ""; + if (aop.opt_descriptor.has_value()) + { + const currency::asset_descriptor_base& adb = aop.opt_descriptor.value(); + ss << "[" << std::setw(2) << idx << "] operation: " << currency::get_asset_operation_type_string(aop.operation_type) << " (" << (int)aop.operation_type << ")" << ENDL << + " ticker: \"" << adb.ticker << "\"" << ENDL << + " full name: \"" << adb.full_name << "\"" << ENDL << + " meta info: \"" << adb.meta_info << "\"" << ENDL << + " current supply: " << currency::print_money_brief(adb.current_supply, adb.decimal_point) << ENDL << + " max supply: " << currency::print_money_brief(adb.total_max_supply, adb.decimal_point) << ENDL << + " decimal point: " << (int)adb.decimal_point << ENDL << + " owner * 1/8: " << adb.owner << ENDL << + " amount cmt * 1/8: " << (aop.opt_amount_commitment.has_value() ? aop.opt_amount_commitment.value() : currency::null_pkey) << ENDL << + " hidden supply: " << (adb.hidden_supply ? "yes" : "no") << ENDL << + ""; + } ++idx; } From 6d7923b7e375ab7c9901fee6781fb677e6132813 Mon Sep 17 00:00:00 2001 From: sowle Date: Wed, 23 Oct 2024 03:40:43 +0200 Subject: [PATCH 051/106] hf5 min build number set to 354 --- src/currency_core/currency_config.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/currency_core/currency_config.h b/src/currency_core/currency_config.h index 03ce9616..a1783c1e 100644 --- a/src/currency_core/currency_config.h +++ b/src/currency_core/currency_config.h @@ -271,16 +271,16 @@ #define ZANO_HARDFORK_04_AFTER_HEIGHT 2555000 // 2024-03-21 11:49:55 #define ZANO_HARDFORK_04_TIMESTAMP_ACTUAL 1711021795ull // block 2555000, 2024-03-21 11:49:55 UTC #define ZANO_HARDFORK_05_AFTER_HEIGHT 999999999999999999 -#define ZANO_HARDFORK_05_MIN_BUILD_VER 343 +#define ZANO_HARDFORK_05_MIN_BUILD_VER 354 #else // Testnet #define ZANO_HARDFORK_01_AFTER_HEIGHT 0 #define ZANO_HARDFORK_02_AFTER_HEIGHT 0 #define ZANO_HARDFORK_03_AFTER_HEIGHT 0 #define ZANO_HARDFORK_04_AFTER_HEIGHT 200 -#define ZANO_HARDFORK_04_TIMESTAMP_ACTUAL 1712785801ull // block 200, 2024-04-10 21:50:01 UTC +#define ZANO_HARDFORK_04_TIMESTAMP_ACTUAL 1712800000ull // block 200, 2024-00-00 00:00:00 UTC #define ZANO_HARDFORK_05_AFTER_HEIGHT 241750 -#define ZANO_HARDFORK_05_MIN_BUILD_VER 343 +#define ZANO_HARDFORK_05_MIN_BUILD_VER 354 #endif From 312444aa48a60711b78e4e050a6a34be3b5cb579 Mon Sep 17 00:00:00 2001 From: zano build machine Date: Wed, 23 Oct 2024 04:41:18 +0300 Subject: [PATCH 052/106] === build number: 353 -> 354 === --- src/version.h.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h.in b/src/version.h.in index ea0fbd13..b82b8f10 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -8,6 +8,6 @@ #define PROJECT_REVISION "0" #define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION -#define PROJECT_VERSION_BUILD_NO 353 +#define PROJECT_VERSION_BUILD_NO 354 #define PROJECT_VERSION_BUILD_NO_STR STRINGIFY_EXPAND(PROJECT_VERSION_BUILD_NO) #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO_STR "[" BUILD_COMMIT_ID "]" From 88fa0bca64f4eb6259392ff38ece26bf03844fe5 Mon Sep 17 00:00:00 2001 From: sowle Date: Wed, 23 Oct 2024 11:04:12 +0200 Subject: [PATCH 053/106] readme: Boost version updated --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 31f4934d..45560781 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Be sure to clone the repository properly:\ | [MSVC](https://visualstudio.microsoft.com/downloads/) (Windows) | 2017 (15.9.30) | 2019 (16.11.34) | 2022 (17.11.5) | | [XCode](https://developer.apple.com/downloads/) (macOS) | 12.3 | 14.3 | 15.2 | | [CMake](https://cmake.org/download/) | 3.15.5 | 3.26.3 | 3.29.0 | -| [Boost](https://www.boost.org/users/download/) | 1.75 | 1.75 | 1.84 | +| [Boost](https://www.boost.org/users/download/) | 1.75 | 1.84 | 1.84 | | [OpenSSL](https://www.openssl.org/source/) [(win)](https://slproweb.com/products/Win32OpenSSL.html) | 1.1.1n | 1.1.1w | 1.1.1w | | [Qt](https://download.qt.io/archive/qt/) (*only for GUI*) | 5.8.0 | 5.11.2 | 5.15.2 | @@ -52,10 +52,9 @@ Recommended OS versions: Ubuntu 20.04, 22.04 LTS. 3. Download and build Boost\ (Assuming you have cloned Zano into the 'zano' folder. If you used a different location for Zano, **edit line 4** accordingly.) - curl -OL https://boostorg.jfrog.io/artifactory/main/release/1.70.0/source/boost_1_70_0.tar.bz2 - echo "430ae8354789de4fd19ee52f3b1f739e1fba576f0aded0897c3c2bc00fb38778 boost_1_70_0.tar.bz2" | shasum -c && tar -xjf boost_1_70_0.tar.bz2 - rm boost_1_70_0.tar.bz2 && cd boost_1_70_0 - patch -p0 < ../zano/utils/boost_1.70_gcc_8.patch || cd .. + curl -OL https://boostorg.jfrog.io/artifactory/main/release/1.84.0/source/boost_1_84_0.tar.bz2 + echo "cc4b893acf645c9d4b698e9a0f08ca8846aa5d6c68275c14c3e7949c24109454 boost_1_84_0.tar.bz2" | shasum -c && tar -xjf boost_1_84_0.tar.bz2 + rm boost_1_84_0.tar.bz2 && cd boost_1_84_0 ./bootstrap.sh --with-libraries=system,filesystem,thread,date_time,chrono,regex,serialization,atomic,program_options,locale,timer,log ./b2 && cd .. Make sure that you see "The Boost C++ Libraries were successfully built!" message at the end. From 635cea55e052b42d5e5b67842d0f2862c0c21c3f Mon Sep 17 00:00:00 2001 From: sowle Date: Wed, 23 Oct 2024 11:26:34 +0200 Subject: [PATCH 054/106] testnet: P2P_DEFAULT_PORT calculation pattern changed --- src/currency_core/currency_config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/currency_core/currency_config.h b/src/currency_core/currency_config.h index a1783c1e..b8ac5cd2 100644 --- a/src/currency_core/currency_config.h +++ b/src/currency_core/currency_config.h @@ -118,7 +118,7 @@ #define P2P_MAINTAINERS_PUB_KEY "8f138bb73f6d663a3746a542770781a09579a7b84cb4125249e95530824ee607" #define DIFFICULTY_POS_STARTER 1 #else -#define P2P_DEFAULT_PORT (11112 + CURRENCY_FORMATION_VERSION) +#define P2P_DEFAULT_PORT (11211 + CURRENCY_FORMATION_VERSION) #define RPC_DEFAULT_PORT 12111 #define STRATUM_DEFAULT_PORT 11888 #define STRARUM_DEFAULT_PORT 51113 From d3fc6b955f1794541d831fae6854bb0f09ba7174 Mon Sep 17 00:00:00 2001 From: zano build machine Date: Wed, 23 Oct 2024 12:27:24 +0300 Subject: [PATCH 055/106] === build number: 354 -> 355 === --- src/version.h.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h.in b/src/version.h.in index b82b8f10..d50592f7 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -8,6 +8,6 @@ #define PROJECT_REVISION "0" #define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION -#define PROJECT_VERSION_BUILD_NO 354 +#define PROJECT_VERSION_BUILD_NO 355 #define PROJECT_VERSION_BUILD_NO_STR STRINGIFY_EXPAND(PROJECT_VERSION_BUILD_NO) #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO_STR "[" BUILD_COMMIT_ID "]" From bd473960e452ea2d48e854dba888af1eda8452ce Mon Sep 17 00:00:00 2001 From: sowle Date: Wed, 23 Oct 2024 12:22:32 +0200 Subject: [PATCH 056/106] testnet: hardforks heights reset --- src/currency_core/currency_config.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/currency_core/currency_config.h b/src/currency_core/currency_config.h index b8ac5cd2..1d6edf20 100644 --- a/src/currency_core/currency_config.h +++ b/src/currency_core/currency_config.h @@ -277,10 +277,10 @@ #define ZANO_HARDFORK_01_AFTER_HEIGHT 0 #define ZANO_HARDFORK_02_AFTER_HEIGHT 0 #define ZANO_HARDFORK_03_AFTER_HEIGHT 0 -#define ZANO_HARDFORK_04_AFTER_HEIGHT 200 -#define ZANO_HARDFORK_04_TIMESTAMP_ACTUAL 1712800000ull // block 200, 2024-00-00 00:00:00 UTC -#define ZANO_HARDFORK_05_AFTER_HEIGHT 241750 -#define ZANO_HARDFORK_05_MIN_BUILD_VER 354 +#define ZANO_HARDFORK_04_AFTER_HEIGHT 100 +#define ZANO_HARDFORK_04_TIMESTAMP_ACTUAL 1712800000ull // block 100, 2024-00-00 00:00:00 UTC +#define ZANO_HARDFORK_05_AFTER_HEIGHT 200 +#define ZANO_HARDFORK_05_MIN_BUILD_VER 356 #endif From 5e4fae1246c6f46cafdf560796ff79df76d7cd98 Mon Sep 17 00:00:00 2001 From: zano build machine Date: Wed, 23 Oct 2024 13:22:54 +0300 Subject: [PATCH 057/106] === build number: 355 -> 356 === --- src/version.h.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h.in b/src/version.h.in index d50592f7..a74c9e1c 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -8,6 +8,6 @@ #define PROJECT_REVISION "0" #define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION -#define PROJECT_VERSION_BUILD_NO 355 +#define PROJECT_VERSION_BUILD_NO 356 #define PROJECT_VERSION_BUILD_NO_STR STRINGIFY_EXPAND(PROJECT_VERSION_BUILD_NO) #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO_STR "[" BUILD_COMMIT_ID "]" From fd810d5e292c402fa9ff43d7f2f84a574ef7d566 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 23 Oct 2024 14:47:02 +0400 Subject: [PATCH 058/106] workaround fix for old android sdk with old boost(with missing pfr) --- src/common/boost_serialization_maps.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/common/boost_serialization_maps.h b/src/common/boost_serialization_maps.h index 2bd9fda3..d27bf52a 100644 --- a/src/common/boost_serialization_maps.h +++ b/src/common/boost_serialization_maps.h @@ -4,8 +4,9 @@ #pragma once #include + #ifndef MOBILE_WALLET #include - +#endif #define BEGIN_BOOST_SERIALIZATION() template void serialize(t_archive &_arch, const unsigned int ver) { template struct TAssertEquality { @@ -37,8 +38,11 @@ template struct TAssertEquality { fields to the structure but forgets to update the serialization map, the compilation will fail. Any update to "num_fields" must be accompanied by a thorough review of the serialization map to ensure no fields are omitted. **********************************************************************************************************************************/ -#define END_BOOST_SERIALIZATION_TOTAL_FIELDS(num_fields) static_assert(num_fields == boost::pfr::tuple_size::type>::value, "Unexpected number of fields!"); } - +#ifndef MOBILE_WALLET + #define END_BOOST_SERIALIZATION_TOTAL_FIELDS(num_fields) static_assert(num_fields == boost::pfr::tuple_size::type>::value, "Unexpected number of fields!"); } +#else + #define END_BOOST_SERIALIZATION_TOTAL_FIELDS(num_fields) END_BOOST_SERIALIZATION() +#endif #define BOOST_SERIALIZATION_CURRENT_ARCHIVE_VER(current_version) static const unsigned int current_boost_version_serialization_version = current_version; From 5387a7711ccc7189e42ec316ee3a3a453b2f117f Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 23 Oct 2024 15:01:43 +0400 Subject: [PATCH 059/106] workaround fix for old android sdk with old boost(with missing pfr) v2 --- CMakeLists.txt | 1 + src/common/boost_serialization_maps.h | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e665230..b07daa1d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,6 +89,7 @@ endif() if(CAKEWALLET) message("NOTICE: Building libraries for CAKEWALLET") add_definitions(-DCAKEWALLET) + add_definitions(-DDISABLE_PFR_SERIALIZATION_SELFCHECK) endif() set(OPENSSL_USE_STATIC_LIBS TRUE) # link statically diff --git a/src/common/boost_serialization_maps.h b/src/common/boost_serialization_maps.h index d27bf52a..c5739279 100644 --- a/src/common/boost_serialization_maps.h +++ b/src/common/boost_serialization_maps.h @@ -4,8 +4,8 @@ #pragma once #include - #ifndef MOBILE_WALLET -#include +#ifdef DISABLE_PFR_SERIALIZATION_SELFCHECK + #include #endif #define BEGIN_BOOST_SERIALIZATION() template void serialize(t_archive &_arch, const unsigned int ver) { @@ -38,7 +38,7 @@ template struct TAssertEquality { fields to the structure but forgets to update the serialization map, the compilation will fail. Any update to "num_fields" must be accompanied by a thorough review of the serialization map to ensure no fields are omitted. **********************************************************************************************************************************/ -#ifndef MOBILE_WALLET +#ifdef DISABLE_PFR_SERIALIZATION_SELFCHECK #define END_BOOST_SERIALIZATION_TOTAL_FIELDS(num_fields) static_assert(num_fields == boost::pfr::tuple_size::type>::value, "Unexpected number of fields!"); } #else #define END_BOOST_SERIALIZATION_TOTAL_FIELDS(num_fields) END_BOOST_SERIALIZATION() From a8a188b7541b4977d2be9f61f9d87f6e17c5367b Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 23 Oct 2024 15:05:46 +0400 Subject: [PATCH 060/106] workaround fix for old android sdk with old boost(with missing pfr) v3 (shame on me) --- src/common/boost_serialization_maps.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/boost_serialization_maps.h b/src/common/boost_serialization_maps.h index c5739279..f6704b95 100644 --- a/src/common/boost_serialization_maps.h +++ b/src/common/boost_serialization_maps.h @@ -4,7 +4,7 @@ #pragma once #include -#ifdef DISABLE_PFR_SERIALIZATION_SELFCHECK +#ifndef DISABLE_PFR_SERIALIZATION_SELFCHECK #include #endif #define BEGIN_BOOST_SERIALIZATION() template void serialize(t_archive &_arch, const unsigned int ver) { @@ -38,7 +38,7 @@ template struct TAssertEquality { fields to the structure but forgets to update the serialization map, the compilation will fail. Any update to "num_fields" must be accompanied by a thorough review of the serialization map to ensure no fields are omitted. **********************************************************************************************************************************/ -#ifdef DISABLE_PFR_SERIALIZATION_SELFCHECK +#ifndef DISABLE_PFR_SERIALIZATION_SELFCHECK #define END_BOOST_SERIALIZATION_TOTAL_FIELDS(num_fields) static_assert(num_fields == boost::pfr::tuple_size::type>::value, "Unexpected number of fields!"); } #else #define END_BOOST_SERIALIZATION_TOTAL_FIELDS(num_fields) END_BOOST_SERIALIZATION() From f08d8797a0bbc89500a2bf8ec279170072420f66 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 23 Oct 2024 20:05:03 +0400 Subject: [PATCH 061/106] fixed http-forwarding issue --- contrib/epee/include/net/http_server_handlers_map2.h | 9 ++++++++- src/wallet/wallet_rpc_server.cpp | 12 ++++++------ src/wallet/wallet_rpc_server.h | 10 +++++++++- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/contrib/epee/include/net/http_server_handlers_map2.h b/contrib/epee/include/net/http_server_handlers_map2.h index b4466ad4..9e1ad044 100644 --- a/contrib/epee/include/net/http_server_handlers_map2.h +++ b/contrib/epee/include/net/http_server_handlers_map2.h @@ -282,8 +282,15 @@ namespace epee { namespace http { struct i_chain_handler { + virtual bool handle_http_request(const epee::net_utils::http::http_request_info& query_info, epee::net_utils::http::http_response_info& response_info, + epee::net_utils::connection_context_base& conn_context, bool& call_found, documentation& docs = epee::net_utils::http::i_chain_handler::m_empty_documentation) + { + return this->handle_http_request_map(query_info, response_info, conn_context, call_found, docs); + } + virtual bool handle_http_request_map(const epee::net_utils::http::http_request_info& query_info, epee::net_utils::http::http_response_info& response_info, epee::net_utils::connection_context_base& m_conn_context, bool& call_found, documentation& docs = epee::net_utils::http::i_chain_handler::m_empty_documentation) = 0; + static inline documentation m_empty_documentation; }; @@ -365,7 +372,7 @@ namespace epee { LOG_PRINT( "[HTTP/BIN][" << epee::string_tools::get_ip_string_from_int32(m_conn_context.m_remote_ip ) << "][" << query_info.m_URI << "] processed with " << ticks1-ticks << "/"<< ticks2-ticks1 << "/" << ticks3-ticks2 << "ms", LOG_LEVEL_2); \ } -#define CHAIN_TO_PHANDLER(pi_chain_handler) else if (pi_chain_handler && pi_chain_handler->handle_http_request_map(query_info, response_info, m_conn_context, call_found, docs) && call_found) { return true;} +#define CHAIN_TO_PHANDLER(pi_chain_handler) else if (pi_chain_handler && pi_chain_handler->handle_http_request(query_info, response_info, m_conn_context, call_found, docs) && call_found) { return true;} #define CHAIN_URI_MAP2(callback) else {callback(query_info, response_info, m_conn_context);call_found = true;} diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index afd527b3..b601d478 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -212,7 +212,7 @@ namespace tools return epee::http_server_impl_base::init(m_port, m_bind_ip); } //------------------------------------------------------------------------------------------------------------------------------ - bool wallet_rpc_server::auth_http_request(const epee::net_utils::http::http_request_info& query_info, epee::net_utils::http::http_response_info& response, connection_context& m_conn_context) + bool wallet_rpc_server::auth_http_request(const epee::net_utils::http::http_request_info& query_info, epee::net_utils::http::http_response_info& response, connection_context& conn_context) { auto it = std::find_if(query_info.m_header_info.m_etc_fields.begin(), query_info.m_header_info.m_etc_fields.end(), [](const auto& element) @@ -266,12 +266,13 @@ namespace tools } //------------------------------------------------------------------------------------------------------------------------------ - bool wallet_rpc_server::handle_http_request(const epee::net_utils::http::http_request_info& query_info, epee::net_utils::http::http_response_info& response, connection_context& m_conn_context) + bool wallet_rpc_server::handle_http_request(const epee::net_utils::http::http_request_info& query_info, epee::net_utils::http::http_response_info& response, epee::net_utils::connection_context_base& conn_context, bool& call_found, documentation& docs) { - if (m_jwt_secret.size() && m_conn_context.m_connection_id != RPC_INTERNAL_UI_CONTEXT) + if (m_jwt_secret.size() && conn_context.m_connection_id != RPC_INTERNAL_UI_CONTEXT) { - if (!auth_http_request(query_info, response, m_conn_context)) + if (!auth_http_request(query_info, response, conn_context)) { + call_found = true; response.m_response_code = 401; response.m_response_comment = "Unauthorized"; return true; @@ -281,14 +282,13 @@ namespace tools response.m_response_code = 200; response.m_response_comment = "Ok"; std::string reference_stub; - bool call_found = false; if (m_deaf) { response.m_response_code = 500; response.m_response_comment = "Internal Server Error"; return true; } - if (!handle_http_request_map(query_info, response, m_conn_context, call_found) && response.m_response_code == 200) + if (!handle_http_request_map(query_info, response, conn_context, call_found, docs) && response.m_response_code == 200) { response.m_response_code = 500; response.m_response_comment = "Internal Server Error"; diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 70e3458b..2f50e99c 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -88,7 +88,15 @@ namespace tools static void init_options(boost::program_options::options_description& desc); bool init(const boost::program_options::variables_map& vm); bool run(bool do_mint, bool offline_mode, const currency::account_public_address& miner_address); - bool handle_http_request(const epee::net_utils::http::http_request_info& query_info, epee::net_utils::http::http_response_info& response, connection_context& m_conn_context); + + virtual bool handle_http_request(const epee::net_utils::http::http_request_info& query_info, epee::net_utils::http::http_response_info& response_info, + connection_context& conn_context) { + bool call_found = false; + return this->handle_http_request(query_info, response_info, conn_context, call_found, epee::net_utils::http::i_chain_handler::m_empty_documentation); + } + + virtual bool handle_http_request(const epee::net_utils::http::http_request_info& query_info, epee::net_utils::http::http_response_info& response_info, + epee::net_utils::connection_context_base& conn_context, bool& call_found, documentation& docs = epee::net_utils::http::i_chain_handler::m_empty_documentation); void set_jwt_secret(const std::string& jwt); const std::string& get_jwt_secret(); From c90344ea47c58f705934abb838258740dd083e33 Mon Sep 17 00:00:00 2001 From: sowle Date: Wed, 23 Oct 2024 21:52:33 +0200 Subject: [PATCH 062/106] an attempt to fix incorrect PoW block template creation in case when there are lots of PoS blocks --- src/currency_core/blockchain_storage.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/currency_core/blockchain_storage.cpp b/src/currency_core/blockchain_storage.cpp index e41c9ada..4911c3c2 100644 --- a/src/currency_core/blockchain_storage.cpp +++ b/src/currency_core/blockchain_storage.cpp @@ -1553,8 +1553,21 @@ bool blockchain_storage::create_block_template(const create_block_template_param } diffic = get_next_diff_conditional(pos); + CHECK_AND_ASSERT_MES(diffic, false, "get_next_diff_conditional failed"); + + // check PoW block timestamp against the current blockchain timestamp median -- if it's not okay, don't create a new block + // TODO (performance) both get_next_diff_conditional and get_last_n_blocks_timestamps obtains last N blocks, consider data reusing -- sowle + if (!pos) + { + std::vector timestamps = get_last_n_blocks_timestamps(BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW); + uint64_t median_ts = epee::misc_utils::median(timestamps); + if(b.timestamp < median_ts) + { + LOG_PRINT_YELLOW("Block template construction failed because current core timestamp, " << b.timestamp << ", is less than median of last " << BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW << " blocks, " << median_ts, LOG_LEVEL_2); + return false; + } + } - CHECK_AND_ASSERT_MES(diffic, false, "difficulty owverhead."); From 2b15aad3203dbfcdd4b0ec13c0105383e7622ebb Mon Sep 17 00:00:00 2001 From: sowle Date: Wed, 23 Oct 2024 21:59:05 +0200 Subject: [PATCH 063/106] minor improvement for blockchain_storage::create_block_template() --- src/currency_core/blockchain_storage.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/currency_core/blockchain_storage.cpp b/src/currency_core/blockchain_storage.cpp index 4911c3c2..5c6f4e65 100644 --- a/src/currency_core/blockchain_storage.cpp +++ b/src/currency_core/blockchain_storage.cpp @@ -1559,8 +1559,7 @@ bool blockchain_storage::create_block_template(const create_block_template_param // TODO (performance) both get_next_diff_conditional and get_last_n_blocks_timestamps obtains last N blocks, consider data reusing -- sowle if (!pos) { - std::vector timestamps = get_last_n_blocks_timestamps(BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW); - uint64_t median_ts = epee::misc_utils::median(timestamps); + uint64_t median_ts = get_last_n_blocks_timestamps_median(BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW); if(b.timestamp < median_ts) { LOG_PRINT_YELLOW("Block template construction failed because current core timestamp, " << b.timestamp << ", is less than median of last " << BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW << " blocks, " << median_ts, LOG_LEVEL_2); From 6087a1bd400c6c87575b6d08ccb25dcdab293b83 Mon Sep 17 00:00:00 2001 From: sowle Date: Wed, 23 Oct 2024 22:14:10 +0200 Subject: [PATCH 064/106] cpu miner minor improvements (sounds nice) --- src/currency_core/miner.cpp | 58 +++++++++++++++++++++---------------- src/currency_core/miner.h | 1 + 2 files changed, 34 insertions(+), 25 deletions(-) diff --git a/src/currency_core/miner.cpp b/src/currency_core/miner.cpp index d52e51e2..109d9966 100644 --- a/src/currency_core/miner.cpp +++ b/src/currency_core/miner.cpp @@ -40,24 +40,25 @@ namespace currency } - miner::miner(i_miner_handler* phandler, blockchain_storage& bc):m_stop(1), - //m_bc(bc), - m_template(boost::value_initialized()), - m_template_no(0), - m_diffic(0), - m_thread_index(0), - m_phandler(phandler), - m_height(0), - m_pausers_count(0), - m_threads_total(0), - m_starter_nonce(0), - m_do_print_hashrate(false), - m_do_mining(false), - m_current_hash_rate(0), - m_last_hr_merge_time(0), - m_hashes(0), - m_config(AUTO_VAL_INIT(m_config)), - m_mine_address{} + miner::miner(i_miner_handler* phandler, blockchain_storage& bc) + : m_stop(1) + , m_template(boost::value_initialized()) + , m_template_no(0) + , m_diffic(0) + , m_thread_index(0) + , m_phandler(phandler) + , m_height(0) + , m_pausers_count(0) + , m_block_template_ready(false) + , m_threads_total(0) + , m_starter_nonce(0) + , m_do_print_hashrate(false) + , m_do_mining(false) + , m_current_hash_rate(0) + , m_last_hr_merge_time(0) + , m_hashes(0) + , m_config(AUTO_VAL_INIT(m_config)) + , m_mine_address{} { } //----------------------------------------------------------------------------------------------------- @@ -76,6 +77,7 @@ namespace currency m_height = height; ++m_template_no; m_starter_nonce = crypto::rand(); + m_block_template_ready = true; return true; } //----------------------------------------------------------------------------------------------------- @@ -100,7 +102,8 @@ namespace currency } if(!m_phandler->get_block_template(bl, m_mine_address, m_mine_address, di, height, extra_nonce)) { - LOG_ERROR("Failed to get_block_template()"); + // it's quite possible that block template cannot be created at particular time; need to wait + m_block_template_ready = false; return false; } set_block_template(bl, di, height); @@ -110,7 +113,8 @@ namespace currency bool miner::on_idle() { m_update_block_template_interval.do_call([&](){ - if(is_mining())request_block_template(); + if(is_mining()) + request_block_template(); return true; }); @@ -233,8 +237,13 @@ namespace currency return false; } - if(!m_template_no) - request_block_template();//lets update block template + while(!m_template_no) + { + if (request_block_template()) + break; + std::this_thread::sleep_for(std::chrono::seconds(2)); + LOG_PRINT_L1("Trying to get block template...."); + } boost::interprocess::ipcdetail::atomic_write32(&m_stop, 0); boost::interprocess::ipcdetail::atomic_write32(&m_thread_index, 0); @@ -313,12 +322,11 @@ namespace currency uint64_t local_height = 0; crypto::hash local_blob_data_hash = null_hash; - //uint64_t local_template_height = 0; - block b; + block b{}; while(!m_stop) { - if(m_pausers_count)//anti split workaround + if(m_pausers_count != 0 || !m_block_template_ready) { misc_utils::sleep_no_w(100); continue; diff --git a/src/currency_core/miner.h b/src/currency_core/miner.h index 94caea2b..c8a37225 100644 --- a/src/currency_core/miner.h +++ b/src/currency_core/miner.h @@ -101,6 +101,7 @@ namespace currency volatile uint32_t m_thread_index; volatile uint32_t m_threads_total; std::atomic m_pausers_count; + std::atomic m_block_template_ready; epee::critical_section m_miners_count_lock; std::list m_threads; From 5ae9b2038ee37e4de44a438257ffc1ea05b1b2ba Mon Sep 17 00:00:00 2001 From: sowle Date: Wed, 23 Oct 2024 23:45:19 +0200 Subject: [PATCH 065/106] coretests: several_asset_emit_burn_txs_in_pool test improved and enabled --- tests/core_tests/chaingen_main.cpp | 2 +- tests/core_tests/multiassets_test.cpp | 43 +++++++++++++++++++-------- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index ff0dbc59..d1a217c6 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -1299,7 +1299,7 @@ int main(int argc, char* argv[]) GENERATE_AND_PLAY_HF(eth_signed_asset_basics, "5-*"); // TODO: make HF4 version GENERATE_AND_PLAY_HF(eth_signed_asset_via_rpc, "5-*"); // TODO: make HF4 version //GENERATE_AND_PLAY_HF(asset_current_and_total_supplies_comparative_constraints, "4-*"); <-- temporary disabled, waiting for Stepan's fix -- sowle - //GENERATE_AND_PLAY_HF(several_asset_emit_burn_txs_in_pool, "5-*"); <-- temporary disable, waiting till assets' refactoring is finished -- sowle + GENERATE_AND_PLAY_HF(several_asset_emit_burn_txs_in_pool, "5-*"); GENERATE_AND_PLAY_HF(pos_fuse_test, "4-*"); GENERATE_AND_PLAY_HF(wallet_reorganize_and_trim_test, "4-*"); diff --git a/tests/core_tests/multiassets_test.cpp b/tests/core_tests/multiassets_test.cpp index a317c4f7..b9a57f02 100644 --- a/tests/core_tests/multiassets_test.cpp +++ b/tests/core_tests/multiassets_test.cpp @@ -2411,6 +2411,9 @@ several_asset_emit_burn_txs_in_pool::several_asset_emit_burn_txs_in_pool() bool several_asset_emit_burn_txs_in_pool::generate(std::vector& events) const { + // + // Test idea: make sure two asset emit or two asset burn tx can be added to the pool for the same asset. + // uint64_t ts = test_core_time::get_time(); m_accounts.resize(TOTAL_ACCS_COUNT); account_base& miner_acc = m_accounts[MINER_ACC_IDX]; miner_acc.generate(); miner_acc.set_createtime(ts); @@ -2497,22 +2500,22 @@ bool several_asset_emit_burn_txs_in_pool::c1(currency::core& c, size_t ev_index, CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt, "Alice", total_asset_amount, asset_id, adb.decimal_point), false, ""); // 2.2 the second emit - //additional_emit_amount = 2'500; - //total_asset_amount += additional_emit_amount; + additional_emit_amount = 2'500; + total_asset_amount += additional_emit_amount; - //destinations.clear(); - //destinations.emplace_back(additional_emit_amount, m_accounts[ALICE_ACC_IDX].get_public_address(), null_pkey); - //ft = finalized_tx{}; - //miner_wlt->emit_asset(asset_id, destinations, ft); + destinations.clear(); + destinations.emplace_back(additional_emit_amount, m_accounts[ALICE_ACC_IDX].get_public_address(), null_pkey); + ft = finalized_tx{}; + miner_wlt->emit_asset(asset_id, destinations, ft); - //// make sure the second tx was added to the pool - //CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 2, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count()); + // make sure the second tx was added to the pool + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 2, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count()); - //// Alice checks her asset balance (including unconfirmed txs) - //alice_wlt->refresh(blocks_fetched); - //CHECK_AND_ASSERT_EQ(blocks_fetched, 0); - //alice_wlt->scan_tx_pool(stub); - //CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt, "Alice", total_asset_amount, asset_id, adb.decimal_point), false, ""); + // Alice checks her asset balance (including unconfirmed txs) + alice_wlt->refresh(blocks_fetched); + CHECK_AND_ASSERT_EQ(blocks_fetched, 0); + alice_wlt->scan_tx_pool(stub); + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt, "Alice", total_asset_amount, asset_id, adb.decimal_point), false, ""); // 2.3 // mine a block to confirm both txs to make sure everything is alright @@ -2564,6 +2567,20 @@ bool several_asset_emit_burn_txs_in_pool::c1(currency::core& c, size_t ev_index, alice_wlt->scan_tx_pool(stub); CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt, "Alice", total_asset_amount, asset_id, adb.decimal_point), false, ""); + // 3.3 + // mine a block to confirm both txs to make sure everything is alright + CHECK_AND_ASSERT_MES(mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c), false, ""); + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count()); + + alice_wlt->refresh(blocks_fetched); + CHECK_AND_ASSERT_EQ(blocks_fetched, 1); + + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt, "Alice", total_asset_amount, asset_id, adb.decimal_point), false, ""); + + // make sure these txs are fully confirmed + CHECK_AND_ASSERT_MES(mine_next_pow_blocks_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c, CURRENCY_MINED_MONEY_UNLOCK_WINDOW), false, ""); + + CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt, "Alice", total_asset_amount, asset_id, adb.decimal_point), false, ""); return true; } From d625ec94a57159716f8a8b1d0eabc7745b24d17a Mon Sep 17 00:00:00 2001 From: sowle Date: Thu, 24 Oct 2024 14:10:14 +0200 Subject: [PATCH 066/106] made ts check for pow block template optional --- src/currency_core/blockchain_storage.cpp | 2 +- src/currency_core/blockchain_storage_basic.h | 1 + tests/core_tests/chaingen_helpers.h | 47 +++++++++++--------- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/currency_core/blockchain_storage.cpp b/src/currency_core/blockchain_storage.cpp index 5c6f4e65..3dcbaed8 100644 --- a/src/currency_core/blockchain_storage.cpp +++ b/src/currency_core/blockchain_storage.cpp @@ -1557,7 +1557,7 @@ bool blockchain_storage::create_block_template(const create_block_template_param // check PoW block timestamp against the current blockchain timestamp median -- if it's not okay, don't create a new block // TODO (performance) both get_next_diff_conditional and get_last_n_blocks_timestamps obtains last N blocks, consider data reusing -- sowle - if (!pos) + if (!pos && !params.ignore_pow_ts_check) { uint64_t median_ts = get_last_n_blocks_timestamps_median(BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW); if(b.timestamp < median_ts) diff --git a/src/currency_core/blockchain_storage_basic.h b/src/currency_core/blockchain_storage_basic.h index 0013dc51..c15cdbe9 100644 --- a/src/currency_core/blockchain_storage_basic.h +++ b/src/currency_core/blockchain_storage_basic.h @@ -136,6 +136,7 @@ namespace currency account_public_address stakeholder_address; blobdata ex_nonce; bool pos = false; + bool ignore_pow_ts_check = false; pos_entry pe; std::list explicit_txs; fill_block_template_func_t *pcustom_fill_block_template_func; diff --git a/tests/core_tests/chaingen_helpers.h b/tests/core_tests/chaingen_helpers.h index 4810ed5f..703aec6f 100644 --- a/tests/core_tests/chaingen_helpers.h +++ b/tests/core_tests/chaingen_helpers.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2018 Zano Project +// Copyright (c) 2014-2024 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -17,25 +17,26 @@ template inline bool mine_next_pow_block_in_playtime(const currency::account_public_address& miner_addr, currency::core& c, t_callbacktype modify_block_cb, currency::block* output = nullptr) { - currency::block b = AUTO_VAL_INIT(b); - currency::wide_difficulty_type diff; - uint64_t height; - currency::blobdata extra = AUTO_VAL_INIT(extra); - bool r = c.get_block_template(b, miner_addr, miner_addr, diff, height, extra); + currency::create_block_template_params cbtp{}; + cbtp.ignore_pow_ts_check = true; + cbtp.miner_address = miner_addr; + currency::create_block_template_response cbtr{}; + bool r = c.get_block_template(cbtp, cbtr); CHECK_AND_ASSERT_MES(r, false, "get_block_template failed"); + currency::block& b = cbtr.b; // adjust block's timestamp to keep difficulty low - currency::block last_block = AUTO_VAL_INIT(last_block); + currency::block last_block{}; c.get_blockchain_storage().get_top_block(last_block); b.timestamp = last_block.timestamp + DIFFICULTY_POW_TARGET; // keep global time up with blocks' timestamps test_core_time::adjust(b.timestamp); modify_block_cb(b); - r = currency::miner::find_nonce_for_given_block(b, diff, height); + r = currency::miner::find_nonce_for_given_block(b, cbtr.diffic, cbtr.height); CHECK_AND_ASSERT_MES(r, false, "find_nonce_for_given_block failed"); - currency::block_verification_context bvc = AUTO_VAL_INIT(bvc); + currency::block_verification_context bvc{}; c.handle_incoming_block(t_serializable_object_to_blob(b), bvc); CHECK_AND_NO_ASSERT_MES(!bvc.m_verification_failed && !bvc.m_marked_as_orphaned && !bvc.m_already_exists, false, "block verification context check failed"); @@ -79,21 +80,23 @@ inline bool mine_next_pow_block_in_playtime_with_given_txs(const currency::accou static epee::critical_section s_locker; CHECK_AND_ASSERT_MES((height == SIZE_MAX) == (prev_id == currency::null_hash), false, "invalid agruments: height and prev_id should be specified or not specified together"); - currency::block b = AUTO_VAL_INIT(b); - currency::wide_difficulty_type diff; - uint64_t height_from_template = 0; - currency::blobdata extra = AUTO_VAL_INIT(extra); - currency::pos_entry pe = AUTO_VAL_INIT(pe); + currency::create_block_template_params cbtp{}; + cbtp.ignore_pow_ts_check = true; + cbtp.miner_address = miner_addr; + cbtp.pcustom_fill_block_template_func = loc_helper::fill_block_template_func; + currency::create_block_template_response cbtr{}; bool r = false; { CRITICAL_REGION_LOCAL(s_locker); loc_helper::txs_accessor() = &txs; - r = c.get_blockchain_storage().create_block_template(miner_addr, miner_addr, extra, false, pe, loc_helper::fill_block_template_func, b, diff, height_from_template); + r = c.get_block_template(cbtp, cbtr); + } CHECK_AND_ASSERT_MES(r, false, "get_block_template failed"); + currency::block& b = cbtr.b; // adjust block's timestamp to keep difficulty low - currency::block last_block = AUTO_VAL_INIT(last_block); + currency::block last_block{}; if (prev_id == currency::null_hash) r = c.get_blockchain_storage().get_top_block(last_block); else @@ -109,17 +112,17 @@ inline bool mine_next_pow_block_in_playtime_with_given_txs(const currency::accou CHECK_AND_ASSERT_MES(b.miner_tx.vin.size() > 0, false, "invalid miner_tx.vin"); CHECKED_GET_SPECIFIC_VARIANT(b.miner_tx.vin[0], currency::txin_gen, in, false); in.height = height; - set_tx_unlock_time(b.miner_tx, height + CURRENCY_MINED_MONEY_UNLOCK_WINDOW); + set_tx_unlock_time(b.miner_tx, cbtr.height + CURRENCY_MINED_MONEY_UNLOCK_WINDOW); } else { - height = height_from_template; + height = cbtr.height; } - r = currency::miner::find_nonce_for_given_block(b, diff, height); + r = currency::miner::find_nonce_for_given_block(b, cbtr.diffic, cbtr.height); CHECK_AND_ASSERT_MES(r, false, "find_nonce_for_given_block failed"); - currency::block_verification_context bvc = AUTO_VAL_INIT(bvc); + currency::block_verification_context bvc{}; for (auto& tx : txs) { crypto::hash tx_id = currency::get_transaction_hash(tx); @@ -153,7 +156,7 @@ inline bool mine_next_pow_blocks_in_playtime_with_given_txs(const currency::acco std::vector txs_local = txs; crypto::hash prev_id_internal = prev_id; - currency::block prv_block = AUTO_VAL_INIT(prv_block); + currency::block prv_block{}; bool r = c.get_blockchain_storage().get_block_by_hash(prev_id, prv_block); CHECK_AND_ASSERT_MES(r, false, "block with id " << prev_id << " not found"); @@ -172,7 +175,7 @@ inline bool mine_next_pow_blocks_in_playtime_with_given_txs(const currency::acco // NOTE: stake coins return back to the wallet, newly generated coins go to miner_address (by default they are the same destinations) inline bool mine_next_pos_block_in_playtime_with_wallet(tools::wallet2& w, const currency::account_public_address& miner_address, size_t& pos_entries_count) { - tools::wallet2::mining_context ctx = AUTO_VAL_INIT(ctx); + tools::wallet2::mining_context ctx{}; w.fill_mining_context(ctx); if (!ctx.is_pos_allowed) return false; From a58c0810fb6aec6fb4d77ab57b0f3625288d08b9 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 23 Oct 2024 23:38:55 +0400 Subject: [PATCH 067/106] added ephemenral field for ado in tx history api response --- .../keyvalue_serialization_overloads.h | 8 ++++++-- src/wallet/wallet2_base.h | 10 ++++++---- src/wallet/wallet_public_structs_defs.h | 20 ++++++++++++++----- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/contrib/epee/include/serialization/keyvalue_serialization_overloads.h b/contrib/epee/include/serialization/keyvalue_serialization_overloads.h index d0d7c408..1bd2db1c 100644 --- a/contrib/epee/include/serialization/keyvalue_serialization_overloads.h +++ b/contrib/epee/include/serialization/keyvalue_serialization_overloads.h @@ -360,8 +360,12 @@ namespace epee template< class t_type_stored, class t_type, class t_storage, typename cb_serialize> static bool serialize_ephemeral(const t_type& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname, cb_serialize cb_s) { - t_type_stored a = cb_s(d); - return epee::serialization::selector::serialize(a, stg, hparent_section, pname); + t_type_stored a = AUTO_VAL_INIT(a); + bool add_val = cb_s(d, a); + if (add_val) + return epee::serialization::selector::serialize(a, stg, hparent_section, pname); + else + return true; } }; diff --git a/src/wallet/wallet2_base.h b/src/wallet/wallet2_base.h index 4c88dc58..76fffc18 100644 --- a/src/wallet/wallet2_base.h +++ b/src/wallet/wallet2_base.h @@ -386,14 +386,16 @@ namespace tools return true; return false; } - static inline uint64_t transfer_details_base_to_amount(const transfer_details_base& tdb) + static inline bool transfer_details_base_to_amount(const transfer_details_base& tdb, uint64_t& val) { - return tdb.amount(); + val = tdb.amount(); + return true; } //---------------------------------------------------------------------------------------------------- - static inline std::string transfer_details_base_to_tx_hash(const transfer_details_base& tdb) + static inline bool transfer_details_base_to_tx_hash(const transfer_details_base& tdb, std::string& val) { - return epee::string_tools::pod_to_hex(currency::get_transaction_hash(tdb.m_ptx_wallet_info->m_tx)); + val = epee::string_tools::pod_to_hex(currency::get_transaction_hash(tdb.m_ptx_wallet_info->m_tx)); + return true; } diff --git a/src/wallet/wallet_public_structs_defs.h b/src/wallet/wallet_public_structs_defs.h index ba276a0a..e81aaa87 100644 --- a/src/wallet/wallet_public_structs_defs.h +++ b/src/wallet/wallet_public_structs_defs.h @@ -154,7 +154,6 @@ namespace wallet_public std::vector remote_addresses; //optional std::vector remote_aliases; //optional, describe only if there only one remote address std::vector subtransfers; - boost::optional data_for_external_signing; //not included in streaming serialization uint64_t fee = 0; @@ -191,6 +190,8 @@ namespace wallet_public KV_SERIALIZE(remote_addresses) DOC_DSCR("Remote addresses of this transfer(destination if it's outgoing transfer or sender if it's incoming transaction)") DOC_EXMP_AUTO(1, "ZxBvJDuQjMG9R2j4WnYUhBYNrwZPwuyXrC7FHdVmWqaESgowDvgfWtiXeNGu8Px9B24pkmjsA39fzSSiEQG1ekB225ZnrMTBp") DOC_END KV_SERIALIZE(remote_aliases) DOC_DSCR("Aliases for remot addresses, of discovered") DOC_EXMP_AUTO(1, "roger") DOC_END KV_SERIALIZE(subtransfers) DOC_DSCR("Essential part of transfer entry: amounts that been transfered in this transaction grouped by asset id") DOC_EXMP_AUTO(1) DOC_END + + KV_SERIALIZE_EPHEMERAL_N(currency::asset_descriptor_operation, wallet_transfer_info_get_ado, "ado") DOC_DSCR("\"Asset Descriptor Operation\" if it was present in transaction") DOC_END END_KV_SERIALIZE_MAP() BEGIN_BOOST_SERIALIZATION() @@ -277,6 +278,13 @@ namespace wallet_public subtransfers.back().is_income = true; return subtransfers.back().amount; } + static inline bool wallet_transfer_info_get_ado(const wallet_transfer_info& tdb, currency::asset_descriptor_operation& val) + { + if (currency::get_type_in_variant_container(tdb.tx.extra, val)) + return true; + + return false; + } }; struct wallet_transfer_info_old : public wallet_transfer_info @@ -291,14 +299,16 @@ namespace wallet_public KV_CHAIN_BASE(wallet_transfer_info) END_KV_SERIALIZE_MAP() - static uint64_t wallet_transfer_info_to_amount(const wallet_transfer_info_old& wtio) + static bool wallet_transfer_info_to_amount(const wallet_transfer_info_old& wtio, uint64_t &val) { - return wtio.get_native_amount(); + val = wtio.get_native_amount(); + return true; } - static bool wallet_transfer_info_to_is_income(const wallet_transfer_info_old& wtio) + static bool wallet_transfer_info_to_is_income(const wallet_transfer_info_old& wtio, bool& val) { - return wtio.get_native_is_income(); + val = wtio.get_native_is_income(); + return true; } }; From f7e64b9164b8995574a75e991e9ccd09097c0c79 Mon Sep 17 00:00:00 2001 From: sowle Date: Thu, 24 Oct 2024 14:44:15 +0200 Subject: [PATCH 068/106] don't populate cache in get_last_n_blocks_timestamps_median() if the resulted num of blocks isn't equal to window size --- src/currency_core/blockchain_storage.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/currency_core/blockchain_storage.cpp b/src/currency_core/blockchain_storage.cpp index 3dcbaed8..0d6d41f8 100644 --- a/src/currency_core/blockchain_storage.cpp +++ b/src/currency_core/blockchain_storage.cpp @@ -5709,7 +5709,8 @@ uint64_t blockchain_storage::get_last_n_blocks_timestamps_median(size_t n) const std::vector timestamps = get_last_n_blocks_timestamps(n); uint64_t median_res = epee::misc_utils::median(timestamps); - m_timestamps_median_cache[n] = median_res; + if (timestamps.size() == n) + m_timestamps_median_cache[n] = median_res; return median_res; } //------------------------------------------------------------------ From 8747abe3dd2372fa775876e08d72e61b43c9f99d Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 24 Oct 2024 21:57:25 +0400 Subject: [PATCH 069/106] updated read.me --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 45560781..655e780a 100644 --- a/README.md +++ b/README.md @@ -87,13 +87,13 @@ For instance, by adding the following lines to `~/.bashrc` [*server version*] - export BOOST_ROOT=/home/user/boost_1_70_0 + export BOOST_ROOT=/home/user/boost_1_84_0 export OPENSSL_ROOT_DIR=/home/user/openssl [*GUI version*] - export BOOST_ROOT=/home/user/boost_1_70_0 + export BOOST_ROOT=/home/user/boost_1_84_0 export OPENSSL_ROOT_DIR=/home/user/openssl export QT_PREFIX_PATH=/home/user/Qt5.11.2/5.11.2/gcc_64 From 26f2f7511a85d74e2e24e4e1dc9af88c6c1930d3 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Fri, 25 Oct 2024 13:43:50 +0400 Subject: [PATCH 070/106] added salt to asset generation --- src/wallet/wallet2.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 80de888f..4e6a93ec 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -5548,6 +5548,10 @@ void wallet2::deploy_new_asset(const currency::asset_descriptor_base& asset_info asset_reg_info.opt_descriptor = asset_info; fill_adb_version_based_onhardfork(*asset_reg_info.opt_descriptor); asset_reg_info.operation_type = ASSET_DESCRIPTOR_OPERATION_REGISTER; + if (is_in_hardfork_zone(ZANO_HARDFORK_05)) + { + asset_reg_info.opt_asset_id_salt = crypto::rand(); + } construct_tx_param ctp = get_default_construct_tx_param(); ctp.dsts = destinations; ctp.extra.push_back(asset_reg_info); From d88e1952a6bd2dc93835f377d22b4bc8593764a0 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Fri, 25 Oct 2024 15:26:45 +0400 Subject: [PATCH 071/106] additional condition to handle ado tx --- src/wallet/wallet2.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 4e6a93ec..f688cc16 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -975,7 +975,9 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t //check if there are asset_registration that belong to this wallet const asset_descriptor_operation* pado = get_type_in_variant_container(tx.extra); - if (pado && (ptc.employed_entries.receive.size() || ptc.employed_entries.spent.size() || (pado->opt_descriptor.has_value() && pado->opt_descriptor->owner == m_account.get_public_address().spend_public_key))) + if (pado && (ptc.employed_entries.receive.size() || ptc.employed_entries.spent.size() || (pado->opt_descriptor.has_value() && pado->opt_descriptor->owner == m_account.get_public_address().spend_public_key) || + (pado->opt_asset_id.has_value() && m_own_asset_descriptors.count(pado->opt_asset_id.value())) + )) { //check if there are asset_registration that belong to this wallet process_ado_in_new_transaction(*pado, ptc); From 4e78beebeba569508e6de9c94c114855a3119882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=D1=91pa=20Dolgorukov?= Date: Fri, 25 Oct 2024 21:06:21 +0500 Subject: [PATCH 072/106] unit_tests: repair compilation on the G++ 12 compiler (#473) --- tests/unit_tests/serialization.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/unit_tests/serialization.cpp b/tests/unit_tests/serialization.cpp index bd53b009..fd0807b5 100644 --- a/tests/unit_tests/serialization.cpp +++ b/tests/unit_tests/serialization.cpp @@ -26,10 +26,12 @@ struct Struct char blob[8]; }; - -std::ostream& operator <<(std::ostream& o, const currency::signature_v& v) +namespace currency { - return o; + ostream& operator<<(ostream& stream, [[maybe_unused]] const currency::signature_v& signature) + { + return stream; + } } template @@ -902,4 +904,4 @@ TEST(Serialization, versioning2) r = perform_test_ser_vers(a_3); ASSERT_TRUE(r); -} \ No newline at end of file +} From c0c39276c718313cf30630c96807c89a6aba5f1d Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Fri, 25 Oct 2024 20:17:57 +0400 Subject: [PATCH 073/106] fixed BUILD_SHARED_LIBS overriding in bitcoin-secp256k1 --- contrib/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt index c1a5535f..122b0c89 100644 --- a/contrib/CMakeLists.txt +++ b/contrib/CMakeLists.txt @@ -11,6 +11,11 @@ option(SECP256K1_BUILD_EXHAUSTIVE_TESTS "Build exhaustive tests." OFF) option(SECP256K1_BUILD_CTIME_TESTS "Build constant-time tests." OFF) option(SECP256K1_BUILD_EXAMPLES "Build examples." OFF) set_property(GLOBAL PROPERTY CTEST_TARGETS_ADDED 1) + +if(STATIC) + set(SECP256K1_DISABLE_SHARED ON CACHE BOOL "Disable shared library for secp256k1") + set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build static libraries by default" FORCE) +endif() add_subdirectory(bitcoin-secp256k1) if( NOT DISABLE_TOR) From 13e67e23e11a21060d21126c9586885c1a9e72ab Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Fri, 25 Oct 2024 23:57:59 +0400 Subject: [PATCH 074/106] very intial code for ECDSA from OpenSSL --- src/crypto/eth_signature.cpp | 199 +++++++++++++++++++++++++- tests/core_tests/multiassets_test.cpp | 4 +- 2 files changed, 201 insertions(+), 2 deletions(-) diff --git a/src/crypto/eth_signature.cpp b/src/crypto/eth_signature.cpp index ba5b1358..5d70b11a 100644 --- a/src/crypto/eth_signature.cpp +++ b/src/crypto/eth_signature.cpp @@ -9,6 +9,196 @@ #include +#define USE_OPEN_SSL_FOR_ETH + +#ifdef USE_OPEN_SSL_FOR_ETH +#include +#include +#include +#include +#endif + + + + + +// Function to create EC_KEY from raw 32 - byte private key +EC_KEY * create_ec_key_from_private_key(const unsigned char* private_key) { + EC_KEY* key = EC_KEY_new_by_curve_name(NID_secp256k1); + if (!key) { + std::cerr << "Failed to create new EC Key" << std::endl; + return nullptr; + } + + BIGNUM* priv_key_bn = BN_bin2bn(private_key, 32, nullptr); + if (!priv_key_bn) { + std::cerr << "Failed to convert private key to BIGNUM" << std::endl; + EC_KEY_free(key); + return nullptr; + } + + if (!EC_KEY_set_private_key(key, priv_key_bn)) { + std::cerr << "Failed to set private key" << std::endl; + EC_KEY_free(key); + BN_free(priv_key_bn); + return nullptr; + } + + BN_free(priv_key_bn); + return key; +} + + +void ensure_canonical_s(BIGNUM* s, const EC_GROUP* group) { + // Get the order of the curve + BIGNUM* order = BN_new(); + EC_GROUP_get_order(group, order, nullptr); + + // Compute half of the order: `n / 2` + BIGNUM* half_order = BN_new(); + BN_rshift1(half_order, order); + + // If `s` is greater than `n / 2`, replace `s` with `n - s` + if (BN_cmp(s, half_order) > 0) { + BN_sub(s, order, s); + } + + BN_free(order); + BN_free(half_order); +} + +// Update the function to ensure canonical `s` +bool generate_ethereum_signature(const unsigned char* hash, const unsigned char* private_key, crypto::eth_signature& sig_res) { + EC_KEY* ec_key = create_ec_key_from_private_key(private_key); + if (!ec_key) { + throw std::runtime_error("Failed to create EC key from private key"); + } + + // Sign the hash + unsigned int sig_len = ECDSA_size(ec_key); + std::vector signature(sig_len); + if (ECDSA_sign(0, hash, 32, signature.data(), &sig_len, ec_key) == 0) { + EC_KEY_free(ec_key); + throw std::runtime_error("Failed to create signature"); + } + signature.resize(sig_len); + + + // The OpenSSL ECDSA signature output is DER encoded, Ethereum expects (r, s, v) + const unsigned char* p = signature.data(); + ECDSA_SIG* sig = d2i_ECDSA_SIG(nullptr, &p, sig_len); + if (!sig) { + EC_KEY_free(ec_key); + throw std::runtime_error("Failed to parse ECDSA signature"); + } + + const BIGNUM* r = nullptr; + const BIGNUM* s = nullptr; + ECDSA_SIG_get0(sig, &r, &s); + + // Ensure canonical `s` + BIGNUM* s_canonical = BN_dup(s); + ensure_canonical_s(s_canonical, EC_KEY_get0_group(ec_key)); + + //std::vector r_bytes(32); + //std::vector s_bytes(32); + + //BN_bn2binpad(r, r_bytes.data(), 32); + //BN_bn2binpad(s_canonical, s_bytes.data(), 32); + BN_bn2binpad(r, (unsigned char* )&sig_res.data[0], 32); + BN_bn2binpad(s_canonical, (unsigned char*)&sig_res.data[32], 32); + + + + // To determine the recovery ID (v), you'd need to use custom logic to determine this. + //unsigned char v = 27; // Placeholder + + //std::vector eth_signature(65); + //std::copy(r_bytes.begin(), r_bytes.end(), eth_signature.begin()); + //std::copy(s_bytes.begin(), s_bytes.end(), eth_signature.begin() + 32); + //eth_signature[64] = v; + + ECDSA_SIG_free(sig); + BN_free(s_canonical); + EC_KEY_free(ec_key); + return true; +} + +// Convert raw 33-byte compressed public key to EC_KEY object +EC_KEY* create_ec_key_from_compressed_public_key(const unsigned char* compressed_pub_key) { + EC_KEY* key = EC_KEY_new_by_curve_name(NID_secp256k1); + if (!key) { + std::cerr << "Failed to create EC_KEY object" << std::endl; + return nullptr; + } + + EC_POINT* pub_point = EC_POINT_new(EC_KEY_get0_group(key)); + if (!EC_POINT_oct2point(EC_KEY_get0_group(key), pub_point, compressed_pub_key, 33, nullptr)) { + std::cerr << "Failed to convert compressed public key" << std::endl; + EC_POINT_free(pub_point); + EC_KEY_free(key); + return nullptr; + } + + if (!EC_KEY_set_public_key(key, pub_point)) { + std::cerr << "Failed to set public key" << std::endl; + EC_POINT_free(pub_point); + EC_KEY_free(key); + return nullptr; + } + + EC_POINT_free(pub_point); + return key; +} + +// Function to verify Ethereum-compatible signature +bool verify_ethereum_signature(const crypto::hash& m, const crypto::eth_signature& sig_res, const crypto::eth_public_key& compressed_pub_key) { + EC_KEY* ec_key = create_ec_key_from_compressed_public_key((const unsigned char*)&compressed_pub_key.data[0]); + if (!ec_key) { + throw std::runtime_error("Failed to create EC key from compressed public key"); + } + const unsigned char* r = (unsigned char*)&sig_res.data[0]; + const unsigned char* s = (unsigned char*)&sig_res.data[32]; + const unsigned char* hash = (unsigned char*)&m; + + // Create ECDSA_SIG from r and s + BIGNUM* bn_r = BN_bin2bn(r, 32, nullptr); + BIGNUM* bn_s = BN_bin2bn(s, 32, nullptr); + if (!bn_r || !bn_s) { + EC_KEY_free(ec_key); + BN_free(bn_r); + BN_free(bn_s); + throw std::runtime_error("Failed to convert r or s to BIGNUM"); + } + + ECDSA_SIG* sig = ECDSA_SIG_new(); + if (!sig) { + EC_KEY_free(ec_key); + BN_free(bn_r); + BN_free(bn_s); + throw std::runtime_error("Failed to create ECDSA_SIG object"); + } + + if (!ECDSA_SIG_set0(sig, bn_r, bn_s)) { + EC_KEY_free(ec_key); + ECDSA_SIG_free(sig); + BN_free(bn_r); + BN_free(bn_s); + throw std::runtime_error("Failed to set r and s in ECDSA_SIG"); + } + + // Verify the signature + int verification_result = ECDSA_do_verify(hash, 32, sig, ec_key); + + ECDSA_SIG_free(sig); + EC_KEY_free(ec_key); + //BN_free(bn_r); + //BN_free(bn_s); + + return verification_result == 1; +} + + namespace crypto { bool generate_eth_key_pair(eth_secret_key& sec_key, eth_public_key& pub_key) noexcept @@ -83,6 +273,7 @@ namespace crypto { try { +#ifndef USE_OPEN_SSL_FOR_ETH___ secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); auto slh = epee::misc_utils::create_scope_leave_handler([&ctx](){ secp256k1_context_destroy(ctx); @@ -102,6 +293,9 @@ namespace crypto return false; return true; +#else + return generate_ethereum_signature((const unsigned char*)&m.data, (unsigned char*)&sec_key.data, sig); +#endif } catch(...) { @@ -115,7 +309,7 @@ namespace crypto try { // TODO (performance) consider using secp256k1_context_static for verification -- sowle - +#ifndef USE_OPEN_SSL_FOR_ETH secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); auto slh = epee::misc_utils::create_scope_leave_handler([&ctx](){ secp256k1_context_destroy(ctx); @@ -141,6 +335,9 @@ namespace crypto return false; return true; +#else + return verify_ethereum_signature(m, sig, pub_key); +#endif } catch(...) { diff --git a/tests/core_tests/multiassets_test.cpp b/tests/core_tests/multiassets_test.cpp index b9a57f02..f8948e08 100644 --- a/tests/core_tests/multiassets_test.cpp +++ b/tests/core_tests/multiassets_test.cpp @@ -1488,7 +1488,9 @@ bool eth_signed_asset_basics::c1(currency::core& c, size_t ev_index, const std:: crypto::eth_signature eth_sig{}; r = crypto::generate_eth_signature(ft.tx_id, eth_sk, eth_sig); CHECK_AND_ASSERT_MES(r, false, "generate_eth_signature failed"); - + r = crypto::verify_eth_signature(ft.tx_id, eth_pk, eth_sig); + CHECK_AND_ASSERT_MES(r, false, "generate_eth_signature self validation failed"); + transaction emit_tx{}; bool transfers_unlocked = false; miner_wlt->submit_externally_signed_asset_tx(ft, eth_sig, true, emit_tx, transfers_unlocked); From b0efef8ef1456a6eea6bbcfdf1004934b6849674 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sat, 26 Oct 2024 14:28:27 +0400 Subject: [PATCH 075/106] temporary disabled secp256k1 library --- CMakeLists.txt | 1 + contrib/CMakeLists.txt | 26 ++++--- src/CMakeLists.txt | 9 ++- src/crypto/eth_signature.cpp | 101 ++++++++++++++++++-------- src/crypto/eth_signature.h | 2 +- tests/core_tests/multiassets_test.cpp | 2 +- 6 files changed, 96 insertions(+), 45 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b07daa1d..099c1fc4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,7 @@ set(USE_PCH FALSE CACHE BOOL "Use shared precompiled headers") set(DISABLE_TOR FALSE CACHE BOOL "Disable TOR library(and related tor-connect submodule)") set(TESTNET FALSE CACHE BOOL "Compile for testnet") set(BUILD_GUI FALSE CACHE BOOL "Build qt-daemon") +set(USE_BITCOIN_SECP256K1_FOR_ECDSA FALSE CACHE BOOL "Use bitcoin-secp256k1 library for validating ECDSA(instead of OpenSSL)") include_directories(src contrib/eos_portable_archive contrib contrib/epee/include contrib/jwt-cpp/include ${OPENSSL_INCLUDE_DIR} "${CMAKE_BINARY_DIR}/version" "${CMAKE_BINARY_DIR}/contrib/zlib") diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt index 122b0c89..842f905c 100644 --- a/contrib/CMakeLists.txt +++ b/contrib/CMakeLists.txt @@ -5,18 +5,22 @@ add_subdirectory(zlib) add_subdirectory(db) add_subdirectory(ethereum) -option(SECP256K1_BUILD_BENCHMARK "Build benchmarks." OFF) -option(SECP256K1_BUILD_TESTS "Build tests." OFF) -option(SECP256K1_BUILD_EXHAUSTIVE_TESTS "Build exhaustive tests." OFF) -option(SECP256K1_BUILD_CTIME_TESTS "Build constant-time tests." OFF) -option(SECP256K1_BUILD_EXAMPLES "Build examples." OFF) -set_property(GLOBAL PROPERTY CTEST_TARGETS_ADDED 1) +if(USE_BITCOIN_SECP256K1_FOR_ECDSA) + option(SECP256K1_BUILD_BENCHMARK "Build benchmarks." OFF) + option(SECP256K1_BUILD_TESTS "Build tests." OFF) + option(SECP256K1_BUILD_EXHAUSTIVE_TESTS "Build exhaustive tests." OFF) + option(SECP256K1_BUILD_CTIME_TESTS "Build constant-time tests." OFF) + option(SECP256K1_BUILD_EXAMPLES "Build examples." OFF) + set_property(GLOBAL PROPERTY CTEST_TARGETS_ADDED 1) -if(STATIC) - set(SECP256K1_DISABLE_SHARED ON CACHE BOOL "Disable shared library for secp256k1") - set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build static libraries by default" FORCE) + if(STATIC) + set(SECP256K1_DISABLE_SHARED ON CACHE BOOL "Disable shared library for secp256k1") + set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build static libraries by default" FORCE) + endif() + add_subdirectory(bitcoin-secp256k1) + set_property(TARGET secp256k1 PROPERTY FOLDER "contrib") + set_property(TARGET secp256k1_precomputed PROPERTY FOLDER "contrib") endif() -add_subdirectory(bitcoin-secp256k1) if( NOT DISABLE_TOR) add_subdirectory(tor-connect) @@ -36,8 +40,6 @@ set_property(TARGET libminiupnpc-static PROPERTY FOLDER "contrib") set_property(TARGET zlibstatic PROPERTY FOLDER "contrib") set_property(TARGET mdbx PROPERTY FOLDER "contrib") set_property(TARGET lmdb PROPERTY FOLDER "contrib") -set_property(TARGET secp256k1 PROPERTY FOLDER "contrib") -set_property(TARGET secp256k1_precomputed PROPERTY FOLDER "contrib") if( NOT DISABLE_TOR) set_property(TARGET tor-connect PROPERTY FOLDER "contrib") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 563b4266..4505b8f8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -116,8 +116,13 @@ else() endif() add_library(crypto ${CRYPTO}) -add_dependencies(crypto secp256k1) -target_link_libraries(crypto secp256k1) +if(USE_BITCOIN_SECP256K1_FOR_ECDSA) + add_dependencies(crypto secp256k1) + target_link_libraries(crypto secp256k1) +else() + add_dependencies(crypto OpenSSL::Crypto) + target_link_libraries(crypto OpenSSL::Crypto) +endif() add_library(currency_core ${CURRENCY_CORE}) add_dependencies(currency_core version ${PCH_LIB_NAME}) diff --git a/src/crypto/eth_signature.cpp b/src/crypto/eth_signature.cpp index 5d70b11a..972eab3b 100644 --- a/src/crypto/eth_signature.cpp +++ b/src/crypto/eth_signature.cpp @@ -3,25 +3,25 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "eth_signature.h" #include "crypto.h" -#include "bitcoin-secp256k1/include/secp256k1.h" +#ifndef USE_OPEN_SSL_FOR_ECDSA + #include "bitcoin-secp256k1/include/secp256k1.h" +#endif #include "random.h" #include "misc_language.h" #include -#define USE_OPEN_SSL_FOR_ETH +#define USE_OPEN_SSL_FOR_ECDSA -#ifdef USE_OPEN_SSL_FOR_ETH -#include -#include -#include -#include +#ifdef USE_OPEN_SSL_FOR_ECDSA + #include + #include + #include + #include + #include #endif - - - // Function to create EC_KEY from raw 32 - byte private key EC_KEY * create_ec_key_from_private_key(const unsigned char* private_key) { EC_KEY* key = EC_KEY_new_by_curve_name(NID_secp256k1); @@ -100,24 +100,10 @@ bool generate_ethereum_signature(const unsigned char* hash, const unsigned char* BIGNUM* s_canonical = BN_dup(s); ensure_canonical_s(s_canonical, EC_KEY_get0_group(ec_key)); - //std::vector r_bytes(32); - //std::vector s_bytes(32); - - //BN_bn2binpad(r, r_bytes.data(), 32); - //BN_bn2binpad(s_canonical, s_bytes.data(), 32); BN_bn2binpad(r, (unsigned char* )&sig_res.data[0], 32); BN_bn2binpad(s_canonical, (unsigned char*)&sig_res.data[32], 32); - - // To determine the recovery ID (v), you'd need to use custom logic to determine this. - //unsigned char v = 27; // Placeholder - - //std::vector eth_signature(65); - //std::copy(r_bytes.begin(), r_bytes.end(), eth_signature.begin()); - //std::copy(s_bytes.begin(), s_bytes.end(), eth_signature.begin() + 32); - //eth_signature[64] = v; - ECDSA_SIG_free(sig); BN_free(s_canonical); EC_KEY_free(ec_key); @@ -192,19 +178,72 @@ bool verify_ethereum_signature(const crypto::hash& m, const crypto::eth_signatur ECDSA_SIG_free(sig); EC_KEY_free(ec_key); - //BN_free(bn_r); - //BN_free(bn_s); + return verification_result == 1; } +// +// struct KeyPair { +// std::vector private_key; // 32 bytes +// std::vector public_key; // 33 bytes (compressed format) +// }; + +// Function to generate an Ethereum-compatible key pair +bool generate_ethereum_key_pair(crypto::eth_secret_key& sec_key, crypto::eth_public_key& pub_key) { + /*KeyPair keypair;*/ + + // Create a new EC_KEY object with the secp256k1 curve + EC_KEY* key = EC_KEY_new_by_curve_name(NID_secp256k1); + if (!key) { + throw std::runtime_error("Failed to create new EC_KEY object"); + } + + // Generate the key pair + if (EC_KEY_generate_key(key) == 0) { + EC_KEY_free(key); + throw std::runtime_error("Failed to generate key pair"); + } + + // Extract the private key + const BIGNUM* priv_bn = EC_KEY_get0_private_key(key); + if (!priv_bn) { + EC_KEY_free(key); + throw std::runtime_error("Failed to get private key"); + } + + BN_bn2binpad(priv_bn, (unsigned char*)&sec_key.data[0], 32); + + // Extract the public key in compressed format + const EC_POINT* pub_point = EC_KEY_get0_public_key(key); + if (!pub_point) { + EC_KEY_free(key); + throw std::runtime_error("Failed to get public key"); + } + + //keypair.public_key.resize(33); // Compressed format + if (EC_POINT_point2oct(EC_KEY_get0_group(key), pub_point, POINT_CONVERSION_COMPRESSED, + (unsigned char*)&pub_key.data[0], sizeof(pub_key.data), nullptr) == 0) { + EC_KEY_free(key); + throw std::runtime_error("Failed to convert public key to compressed format"); + } + + EC_KEY_free(key); + return true; +} + + + + + namespace crypto { bool generate_eth_key_pair(eth_secret_key& sec_key, eth_public_key& pub_key) noexcept { try { +#ifndef USE_OPEN_SSL_FOR_ECDSA secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); auto slh = epee::misc_utils::create_scope_leave_handler([&ctx](){ secp256k1_context_destroy(ctx); @@ -234,6 +273,9 @@ namespace crypto return false; return true; +#else + return generate_ethereum_key_pair(sec_key, pub_key); +#endif } catch(...) { @@ -241,6 +283,7 @@ namespace crypto } } +#ifndef USE_OPEN_SSL_FOR_ECDSA bool eth_secret_key_to_public_key(const eth_secret_key& sec_key, eth_public_key& pub_key) noexcept { try @@ -267,13 +310,13 @@ namespace crypto return false; } } - +#endif // generates secp256k1 ECDSA signature bool generate_eth_signature(const hash& m, const eth_secret_key& sec_key, eth_signature& sig) noexcept { try { -#ifndef USE_OPEN_SSL_FOR_ETH___ +#ifndef USE_OPEN_SSL_FOR_ECDSA secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); auto slh = epee::misc_utils::create_scope_leave_handler([&ctx](){ secp256k1_context_destroy(ctx); @@ -309,7 +352,7 @@ namespace crypto try { // TODO (performance) consider using secp256k1_context_static for verification -- sowle -#ifndef USE_OPEN_SSL_FOR_ETH +#ifndef USE_OPEN_SSL_FOR_ECDSA secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE); auto slh = epee::misc_utils::create_scope_leave_handler([&ctx](){ secp256k1_context_destroy(ctx); diff --git a/src/crypto/eth_signature.h b/src/crypto/eth_signature.h index b12d41fe..4db0b8e7 100644 --- a/src/crypto/eth_signature.h +++ b/src/crypto/eth_signature.h @@ -31,7 +31,7 @@ namespace crypto bool generate_eth_key_pair(eth_secret_key& sec_key, eth_public_key& pub_key) noexcept; // converts eth_secret_key to eth_public_key - bool eth_secret_key_to_public_key(const eth_secret_key& sec_key, eth_public_key& pub_key) noexcept; + //bool _eth_secret_key_to_public_key(const eth_secret_key& sec_key, eth_public_key& pub_key) noexcept; // generates secp256k1 ECDSA signature bool generate_eth_signature(const hash& m, const eth_secret_key& sec_key, eth_signature& sig) noexcept; diff --git a/tests/core_tests/multiassets_test.cpp b/tests/core_tests/multiassets_test.cpp index f8948e08..a82e2afc 100644 --- a/tests/core_tests/multiassets_test.cpp +++ b/tests/core_tests/multiassets_test.cpp @@ -1490,7 +1490,7 @@ bool eth_signed_asset_basics::c1(currency::core& c, size_t ev_index, const std:: CHECK_AND_ASSERT_MES(r, false, "generate_eth_signature failed"); r = crypto::verify_eth_signature(ft.tx_id, eth_pk, eth_sig); CHECK_AND_ASSERT_MES(r, false, "generate_eth_signature self validation failed"); - + transaction emit_tx{}; bool transfers_unlocked = false; miner_wlt->submit_externally_signed_asset_tx(ft, eth_sig, true, emit_tx, transfers_unlocked); From 647b5c80f611655353042eefac723a0c5a32c9d0 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sat, 26 Oct 2024 15:36:31 +0400 Subject: [PATCH 076/106] fixes related to USE_OPEN_SSL_FOR_ECDSA --- CMakeLists.txt | 4 ++++ src/crypto/eth_signature.cpp | 2 -- tests/functional_tests/crypto_tests.cpp | 8 ++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 099c1fc4..c480300f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,6 +77,10 @@ set(DISABLE_TOR FALSE CACHE BOOL "Disable TOR library(and related tor-connect su set(TESTNET FALSE CACHE BOOL "Compile for testnet") set(BUILD_GUI FALSE CACHE BOOL "Build qt-daemon") set(USE_BITCOIN_SECP256K1_FOR_ECDSA FALSE CACHE BOOL "Use bitcoin-secp256k1 library for validating ECDSA(instead of OpenSSL)") +if(NOT USE_BITCOIN_SECP256K1_FOR_ECDSA) + add_definitions(-DUSE_OPEN_SSL_FOR_ECDSA) +endif() + include_directories(src contrib/eos_portable_archive contrib contrib/epee/include contrib/jwt-cpp/include ${OPENSSL_INCLUDE_DIR} "${CMAKE_BINARY_DIR}/version" "${CMAKE_BINARY_DIR}/contrib/zlib") diff --git a/src/crypto/eth_signature.cpp b/src/crypto/eth_signature.cpp index 972eab3b..c5e0999b 100644 --- a/src/crypto/eth_signature.cpp +++ b/src/crypto/eth_signature.cpp @@ -11,8 +11,6 @@ #include -#define USE_OPEN_SSL_FOR_ECDSA - #ifdef USE_OPEN_SSL_FOR_ECDSA #include #include diff --git a/tests/functional_tests/crypto_tests.cpp b/tests/functional_tests/crypto_tests.cpp index 28a9b18c..599922f2 100644 --- a/tests/functional_tests/crypto_tests.cpp +++ b/tests/functional_tests/crypto_tests.cpp @@ -1966,9 +1966,9 @@ TEST(crypto, generators_precomp) #undef CHECK_PRECOMP } - -#include "bitcoin-secp256k1/include/secp256k1.h" -TEST(crypto, secp256k1_ecdsa_native) +#ifndef USE_OPEN_SSL_FOR_ECDSA + #include "bitcoin-secp256k1/include/secp256k1.h" + TEST(crypto, secp256k1_ecdsa_native) { bool r = false; @@ -2026,7 +2026,7 @@ TEST(crypto, secp256k1_ecdsa_native) secp256k1_context_destroy(ctx); return true; } - +#endif TEST(crypto, eth_signature_basics) { From a6538b5ecabf5cb4cd69a9da00f01baa9372a442 Mon Sep 17 00:00:00 2001 From: zano build machine Date: Sun, 27 Oct 2024 11:01:46 +0300 Subject: [PATCH 077/106] === build number: 356 -> 357 === --- src/version.h.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h.in b/src/version.h.in index a74c9e1c..7437995c 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -8,6 +8,6 @@ #define PROJECT_REVISION "0" #define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION -#define PROJECT_VERSION_BUILD_NO 356 +#define PROJECT_VERSION_BUILD_NO 357 #define PROJECT_VERSION_BUILD_NO_STR STRINGIFY_EXPAND(PROJECT_VERSION_BUILD_NO) #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO_STR "[" BUILD_COMMIT_ID "]" From e4d9f1da59bfcf4b150b7a0af2335d1a2b89f78d Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sun, 27 Oct 2024 14:33:24 +0400 Subject: [PATCH 078/106] added command for printing ip blacklist --- src/daemon/daemon_commands_handler.h | 15 +++++++++++++++ src/p2p/net_node.h | 1 + src/p2p/net_node.inl | 7 +++++++ 3 files changed, 23 insertions(+) diff --git a/src/daemon/daemon_commands_handler.h b/src/daemon/daemon_commands_handler.h index 3de2d295..1b26fb73 100644 --- a/src/daemon/daemon_commands_handler.h +++ b/src/daemon/daemon_commands_handler.h @@ -48,6 +48,7 @@ public: m_cmd_binder.set_handler("print_tx_prun_info", boost::bind(&daemon_commands_handler::print_tx_prun_info, this, ph::_1), "Print tx prunning info"); m_cmd_binder.set_handler("print_tx", boost::bind(&daemon_commands_handler::print_tx, this, ph::_1), "Print transaction, print_tx "); m_cmd_binder.set_handler("print_asset_info", boost::bind(&daemon_commands_handler::print_asset_info, this, ph::_1), "Print information about the given asset by its id"); + m_cmd_binder.set_handler("print_blocked_ips", boost::bind(&daemon_commands_handler::print_blocked_ips, this, ph::_1), "Print ip address blacklists"); m_cmd_binder.set_handler("start_mining", boost::bind(&daemon_commands_handler::start_mining, this, ph::_1), "Start mining for specified address, start_mining [threads=1]"); m_cmd_binder.set_handler("stop_mining", boost::bind(&daemon_commands_handler::stop_mining, this, ph::_1), "Stop mining"); m_cmd_binder.set_handler("print_pool", boost::bind(&daemon_commands_handler::print_pool, this, ph::_1), "Print transaction pool (long format)"); @@ -235,6 +236,20 @@ private: return true; } //-------------------------------------------------------------------------------- + bool print_blocked_ips(const std::vector& args) + { + std::map blocklist; + m_srv.get_ip_block_list(blocklist); + std::stringstream ss; + ss << "BLOCKED IPS:" << ENDL; + for (const auto& e : blocklist) + { + ss << string_tools::get_ip_string_from_int32(e.first) << ", time: " << std::put_time(std::localtime(&e.second), "%Y-%m-%d %H:%M:%S") << ENDL; + } + LOG_PRINT_L0(ss.str()); + return true; + } + //-------------------------------------------------------------------------------- bool print_bc(const std::vector& args) { if (!args.size()) diff --git a/src/p2p/net_node.h b/src/p2p/net_node.h index 9ec3cc17..38b5a5ab 100644 --- a/src/p2p/net_node.h +++ b/src/p2p/net_node.h @@ -121,6 +121,7 @@ namespace nodetool peerlist_manager& get_peerlist_manager(){return m_peerlist;} bool handle_maintainers_entry(const maintainers_entry& me); bool get_maintainers_info(maintainers_info_external& me); + void get_ip_block_list(std::map& blocklist); typedef COMMAND_REQUEST_STAT_INFO_T COMMAND_REQUEST_STAT_INFO; private: diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 70b906dc..fb1c4109 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -448,6 +448,13 @@ namespace nodetool } //----------------------------------------------------------------------------------- template + void node_server::get_ip_block_list(std::map& blocklist) + { + CRITICAL_REGION_LOCAL(m_blocked_ips_lock); + blocklist = m_blocked_ips; + } + //----------------------------------------------------------------------------------- + template bool node_server::on_maintainers_entry_update() { LOG_PRINT_CHANNEL_COLOR2(NULL, NULL, "Fresh maintainers info recieved(timestamp: " << m_maintainers_info_local.timestamp << ")", LOG_LEVEL_0, epee::log_space::console_color_magenta); From 10df22535981f34cd2b7d01087d615038d9d65f6 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Tue, 29 Oct 2024 13:37:16 +0400 Subject: [PATCH 079/106] logs cleanup --- src/wallet/wallet_rpc_server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index b601d478..31d3b86e 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -252,7 +252,7 @@ namespace tools m_jwt_used_salts.add(salt, ticks_now + JWT_TOKEN_EXPIRATION_MAXIMUM); m_jwt_used_salts.remove_if_expiration_less_than(ticks_now); - LOG_PRINT_L0("JWT token OK"); + LOG_PRINT_L3("JWT token OK"); return true; } catch(const std::exception& e) From 69284ae297aa5cbad82a9b92316860f461746b80 Mon Sep 17 00:00:00 2001 From: zano build machine Date: Wed, 30 Oct 2024 12:41:17 +0300 Subject: [PATCH 080/106] === build number: 357 -> 358 === --- src/version.h.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h.in b/src/version.h.in index 7437995c..57a7bf7a 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -8,6 +8,6 @@ #define PROJECT_REVISION "0" #define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION -#define PROJECT_VERSION_BUILD_NO 357 +#define PROJECT_VERSION_BUILD_NO 358 #define PROJECT_VERSION_BUILD_NO_STR STRINGIFY_EXPAND(PROJECT_VERSION_BUILD_NO) #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO_STR "[" BUILD_COMMIT_ID "]" From 0fe6631e50f9c784654661aef4848ae179904a66 Mon Sep 17 00:00:00 2001 From: sowle Date: Wed, 30 Oct 2024 14:10:24 +0100 Subject: [PATCH 081/106] improvements for assets ticket/full_name checking and for password validation --- src/currency_core/currency_format_utils.cpp | 12 ++++++------ src/simplewallet/simplewallet.cpp | 15 ++++++++++++++- src/wallet/wallet2.cpp | 1 + tests/core_tests/multiassets_test.cpp | 2 +- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/currency_core/currency_format_utils.cpp b/src/currency_core/currency_format_utils.cpp index ef6a4144..d185466b 100644 --- a/src/currency_core/currency_format_utils.cpp +++ b/src/currency_core/currency_format_utils.cpp @@ -3633,13 +3633,13 @@ namespace currency return true; } //------------------------------------------------------------------ + #define PASSWORD_REGEXP R"([A-Za-z0-9~!?@#$%^&*_+|{}\[\]()<>:;"'\-=\\/.,]{0,40})" bool validate_password(const std::string& password) { - static const std::string allowed_password_symbols = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~!?@#$%^&*_+|{}[]()<>:;\"'-=\\/.,"; - size_t n = password.find_first_not_of(allowed_password_symbols, 0); - return n == std::string::npos; + // OLD: static const std::string allowed_password_symbols = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~!?@#$%^&*_+|{}[]()<>:;\"'-=\\/.,"; + static std::regex password_regexp(PASSWORD_REGEXP); + return std::regex_match(password, password_regexp); } - //------------------------------------------------------------------ #define ANTI_OVERFLOW_AMOUNT 1000000 #define GET_PERECENTS_BIG_NUMBERS(per, total) (per/ANTI_OVERFLOW_AMOUNT)*100 / (total/ANTI_OVERFLOW_AMOUNT) @@ -4466,8 +4466,8 @@ namespace currency } } //------------------------------------------------------------------ -#define ASSET_TICKER_REGEXP "[A-Za-z0-9]{1,14}" -#define ASSET_FULL_NAME_REGEXP "[A-Za-z0-9.,:!?\\-() ]{0,400}" +#define ASSET_TICKER_REGEXP R"([A-Za-z0-9]{1,14})" +#define ASSET_FULL_NAME_REGEXP R"([A-Za-z0-9.,:!?\-() ]{0,400})" bool validate_asset_ticker(const std::string& ticker) { static std::regex asset_ticker_regexp(ASSET_TICKER_REGEXP); diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 3c5eb1ec..a95f604b 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -591,6 +591,12 @@ bool simple_wallet::try_connect_to_daemon() //---------------------------------------------------------------------------------------------------- bool simple_wallet::new_wallet(const string &wallet_file, const std::string& password, bool create_auditable_wallet) { + if (!currency::validate_password(password)) + { + fail_msg_writer() << R"(Provided password contains invalid characters. Only letters, numbers and ~!?@#$%^&*_+|{}[]()<>:;"'-=\/., symbols are allowed.)" << ENDL; + return false; + } + m_wallet_file = wallet_file; m_wallet.reset(new tools::wallet2()); @@ -2090,6 +2096,13 @@ bool simple_wallet::deploy_new_asset(const std::vector &args) fail_msg_writer() << "Failed to load json file with asset specification: " << args[0]; return true; } + + if (!validate_asset_ticker_and_full_name(adb)) + { + fail_msg_writer() << "ticker or full_name are invalid (perhaps they contain invalid symbols)"; + return true; + } + tx_destination_entry td = AUTO_VAL_INIT(td); td.addr.push_back(m_wallet->get_account().get_public_address()); td.amount = adb.current_supply; @@ -3474,7 +3487,7 @@ int main(int argc, char* argv[]) //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"); + CHECK_AND_ASSERT_MES(r, EXIT_FAILURE, "Failed to initialize wallet"); if (command_line::get_arg(vm, arg_generate_new_wallet).size() || command_line::get_arg(vm, arg_generate_new_auditable_wallet).size()) return EXIT_FAILURE; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index f688cc16..aa611bb1 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -5544,6 +5544,7 @@ void wallet2::fill_adb_version_based_onhardfork(currency::asset_descriptor_base& void wallet2::deploy_new_asset(const currency::asset_descriptor_base& asset_info, const std::vector& destinations, currency::finalized_tx& ft, crypto::public_key& new_asset_id) { WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(asset_info.decimal_point <= 18, "too big decimal point: " << (int)asset_info.decimal_point); + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(validate_asset_ticker_and_full_name(asset_info), "ticker or full_name are invalid (perhaps they contain invalid symbols)"); asset_descriptor_operation asset_reg_info{}; fill_ado_version_based_onhardfork(asset_reg_info); diff --git a/tests/core_tests/multiassets_test.cpp b/tests/core_tests/multiassets_test.cpp index a82e2afc..bef240c0 100644 --- a/tests/core_tests/multiassets_test.cpp +++ b/tests/core_tests/multiassets_test.cpp @@ -900,7 +900,7 @@ bool asset_emission_and_unconfirmed_balance::c1(currency::core& c, size_t ev_ind asset_descriptor_base adb{}; adb.total_max_supply = UINT64_MAX; - adb.full_name = "2**64"; + adb.full_name = "2 xx 64"; adb.ticker = "2POWER64"; std::vector destinations; From d3632b2f049e0d014e57743440de6873a9390c4c Mon Sep 17 00:00:00 2001 From: sowle Date: Wed, 30 Oct 2024 16:41:01 +0100 Subject: [PATCH 082/106] wallet2 refactoring: the callback is a weak pointer now --- src/simplewallet/simplewallet.cpp | 2 +- src/simplewallet/simplewallet.h | 2 +- src/wallet/wallet2.cpp | 90 +++++++++++-------------------- src/wallet/wallet2.h | 4 +- src/wallet/wallet_rpc_server.cpp | 4 +- 5 files changed, 36 insertions(+), 66 deletions(-) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index a95f604b..2bfdb102 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -3155,7 +3155,7 @@ int wmain( int argc, wchar_t* argv_w[ ], wchar_t* envp[ ] ) int main(int argc, char* argv[]) #endif { -#ifdef WIN32 +#if defined(WIN32) && defined(_DEBUG) _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); //_CrtSetBreakAlloc(9594); #endif diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 5f260433..d8bbb5d8 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -29,7 +29,7 @@ namespace currency typedef std::vector command_type; simple_wallet(); - ~simple_wallet(); + virtual ~simple_wallet(); bool init(const boost::program_options::variables_map& vm); bool deinit(); bool run(); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index aa611bb1..d90e84a2 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -69,7 +69,6 @@ namespace tools { wallet2::wallet2() : m_stop(false) - , m_wcallback(new i_wallet2_callback()) //stub , m_core_proxy(new default_http_core_proxy()) , m_upper_transaction_size_limit(0) , m_fake_outputs_count(0) @@ -438,8 +437,8 @@ void wallet2::process_ado_in_new_transaction(const currency::asset_descriptor_op add_rollback_event(ptc.height, asset_register_event{ asset_id }); WLT_LOG_MAGENTA(ss.str(), LOG_LEVEL_0); - if (m_wcallback) - m_wcallback->on_message(i_wallet2_callback::ms_yellow, ss.str()); + if (auto wcb = m_wcallback.lock()) + wcb->on_message(i_wallet2_callback::ms_yellow, ss.str()); } else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT || ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN) { @@ -480,8 +479,8 @@ void wallet2::process_ado_in_new_transaction(const currency::asset_descriptor_op add_rollback_event(ptc.height, asset_register_event{ asset_id }); WLT_LOG_MAGENTA(ss.str(), LOG_LEVEL_0); - if (m_wcallback) - m_wcallback->on_message(i_wallet2_callback::ms_yellow, ss.str()); + if (auto wcb = m_wcallback.lock()) + wcb->on_message(i_wallet2_callback::ms_yellow, ss.str()); } else { @@ -510,8 +509,8 @@ void wallet2::process_ado_in_new_transaction(const currency::asset_descriptor_op add_rollback_event(ptc.height, asset_register_event{ asset_id }); WLT_LOG_MAGENTA(ss.str(), LOG_LEVEL_0); - if (m_wcallback) - m_wcallback->on_message(i_wallet2_callback::ms_yellow, ss.str()); + if (auto wcb = m_wcallback.lock()) + wcb->on_message(i_wallet2_callback::ms_yellow, ss.str()); } else { @@ -793,8 +792,8 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t ss << "and 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."; WLT_LOG_YELLOW(ss.str(), LOG_LEVEL_0); - if (m_wcallback) - m_wcallback->on_message(i_wallet2_callback::ms_yellow, ss.str()); + if (auto wcb = m_wcallback.lock()) + wcb->on_message(i_wallet2_callback::ms_yellow, ss.str()); //if (out.is_native_coin()) //{ //WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(sum_of_native_outs >= out.amount, "sum_of_native_outs: " << sum_of_native_outs << ", out.amount:" << out.amount); @@ -815,8 +814,8 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t ss << " with amount " << print_money_brief(out.amount) << " is targeted to this auditable wallet and has INCORRECT mix_attr = " << (uint64_t)mix_attr << ". Output is IGNORED."; WLT_LOG_YELLOW(ss.str(), LOG_LEVEL_0); - if (m_wcallback) - m_wcallback->on_message(i_wallet2_callback::ms_red, ss.str()); + if (auto wcb = m_wcallback.lock()) + wcb->on_message(i_wallet2_callback::ms_red, ss.str()); //if (out.is_native_coin()) //{ //WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(sum_of_native_outs >= out.amount, "sum_of_native_outs: " << sum_of_native_outs << ", out.amount:" << out.amount); @@ -1690,44 +1689,10 @@ void wallet2::rise_on_transfer2(const wallet_public::wallet_transfer_info& wti) std::list balances; uint64_t mined_balance = 0; this->balance(balances, mined_balance); - m_wcallback->on_transfer2(wti, balances, mined_balance); - // second call for legacy callback handlers - //m_wcallback->on_transfer2(wti, balances, mined_balance); + if (auto wcb = m_wcallback.lock()) + wcb->on_transfer2(wti, balances, mined_balance); } //---------------------------------------------------------------------------------------------------- -/* -void wallet2::handle_money_spent2(const currency::block& b, - const currency::transaction& in_tx, - uint64_t amount, - const money_transfer2_details& td, - const std::vector& recipients, - const std::vector& remote_aliases) -{ - m_transfer_history.push_back(AUTO_VAL_INIT(wallet_public::wallet_transfer_info())); - wallet_public::wallet_transfer_info& wti = m_transfer_history.back(); - wti.is_income = false; - - wti.remote_addresses = recipients; - wti.remote_aliases = remote_aliases; - prepare_wti(wti, get_block_height(b), get_block_datetime(b), in_tx, amount, td); - WLT_LOG_L1("[MONEY SPENT]: " << epee::serialization::store_t_to_json(wti)); - rise_on_transfer2(wti); -} -//---------------------------------------------------------------------------------------------------- -void wallet2::handle_money_received2(const currency::block& b, const currency::transaction& tx, uint64_t amount, const money_transfer2_details& td) -{ - //decrypt attachments - m_transfer_history.push_back(AUTO_VAL_INIT(wallet_public::wallet_transfer_info())); - wallet_public::wallet_transfer_info& wti = m_transfer_history.back(); - wti.is_income = true; - // TODO @#@# this function is only able to handle native coins atm, consider changing -- sowle - wti.asset_id = native_coin_asset_id; - prepare_wti(wti, get_block_height(b), get_block_datetime(b), tx, amount, td); - WLT_LOG_L1("[MONEY RECEIVED]: " << epee::serialization::store_t_to_json(wti)); - rise_on_transfer2(wti); -} -*/ -//---------------------------------------------------------------------------------------------------- void wallet2::load_wti_from_process_transaction_context(wallet_public::wallet_transfer_info& wti, const process_transaction_context& tx_process_context) { wti.remote_addresses = tx_process_context.recipients; @@ -1948,7 +1913,8 @@ void wallet2::process_new_blockchain_entry(const currency::block& b, const curre process_htlc_triggers_on_block_added(height); - m_wcallback->on_new_block(height, b); + if (auto wcb = m_wcallback.lock()) + wcb->on_new_block(height, b); } //---------------------------------------------------------------------------------------------------- @@ -2182,7 +2148,8 @@ void wallet2::handle_pulled_blocks(size_t& blocks_added, std::atomic& stop uint64_t next_percent = (100 * (current_index - m_height_of_start_sync)) / (res.current_height - m_height_of_start_sync); if (next_percent != m_last_sync_percent) { - m_wcallback->on_sync_progress(next_percent); + if (auto wcb = m_wcallback.lock()) + wcb->on_sync_progress(next_percent); m_last_sync_percent = next_percent; } } @@ -2524,8 +2491,8 @@ uint64_t wallet2::get_directly_spent_transfer_index_by_input_in_tracking_wallet( std::stringstream ss; ss << "own transfer tid=" << tid << " tx=" << td.tx_hash() << " mix_attr=" << td.mix_attr() << ", is referenced by a transaction with mixins, ref from input with amount: " << amount << ", gindex: " << gindex; WLT_LOG_YELLOW(ss.str(), LOG_LEVEL_0); - if (m_wcallback) - m_wcallback->on_message(i_wallet2_callback::ms_yellow, ss.str()); + if (auto wcb = m_wcallback.lock()) + wcb->on_message(i_wallet2_callback::ms_yellow, ss.str()); WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.mix_attr() != CURRENCY_TO_KEY_OUT_FORCED_NO_MIX, ss.str()); // if mix_attr == 1 this should never happen (mixing in an output with mix_attr = 1) as the core must reject such txs // our own output has mix_attr != 1 for some reason (a sender did not set correct mix_attr e.g.) // but mixin count > 1 so we can't say it is spent for sure @@ -2852,7 +2819,8 @@ bool wallet2::scan_not_compliant_unconfirmed_txs() } } //fire some event - m_wcallback->on_transfer_canceled(it->second); + if (auto wcb = m_wcallback.lock()) + wcb->on_transfer_canceled(it->second); m_unconfirmed_txs.erase(it++); } else @@ -2922,8 +2890,8 @@ void wallet2::refresh(size_t& blocks_fetched, bool& received_money, std::atomic< if (reset_count > 1) { WLT_LOG_L0("Intenral error: reset_count infinit loop catch"); - if (m_wcallback) - m_wcallback->on_message(tools::i_wallet2_callback::ms_red, "Internal error: reset_count infinite loop catch"); + if (auto wcb = m_wcallback.lock()) + wcb->on_message(tools::i_wallet2_callback::ms_red, "Internal error: reset_count infinite loop catch"); return; } reset_count++; @@ -2941,16 +2909,16 @@ void wallet2::refresh(size_t& blocks_fetched, bool& received_money, std::atomic< if (++try_count > 3) return; WLT_LOG_L2("no connection to the daemon, wait and try pull_blocks again (try_count: " << try_count << ", blocks_fetched: " << blocks_fetched << ")"); - if (m_wcallback) - m_wcallback->on_message(tools::i_wallet2_callback::ms_red, "no connection to daemon"); + if (auto wcb = m_wcallback.lock()) + wcb->on_message(tools::i_wallet2_callback::ms_red, "no connection to daemon"); std::this_thread::sleep_for(std::chrono::seconds(3)); } catch (const std::exception& e) { blocks_fetched += added_blocks; WLT_LOG_ERROR("refresh->pull_blocks failed, try_count: " << try_count << ", blocks_fetched: " << blocks_fetched << ", exception: " << e.what()); - if (m_wcallback) - m_wcallback->on_message(tools::i_wallet2_callback::ms_red, std::string("error on pulling blocks: ") + e.what()); + if (auto wcb = m_wcallback.lock()) + wcb->on_message(tools::i_wallet2_callback::ms_red, std::string("error on pulling blocks: ") + e.what()); return; } } @@ -5306,7 +5274,8 @@ bool wallet2::build_minted_block(const mining_context& cxt, const currency::acco return false; } WLT_LOG_GREEN("PoS block " << print16(block_hash) << " generated and accepted, congrats!", LOG_LEVEL_0); - m_wcallback->on_pos_block_found(b); + if (auto wcb = m_wcallback.lock()) + wcb->on_pos_block_found(b); gracefull_leaving = true; // to prevent source transfer flags be cleared in scope leave handler return true; @@ -7138,7 +7107,8 @@ void wallet2::set_disable_tor_relay(bool disable) //---------------------------------------------------------------------------------------------------------------- void wallet2::notify_state_change(const std::string& state_code, const std::string& details) { - m_wcallback->on_tor_status_change(state_code); + if (auto wcb = m_wcallback.lock()) + wcb->on_tor_status_change(state_code); } //---------------------------------------------------------------------------------------------------------------- void wallet2::send_transaction_to_network(const transaction& tx) diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 6853d011..8fcced41 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -411,7 +411,7 @@ namespace tools //i_wallet2_callback* callback() const { return m_wcallback; } //void callback(i_wallet2_callback* callback) { m_callback = callback; } void callback(std::shared_ptr callback) { m_wcallback = callback; m_do_rise_transfer = (callback != nullptr); } - i_wallet2_callback* get_callback() { return m_wcallback.get(); } + std::shared_ptr get_callback() { return m_wcallback.lock(); } void set_do_rise_transfer(bool do_rise) { m_do_rise_transfer = do_rise; } bool has_related_alias_entry_unconfirmed(const currency::transaction& tx); @@ -968,7 +968,7 @@ private: std::atomic m_stop; std::shared_ptr m_core_proxy; - std::shared_ptr m_wcallback; + std::weak_ptr m_wcallback; currency::core_runtime_config m_core_runtime_config; diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 31d3b86e..4e30d514 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -1448,7 +1448,7 @@ namespace tools bool wallet_rpc_server::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) { WALLET_RPC_BEGIN_TRY_ENTRY(); - i_wallet2_callback* pcallback = w.get_wallet()->get_callback(); + std::shared_ptr pcallback = w.get_wallet()->get_callback(); if (!pcallback) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; @@ -1463,7 +1463,7 @@ namespace tools bool wallet_rpc_server::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) { WALLET_RPC_BEGIN_TRY_ENTRY(); - i_wallet2_callback* pcallback = w.get_wallet()->get_callback(); + std::shared_ptr pcallback = w.get_wallet()->get_callback(); if (!pcallback) { er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; From b3393af19a96aaeaeefbb29c9339ea3cacc14b57 Mon Sep 17 00:00:00 2001 From: sowle Date: Wed, 30 Oct 2024 19:07:21 +0100 Subject: [PATCH 083/106] ui update --- src/gui/qt-daemon/layout | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/qt-daemon/layout b/src/gui/qt-daemon/layout index 5c878005..ca847ff0 160000 --- a/src/gui/qt-daemon/layout +++ b/src/gui/qt-daemon/layout @@ -1 +1 @@ -Subproject commit 5c878005ace55484eafe2985d204cd51e90b203b +Subproject commit ca847ff0d1563ed347a3ade5c37ff34ddcdb9c58 From 8be6f286f132819018c4e9eb91c226106a0b6af7 Mon Sep 17 00:00:00 2001 From: zano build machine Date: Wed, 30 Oct 2024 21:16:10 +0300 Subject: [PATCH 084/106] === build number: 358 -> 359 === --- src/version.h.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h.in b/src/version.h.in index 57a7bf7a..8b603934 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -8,6 +8,6 @@ #define PROJECT_REVISION "0" #define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION -#define PROJECT_VERSION_BUILD_NO 358 +#define PROJECT_VERSION_BUILD_NO 359 #define PROJECT_VERSION_BUILD_NO_STR STRINGIFY_EXPAND(PROJECT_VERSION_BUILD_NO) #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO_STR "[" BUILD_COMMIT_ID "]" From 5bdaf8eaed4e487b77fe0f736ded42f66ab778de Mon Sep 17 00:00:00 2001 From: sowle Date: Thu, 31 Oct 2024 03:39:09 +0100 Subject: [PATCH 085/106] removed a redundant param in generate_zc_outs_range_proof() --- src/currency_core/currency_basic.h | 2 +- src/currency_core/currency_format_utils.cpp | 11 +++++------ src/currency_core/currency_format_utils.h | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/currency_core/currency_basic.h b/src/currency_core/currency_basic.h index 6cfc085d..d0630089 100644 --- a/src/currency_core/currency_basic.h +++ b/src/currency_core/currency_basic.h @@ -815,7 +815,7 @@ namespace currency boost::optional opt_amount_commitment; // + + + - (premultiplied by 1/8) boost::optional opt_asset_id; // - + + + boost::optional opt_descriptor; // + - - + - boost::optional opt_amount; // ? + + - (only for non-hidden supply) + boost::optional opt_amount; // ? ? ? - (only for non-hidden supply) boost::optional opt_asset_id_salt; // ? - - - (optional) std::vector etc; // (reserved for future use) diff --git a/src/currency_core/currency_format_utils.cpp b/src/currency_core/currency_format_utils.cpp index d185466b..2910bc8e 100644 --- a/src/currency_core/currency_format_utils.cpp +++ b/src/currency_core/currency_format_utils.cpp @@ -197,17 +197,17 @@ namespace currency return true; } //-------------------------------------------------------------------------------- - bool generate_zc_outs_range_proof(const crypto::hash& context_hash, size_t out_index_start, const tx_generation_context& outs_gen_context, + bool generate_zc_outs_range_proof(const crypto::hash& context_hash, const tx_generation_context& outs_gen_context, const std::vector& vouts, zc_outs_range_proof& result) { size_t outs_count = outs_gen_context.amounts.size(); // TODO @#@# reconsider this check CHECK_AND_ASSERT_MES(gen_context.check_sizes(outs_count), false, ""); - CHECK_AND_ASSERT_MES(out_index_start + outs_count == vouts.size(), false, ""); + CHECK_AND_ASSERT_MES(outs_count == vouts.size(), false, ""); // prepare data for aggregation proof std::vector amount_commitments_for_rp_aggregation; // E' = amount * U + y' * G crypto::scalar_vec_t y_primes; // y' - for (size_t out_index = out_index_start, i = 0; i < outs_count; ++out_index, ++i) + for (size_t i = 0; i < outs_count; ++i) { crypto::scalar_t y_prime = crypto::scalar_t::random(); amount_commitments_for_rp_aggregation.emplace_back(outs_gen_context.amounts[i] * crypto::c_point_U + y_prime * crypto::c_point_G); // E'_j = e_j * U + y'_j * G @@ -564,7 +564,7 @@ namespace currency // range proofs currency::zc_outs_range_proof range_proofs{}; - r = generate_zc_outs_range_proof(tx_id, 0, tx_gen_context, tx.vout, range_proofs); + r = generate_zc_outs_range_proof(tx_id, tx_gen_context, tx.vout, range_proofs); CHECK_AND_ASSERT_MES(r, false, "Failed to generate zc_outs_range_proof()"); tx.proofs.emplace_back(std::move(range_proofs)); @@ -2632,7 +2632,6 @@ namespace currency // construct outputs uint64_t native_coins_output_sum = 0; size_t output_index = tx.vout.size(); // in case of append mode we need to start output indexing from the last one + 1 - uint64_t range_proof_start_index = 0; std::set existing_derivation_hints, new_derivation_hints; CHECK_AND_ASSERT_MES(copy_all_derivation_hints_from_tx_to_container(tx, existing_derivation_hints), false, "move_all_derivation_hints_from_tx_to_container failed"); for(size_t destination_index = 0; destination_index < shuffled_dsts.size(); ++destination_index, ++output_index) @@ -2774,7 +2773,7 @@ namespace currency // range proofs currency::zc_outs_range_proof range_proofs{}; - r = generate_zc_outs_range_proof(tx_prefix_hash, range_proof_start_index, gen_context, tx.vout, range_proofs); + r = generate_zc_outs_range_proof(tx_prefix_hash, gen_context, tx.vout, range_proofs); CHECK_AND_ASSERT_MES(r, false, "Failed to generate zc_outs_range_proof()"); tx.proofs.emplace_back(std::move(range_proofs)); diff --git a/src/currency_core/currency_format_utils.h b/src/currency_core/currency_format_utils.h index aaeba9a2..399a6d90 100644 --- a/src/currency_core/currency_format_utils.h +++ b/src/currency_core/currency_format_utils.h @@ -273,7 +273,7 @@ namespace currency bool generate_asset_surjection_proof(const crypto::hash& context_hash, bool has_non_zc_inputs, tx_generation_context& ogc, zc_asset_surjection_proof& result); bool verify_asset_surjection_proof(const transaction& tx, const crypto::hash& tx_id); bool generate_tx_balance_proof(const transaction &tx, const crypto::hash& tx_id, const tx_generation_context& ogc, uint64_t block_reward_for_miner_tx, zc_balance_proof& proof); - bool generate_zc_outs_range_proof(const crypto::hash& context_hash, size_t out_index_start, const tx_generation_context& outs_gen_context, + bool generate_zc_outs_range_proof(const crypto::hash& context_hash, const tx_generation_context& outs_gen_context, const std::vector& vouts, zc_outs_range_proof& result); bool check_tx_bare_balance(const transaction& tx, uint64_t additional_inputs_amount_and_fees_for_mining_tx = 0); bool check_tx_balance(const transaction& tx, const crypto::hash& tx_id, uint64_t additional_inputs_amount_and_fees_for_mining_tx = 0); From 753effce28bc9431ccaa5cfa39b1abc880a24382 Mon Sep 17 00:00:00 2001 From: sowle Date: Thu, 31 Oct 2024 04:00:11 +0100 Subject: [PATCH 086/106] fixed usage of generate_zc_outs_range_proof() --- src/wallet/wallet2.cpp | 2 +- tests/core_tests/chaingen.cpp | 2 +- tests/core_tests/pos_block_builder.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index d90e84a2..5ab899dd 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -5048,7 +5048,7 @@ bool wallet2::prepare_and_sign_pos_block(const mining_context& cxt, uint64_t ful // range proofs currency::zc_outs_range_proof range_proofs{}; - r = generate_zc_outs_range_proof(miner_tx_id, 0, miner_tx_tgc, b.miner_tx.vout, range_proofs); + r = generate_zc_outs_range_proof(miner_tx_id, miner_tx_tgc, b.miner_tx.vout, range_proofs); WLT_CHECK_AND_ASSERT_MES(r, false, "Failed to generate zc_outs_range_proof()"); b.miner_tx.proofs.emplace_back(std::move(range_proofs)); diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index a5d97fd1..2679dd39 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -1115,7 +1115,7 @@ bool test_generator::construct_pow_block_with_alias_info_in_coinbase(const accou miner_tx.proofs.emplace_back(std::move(currency::zc_asset_surjection_proof{})); // range proofs currency::zc_outs_range_proof range_proofs{}; - r = generate_zc_outs_range_proof(tx_id, 0, tx_gen_context, miner_tx.vout, range_proofs); + r = generate_zc_outs_range_proof(tx_id, tx_gen_context, miner_tx.vout, range_proofs); CHECK_AND_ASSERT_MES(r, false, "Failed to generate zc_outs_range_proof()"); miner_tx.proofs.emplace_back(std::move(range_proofs)); // balance proof diff --git a/tests/core_tests/pos_block_builder.cpp b/tests/core_tests/pos_block_builder.cpp index 62561121..da3e08c1 100644 --- a/tests/core_tests/pos_block_builder.cpp +++ b/tests/core_tests/pos_block_builder.cpp @@ -282,7 +282,7 @@ void pos_block_builder::step5_sign(const currency::tx_source_entry& se, const cu // range proofs currency::zc_outs_range_proof range_proofs{}; - r = generate_zc_outs_range_proof(miner_tx_id, 0, m_miner_tx_tgc, m_block.miner_tx.vout, range_proofs); + r = generate_zc_outs_range_proof(miner_tx_id, m_miner_tx_tgc, m_block.miner_tx.vout, range_proofs); CHECK_AND_ASSERT_THROW_MES(r, "Failed to generate zc_outs_range_proof()"); m_block.miner_tx.proofs.emplace_back(std::move(range_proofs)); From d0971413ca8b49d9308f631d1096d6f547b51260 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sat, 2 Nov 2024 13:05:45 +0400 Subject: [PATCH 087/106] added postponed wallet loop launch in plain wallet --- src/wallet/core_default_rpc_proxy.cpp | 6 +- src/wallet/plain_wallet_api.cpp | 110 +++++++++++++++++++++--- src/wallet/plain_wallet_api_defs.h | 15 ++++ src/wallet/wallet_public_structs_defs.h | 92 +------------------- src/wallet/wallets_manager.cpp | 13 ++- src/wallet/wallets_manager.h | 1 + tests/performance_tests/main.cpp | 18 +++- 7 files changed, 147 insertions(+), 108 deletions(-) diff --git a/src/wallet/core_default_rpc_proxy.cpp b/src/wallet/core_default_rpc_proxy.cpp index 4bd94188..38907e37 100644 --- a/src/wallet/core_default_rpc_proxy.cpp +++ b/src/wallet/core_default_rpc_proxy.cpp @@ -20,7 +20,11 @@ namespace tools { bool default_http_core_proxy::set_connection_addr(const std::string& url) { - m_daemon_address = url; + if (m_daemon_address != url) + { + m_daemon_address = url; + m_http_client.disconnect(); + } return true; } //------------------------------------------------------------------------------------------------------------------------------ diff --git a/src/wallet/plain_wallet_api.cpp b/src/wallet/plain_wallet_api.cpp index 3a9a1552..bb322481 100644 --- a/src/wallet/plain_wallet_api.cpp +++ b/src/wallet/plain_wallet_api.cpp @@ -56,6 +56,9 @@ void static_destroy_handler() LOG_PRINT_L0("[DESTROY CALLBACK HANDLER FINISHED]: "); } + + + namespace plain_wallet { struct plain_wallet_instance @@ -63,7 +66,9 @@ namespace plain_wallet plain_wallet_instance() :initialized(false), gjobs_counter(1) {} wallets_manager gwm; - std::atomic initialized; + std::atomic initialized = false; + std::atomic postponed_run_wallet = false; + std::atomic postponed_main_worked_started = false; std::atomic gjobs_counter; std::map gjobs; @@ -73,7 +78,6 @@ namespace plain_wallet std::shared_ptr ginstance_ptr; - typedef epee::json_rpc::response error_response; @@ -182,8 +186,11 @@ namespace plain_wallet } - std::string init(const std::string& ip, const std::string& port, const std::string& working_dir, int log_level) + std::string init(const std::string& ip_, const std::string& port_, const std::string& working_dir, int log_level) { + std::string ip = ip_; + std::string port = port_; + auto local_ptr = std::atomic_load(&ginstance_ptr); if (local_ptr) { @@ -197,6 +204,14 @@ namespace plain_wallet std::shared_ptr ptr(new plain_wallet_instance()); + if (ip.empty()) + { + ip = "0.0.0.0"; + port = "0"; + ptr->postponed_run_wallet = true; + } + + set_bundle_working_dir(working_dir); initialize_logs(log_level); @@ -217,10 +232,14 @@ namespace plain_wallet ptr->gwm.set_use_deffered_global_outputs(true); - if(!ptr->gwm.start()) + if (!ptr->postponed_run_wallet && !ptr->postponed_main_worked_started) { - LOG_ERROR("Failed to start wallets_manager"); - return GENERAL_INTERNAL_ERRROR_INIT; + if (!ptr->gwm.start()) + { + LOG_ERROR("Failed to start wallets_manager"); + return GENERAL_INTERNAL_ERRROR_INIT; + } + ptr->postponed_main_worked_started = true; } LOG_PRINT_L0("[INIT PLAIN_WALLET_INSTANCE] Ver:" << PROJECT_VERSION_LONG << "(" << BUILD_TYPE << ")"); @@ -261,11 +280,12 @@ namespace plain_wallet std::string init(const std::string& address, const std::string& working_dir, int log_level) { epee::net_utils::http::url_content url_data = AUTO_VAL_INIT(url_data); - if(!epee::net_utils::parse_url(address, url_data)) + if(!address.empty() && !epee::net_utils::parse_url(address, url_data)) { LOG_ERROR("Failed to parse address"); return API_RETURN_CODE_BAD_ARG; } + return init(url_data.host, std::to_string(url_data.port), working_dir, log_level); } @@ -438,7 +458,10 @@ namespace plain_wallet { ok_response.result.recovered = true; } - inst_ptr->gwm.run_wallet(ok_response.result.wallet_id); + if (!inst_ptr->postponed_run_wallet) + { + inst_ptr->gwm.run_wallet(ok_response.result.wallet_id); + } return epee::serialization::store_t_to_json(ok_response); } @@ -460,7 +483,10 @@ namespace plain_wallet { ok_response.result.recovered = true; } - inst_ptr->gwm.run_wallet(ok_response.result.wallet_id); + if (!inst_ptr->postponed_run_wallet) + { + inst_ptr->gwm.run_wallet(ok_response.result.wallet_id); + } return epee::serialization::store_t_to_json(ok_response); } error_response err_result = AUTO_VAL_INIT(err_result); @@ -481,7 +507,10 @@ namespace plain_wallet { ok_response.result.recovered = true; } - inst_ptr->gwm.run_wallet(ok_response.result.wallet_id); + if (!inst_ptr->postponed_run_wallet) + { + inst_ptr->gwm.run_wallet(ok_response.result.wallet_id); + } return epee::serialization::store_t_to_json(ok_response); } error_response err_result = AUTO_VAL_INIT(err_result); @@ -550,6 +579,52 @@ namespace plain_wallet return std::string("{ \"job_id\": ") + std::to_string(job_id) + "}"; } + std::string handle_reset_connection_url(const std::string& url) + { + GET_INSTANCE_PTR(inst_ptr); + inst_ptr->gwm.set_remote_node_url(url); + + view::api_response ar = AUTO_VAL_INIT(ar); + ar.error_code = API_RETURN_CODE_OK; + return epee::serialization::store_t_to_json(ar); + } + + std::string handle_run_wallet(uint64_t instance_id) + { + GET_INSTANCE_PTR(inst_ptr); + + + //postponed worker loop launch + if (!inst_ptr->postponed_main_worked_started) + { + if (!inst_ptr->gwm.start()) + { + LOG_ERROR("Failed to start wallets_manager"); + return API_RETURN_CODE_INTERNAL_ERROR; + } + inst_ptr->postponed_main_worked_started = true; + } + + view::api_response ar = AUTO_VAL_INIT(ar); + ar.error_code = inst_ptr->gwm.run_wallet(instance_id); + return epee::serialization::store_t_to_json(ar); + } + + std::string handle_configure(const std::string& settings_json) + { + GET_INSTANCE_PTR(inst_ptr); + configure_object conf = AUTO_VAL_INIT(conf); + configure_response conf_resp = AUTO_VAL_INIT(conf_resp); + bool res = epee::serialization::load_t_from_json(conf, settings_json); + if (!res) + { + conf_resp.status = API_RETURN_CODE_BAD_ARG; + return epee::serialization::store_t_to_json(conf_resp); + } + inst_ptr->postponed_run_wallet = conf.postponed_run_wallet; + conf_resp.status = API_RETURN_CODE_OK; + return epee::serialization::store_t_to_json(conf_resp); + } std::string sync_call(const std::string& method_name, uint64_t instance_id, const std::string& params) { @@ -612,6 +687,18 @@ namespace plain_wallet { res = get_wallet_status(instance_id); } + else if (method_name == "configure") + { + res = handle_configure(params); + } + else if (method_name == "reset_connection_url") + { + res = handle_reset_connection_url(params); + } + else if (method_name == "run_wallet") + { + res = handle_run_wallet(instance_id); + } else { view::api_response ar = AUTO_VAL_INIT(ar); @@ -621,9 +708,6 @@ namespace plain_wallet return res; } - - - std::string try_pull_result(uint64_t job_id) { auto inst_ptr = std::atomic_load(&ginstance_ptr); diff --git a/src/wallet/plain_wallet_api_defs.h b/src/wallet/plain_wallet_api_defs.h index 9273d524..613227d0 100644 --- a/src/wallet/plain_wallet_api_defs.h +++ b/src/wallet/plain_wallet_api_defs.h @@ -47,4 +47,19 @@ namespace plain_wallet END_KV_SERIALIZE_MAP() }; + struct configure_object + { + bool postponed_run_wallet = false; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(postponed_run_wallet) + END_KV_SERIALIZE_MAP() + }; + struct configure_response + { + std::string status; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(status) + END_KV_SERIALIZE_MAP() + }; + } // namespace tools diff --git a/src/wallet/wallet_public_structs_defs.h b/src/wallet/wallet_public_structs_defs.h index e81aaa87..74db48f9 100644 --- a/src/wallet/wallet_public_structs_defs.h +++ b/src/wallet/wallet_public_structs_defs.h @@ -1012,92 +1012,6 @@ namespace wallet_public END_KV_SERIALIZE_MAP() }; - struct COMMAND_RPC_MAKETELEPOD - { - struct request - { - uint64_t amount; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(amount) - END_KV_SERIALIZE_MAP() - }; - - struct response - { - std::string status; //"OK", "INSUFFICIENT_COINS", "INTERNAL_ERROR" - telepod tpd; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) - KV_SERIALIZE(tpd) - END_KV_SERIALIZE_MAP() - }; - }; - - - struct COMMAND_RPC_TELEPODSTATUS - { - struct request - { - telepod tpd; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(tpd) - END_KV_SERIALIZE_MAP() - }; - - struct response - { - std::string status; //"OK", "UNCONFIRMED", "BAD", "SPENT", "INTERNAL_ERROR" - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) - END_KV_SERIALIZE_MAP() - }; - }; - - struct COMMAND_RPC_CLONETELEPOD - { - struct request - { - telepod tpd; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(tpd) - END_KV_SERIALIZE_MAP() - }; - - struct response - { - std::string status;//"OK", "UNCONFIRMED", "BAD", "SPENT", "INTERNAL_ERROR:" - telepod tpd; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) - KV_SERIALIZE(tpd) - END_KV_SERIALIZE_MAP() - }; - }; - - struct COMMAND_RPC_WITHDRAWTELEPOD - { - struct request - { - telepod tpd; - std::string addr; - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(tpd) - KV_SERIALIZE(addr) - END_KV_SERIALIZE_MAP() - }; - - struct response - { - std::string status; //"OK", "UNCONFIRMED", "BAD", "SPENT", "INTERNAL_ERROR", "BAD_ADDRESS" - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(status) - END_KV_SERIALIZE_MAP() - }; - }; - - struct create_proposal_param { // uint64_t wallet_id; @@ -2131,8 +2045,8 @@ namespace wallet_public { currency::blobdata finalized_tx; currency::blobdata unsigned_tx; - crypto::eth_signature eth_sig; - crypto::hash expected_tx_id; + crypto::eth_signature eth_sig; //TODO: add value initialization here + crypto::hash expected_tx_id = currency::null_hash; bool unlock_transfers_on_fail = false; BEGIN_KV_SERIALIZE_MAP() @@ -2147,7 +2061,7 @@ namespace wallet_public struct response { std::string status; - bool transfers_were_unlocked; + bool transfers_were_unlocked = false; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(status) DOC_DSCR("Status of the call") DOC_EXMP("OK") DOC_END diff --git a/src/wallet/wallets_manager.cpp b/src/wallet/wallets_manager.cpp index 3a08b448..426aff27 100644 --- a/src/wallet/wallets_manager.cpp +++ b/src/wallet/wallets_manager.cpp @@ -395,7 +395,13 @@ bool wallets_manager::start() CATCH_ENTRY_L0("main", false); } - +std::string wallets_manager::set_remote_node_url(const std::string& url) +{ + if (m_rpc_proxy) + m_rpc_proxy->set_connection_addr(url); + + return API_RETURN_CODE_OK; +} bool wallets_manager::stop() { @@ -1921,7 +1927,10 @@ std::string wallets_manager::stop_pos_mining(uint64_t wallet_id) std::string wallets_manager::run_wallet(uint64_t wallet_id) { GET_WALLET_OPT_BY_ID(wallet_id, wo); - wo.miner_thread = std::thread(boost::bind(&wallets_manager::wallet_vs_options::worker_func, &wo)); + if (!wo.major_stop && !wo.miner_thread.joinable()) + { + wo.miner_thread = std::thread(boost::bind(&wallets_manager::wallet_vs_options::worker_func, &wo)); + } return API_RETURN_CODE_OK; } diff --git a/src/wallet/wallets_manager.h b/src/wallet/wallets_manager.h index 01c62062..6fa14328 100644 --- a/src/wallet/wallets_manager.h +++ b/src/wallet/wallets_manager.h @@ -147,6 +147,7 @@ public: std::string get_tx_pool_info(currency::COMMAND_RPC_GET_POOL_INFO::response& res); std::string export_wallet_history(const view::export_wallet_info& ewi); std::string setup_wallet_rpc(const std::string& jwt_secret); + std::string set_remote_node_url(const std::string& url); #ifndef MOBILE_WALLET_BUILD currency::core_rpc_server& get_rpc_server() { return m_rpc_server; } diff --git a/tests/performance_tests/main.cpp b/tests/performance_tests/main.cpp index 1055bfca..9b285b68 100644 --- a/tests/performance_tests/main.cpp +++ b/tests/performance_tests/main.cpp @@ -24,9 +24,10 @@ #include "free_space_check.h" #include "htlc_hash_tests.h" #include "threads_pool_tests.h" -#include "wallet/plain_wallet_api.h" +#include "wallet/plain_wallet_api.h" #include "wallet/view_iface.h" - +#include "wallet/plain_wallet_api_defs.h" + PUSH_VS_WARNINGS DISABLE_VS_WARNINGS(4244) #include "jwt-cpp/jwt.h" @@ -34,9 +35,15 @@ POP_VS_WARNINGS void test_plain_wallet() { - std::string res = plain_wallet::init("195.201.107.230", "33340", "C:\\Users\\roky\\home\\", 0); + //std::string res = plain_wallet::init("195.201.107.230", "33340", "C:\\Users\\roky\\home\\", 0); + std::string res = plain_wallet::init("", "", "C:\\Users\\roky\\home\\", 0); //std::string res = plain_wallet::init("127.0.0.1", "12111", "C:\\Users\\roky\\home22\\", 0); + plain_wallet::configure_object conf = AUTO_VAL_INIT(conf); + //plain_wallet::configure_response conf_resp = AUTO_VAL_INIT(conf_resp); + conf.postponed_run_wallet = true; + std::string r = plain_wallet::sync_call("configure", 0, epee::serialization::store_t_to_json(conf)); + std::string res___ = plain_wallet::get_wallet_files(); @@ -46,6 +53,10 @@ void test_plain_wallet() //res = plain_wallet::restore("", // "test_restored_2.zan", "111", ""); + epee::misc_utils::sleep_no_w(2000); + + res = plain_wallet::sync_call("reset_connection_url", 0, "195.201.107.230:33336"); + r = plain_wallet::sync_call("run_wallet", instance_id, ""); while(true) { @@ -53,6 +64,7 @@ void test_plain_wallet() res = plain_wallet::sync_call("get_wallet_status", instance_id, ""); view::wallet_sync_status_info wsi = AUTO_VAL_INIT(wsi); epee::serialization::load_t_from_json(wsi, res); + LOG_PRINT_L0("Progress: " << wsi.progress); if (wsi.wallet_state == 2) break; } From 422c7f2a3ab7647dbc46fbc0504053161fdbc734 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=D1=91pa=20Dolgorukov?= Date: Mon, 4 Nov 2024 18:12:19 +0500 Subject: [PATCH 088/106] unit_tests: implemented a test for the function "prepare_outputs_for_key_offsets" (#477) --- ...repare_outputs_entries_for_key_offsets.cpp | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 tests/unit_tests/prepare_outputs_entries_for_key_offsets.cpp diff --git a/tests/unit_tests/prepare_outputs_entries_for_key_offsets.cpp b/tests/unit_tests/prepare_outputs_entries_for_key_offsets.cpp new file mode 100644 index 00000000..d8baa9b6 --- /dev/null +++ b/tests/unit_tests/prepare_outputs_entries_for_key_offsets.cpp @@ -0,0 +1,139 @@ +#include "gtest/gtest.h" +#include "currency_core/currency_format_utils_abstract.h" +#include "currency_core/currency_format_utils_transactions.h" + +TEST(prepare_outputs_for_key_offsets, size_lower_than_2) +{ + for (size_t size{}; size < 2; ++size) + { + std::vector outputs(size); + size_t index{SIZE_MAX}; + + ASSERT_EQ(currency::prepare_outputs_entries_for_key_offsets(outputs, 0, index), outputs); + ASSERT_EQ(index, 0); + } +} + +TEST(prepare_outputs_for_key_offsets, pass_index_greater_or_equal_to_length_of_outputs_container) +{ + const std::vector outputs(2); + + { + auto index{crypto::rand()}; + const auto index_before_preparing_outputs{index}; + + ASSERT_TRUE(currency::prepare_outputs_entries_for_key_offsets(outputs, outputs.size(), index).empty()); + ASSERT_EQ(index, index_before_preparing_outputs); + } + + { + auto index{crypto::rand()}; + const auto index_before_preparing_outputs{index}; + + ASSERT_TRUE(currency::prepare_outputs_entries_for_key_offsets(outputs, outputs.size() + 1, index).empty()); + ASSERT_EQ(index, index_before_preparing_outputs); + } + + { + auto index{crypto::rand()}; + const auto index_before_preparing_outputs{index}; + + ASSERT_TRUE(currency::prepare_outputs_entries_for_key_offsets(outputs, SIZE_MAX - 1, index).empty()); + ASSERT_EQ(index, index_before_preparing_outputs); + } + + { + auto index{crypto::rand()}; + const auto index_before_preparing_outputs{index}; + + ASSERT_EQ(currency::prepare_outputs_entries_for_key_offsets(outputs, SIZE_MAX, index), outputs); + ASSERT_EQ(index, index_before_preparing_outputs); + } +} + +TEST(prepare_outputs_for_key_offsets, uint64_reference_lower_than_by_id_reference) +{ + std::vector outputs{}, expected_prepared_outputs{}; + size_t index{}; + + outputs.reserve(2); + outputs.emplace_back(currency::ref_by_id{}, currency::null_pkey); + outputs.emplace_back(0, currency::null_pkey); + expected_prepared_outputs.reserve(outputs.size()); + expected_prepared_outputs.emplace_back(0, currency::null_pkey); + expected_prepared_outputs.emplace_back(currency::ref_by_id{}, currency::null_pkey); + ASSERT_EQ(outputs.size(), expected_prepared_outputs.size()); + ASSERT_EQ(currency::prepare_outputs_entries_for_key_offsets(outputs, 0, index), expected_prepared_outputs); + ASSERT_EQ(index, 1); +} + +TEST(prepare_outputs_for_key_offsets, subsequence_of_ref_by_id_references_is_not_changed) +{ + { + std::vector outputs{}, expected_prepared_outputs{}; + size_t index{}; + + outputs.reserve(6); + outputs.emplace_back(3, currency::null_pkey); + outputs.emplace_back(currency::ref_by_id{currency::null_hash, 1}, currency::null_pkey); + outputs.emplace_back(currency::ref_by_id{currency::null_hash, 0}, currency::null_pkey); + outputs.emplace_back(2, currency::null_pkey); + outputs.emplace_back(currency::ref_by_id{currency::null_hash, 3}, currency::null_pkey); + outputs.emplace_back(1, currency::null_pkey); + expected_prepared_outputs.reserve(outputs.size()); + expected_prepared_outputs.emplace_back(1, currency::null_pkey); + expected_prepared_outputs.emplace_back(1, currency::null_pkey); + expected_prepared_outputs.emplace_back(1, currency::null_pkey); + expected_prepared_outputs.emplace_back(currency::ref_by_id{currency::null_hash, 1}, currency::null_pkey); + expected_prepared_outputs.emplace_back(currency::ref_by_id{currency::null_hash, 0}, currency::null_pkey); + expected_prepared_outputs.emplace_back(currency::ref_by_id{currency::null_hash, 3}, currency::null_pkey); + ASSERT_EQ(outputs.size(), expected_prepared_outputs.size()); + ASSERT_EQ(currency::prepare_outputs_entries_for_key_offsets(outputs, 0, index), expected_prepared_outputs); + ASSERT_EQ(index, 2); + } + + { + std::vector outputs{}, expected_prepared_outputs{}; + size_t index{}; + + outputs.reserve(5); + outputs.emplace_back(currency::ref_by_id{currency::null_hash, 1}, currency::null_pkey); + outputs.emplace_back(currency::ref_by_id{currency::null_hash, 0}, currency::null_pkey); + outputs.emplace_back(currency::ref_by_id{currency::null_hash, 3}, currency::null_pkey); + outputs.emplace_back(1, currency::null_pkey); + outputs.emplace_back(0, currency::null_pkey); + expected_prepared_outputs.reserve(outputs.size()); + expected_prepared_outputs.emplace_back(0, currency::null_pkey); + expected_prepared_outputs.emplace_back(1, currency::null_pkey); + expected_prepared_outputs.emplace_back(currency::ref_by_id{currency::null_hash, 1}, currency::null_pkey); + expected_prepared_outputs.emplace_back(currency::ref_by_id{currency::null_hash, 0}, currency::null_pkey); + expected_prepared_outputs.emplace_back(currency::ref_by_id{currency::null_hash, 3}, currency::null_pkey); + ASSERT_EQ(outputs.size(), expected_prepared_outputs.size()); + ASSERT_EQ(currency::prepare_outputs_entries_for_key_offsets(outputs, 0, index), expected_prepared_outputs); + ASSERT_EQ(outputs.at(0), expected_prepared_outputs.at(2)); + ASSERT_EQ(index, 2); + } +} + +TEST(prepare_outputs_for_key_offsets, index_will_not_be_changed_if_old_index_is_size_max) +{ + std::vector outputs{}, expected_prepared_outputs{}; + auto index{crypto::rand()}; + const auto index_before_preparing_outputs{index}; + + outputs.reserve(5); + outputs.emplace_back(currency::ref_by_id{currency::null_hash, 1}, currency::null_pkey); + outputs.emplace_back(currency::ref_by_id{currency::null_hash, 0}, currency::null_pkey); + outputs.emplace_back(currency::ref_by_id{currency::null_hash, 3}, currency::null_pkey); + outputs.emplace_back(1, currency::null_pkey); + outputs.emplace_back(0, currency::null_pkey); + expected_prepared_outputs.reserve(outputs.size()); + expected_prepared_outputs.emplace_back(0, currency::null_pkey); + expected_prepared_outputs.emplace_back(1, currency::null_pkey); + expected_prepared_outputs.emplace_back(currency::ref_by_id{currency::null_hash, 1}, currency::null_pkey); + expected_prepared_outputs.emplace_back(currency::ref_by_id{currency::null_hash, 0}, currency::null_pkey); + expected_prepared_outputs.emplace_back(currency::ref_by_id{currency::null_hash, 3}, currency::null_pkey); + ASSERT_EQ(outputs.size(), expected_prepared_outputs.size()); + ASSERT_EQ(currency::prepare_outputs_entries_for_key_offsets(outputs, SIZE_MAX, index), expected_prepared_outputs); + ASSERT_EQ(index, index_before_preparing_outputs); +} From 0dd79e77c45f08c20ba42d215519d01b653e7713 Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 4 Nov 2024 19:46:43 +0100 Subject: [PATCH 089/106] removing backslash from allowed password symbols --- src/currency_core/currency_format_utils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/currency_core/currency_format_utils.cpp b/src/currency_core/currency_format_utils.cpp index 2910bc8e..8ce14334 100644 --- a/src/currency_core/currency_format_utils.cpp +++ b/src/currency_core/currency_format_utils.cpp @@ -3632,7 +3632,7 @@ namespace currency return true; } //------------------------------------------------------------------ - #define PASSWORD_REGEXP R"([A-Za-z0-9~!?@#$%^&*_+|{}\[\]()<>:;"'\-=\\/.,]{0,40})" + #define PASSWORD_REGEXP R"([A-Za-z0-9~!?@#$%^&*_+|{}\[\]()<>:;"'\-=/.,]{0,40})" bool validate_password(const std::string& password) { // OLD: static const std::string allowed_password_symbols = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~!?@#$%^&*_+|{}[]()<>:;\"'-=\\/.,"; From f74445d7fd878964f77725aad3f3ce394f9e9307 Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 4 Nov 2024 22:15:18 +0100 Subject: [PATCH 090/106] implemented password validation for seed phrases generation --- src/currency_core/account.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/currency_core/account.cpp b/src/currency_core/account.cpp index f5663826..464aaff6 100644 --- a/src/currency_core/account.cpp +++ b/src/currency_core/account.cpp @@ -86,6 +86,7 @@ namespace currency std::vector processed_seed_binary = keys_seed_binary; if (!password.empty()) { + CHECK_AND_ASSERT_THROW_MES(currency::validate_password(password), "seed phrase password contains invalid characters, seed phrase cannot be created with such a password"); //encrypt seed phrase binary data crypt_with_pass(&keys_seed_binary[0], keys_seed_binary.size(), &processed_seed_binary[0], password); } From c7e17edbc7f64138237779eefa401b53d861688b Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 4 Nov 2024 22:16:23 +0100 Subject: [PATCH 091/106] simplewallet: minor fix for error text --- src/simplewallet/simplewallet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 2bfdb102..9a5d07e2 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -593,7 +593,7 @@ bool simple_wallet::new_wallet(const string &wallet_file, const std::string& pas { if (!currency::validate_password(password)) { - fail_msg_writer() << R"(Provided password contains invalid characters. Only letters, numbers and ~!?@#$%^&*_+|{}[]()<>:;"'-=\/., symbols are allowed.)" << ENDL; + fail_msg_writer() << R"(Provided password contains invalid characters. Only letters, numbers and ~!?@#$%^&*_+|{}[]()<>:;"'-=/., symbols are allowed.)" << ENDL; return false; } From b963acc6dadd54f3e1f68a4dd97b225bb9fb8550 Mon Sep 17 00:00:00 2001 From: sowle Date: Tue, 5 Nov 2024 15:06:05 +0100 Subject: [PATCH 092/106] ui update --- src/gui/qt-daemon/layout | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/qt-daemon/layout b/src/gui/qt-daemon/layout index ca847ff0..ad7cc4a5 160000 --- a/src/gui/qt-daemon/layout +++ b/src/gui/qt-daemon/layout @@ -1 +1 @@ -Subproject commit ca847ff0d1563ed347a3ade5c37ff34ddcdb9c58 +Subproject commit ad7cc4a52f904284fbe086993ded78f5c85d355e From dca18f4963b88da86daf0a97cd69d26d410174f0 Mon Sep 17 00:00:00 2001 From: zano build machine Date: Tue, 5 Nov 2024 17:32:30 +0300 Subject: [PATCH 093/106] === build number: 359 -> 360 === --- src/version.h.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h.in b/src/version.h.in index 8b603934..db985d94 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -8,6 +8,6 @@ #define PROJECT_REVISION "0" #define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION -#define PROJECT_VERSION_BUILD_NO 359 +#define PROJECT_VERSION_BUILD_NO 360 #define PROJECT_VERSION_BUILD_NO_STR STRINGIFY_EXPAND(PROJECT_VERSION_BUILD_NO) #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO_STR "[" BUILD_COMMIT_ID "]" From ea394e872a406b7fc4a696a0bba12df0dfb78549 Mon Sep 17 00:00:00 2001 From: sowle Date: Wed, 6 Nov 2024 14:33:50 +0100 Subject: [PATCH 094/106] minor improvements here and there --- src/currency_core/currency_format_utils.cpp | 4 ++-- tests/core_tests/multiassets_test.cpp | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/currency_core/currency_format_utils.cpp b/src/currency_core/currency_format_utils.cpp index 8ce14334..96d55b46 100644 --- a/src/currency_core/currency_format_utils.cpp +++ b/src/currency_core/currency_format_utils.cpp @@ -2288,7 +2288,7 @@ namespace currency gen_context.ao_amount_blinding_mask = crypto::hash_helper_t::hs(CRYPTO_HDS_ASSET_CONTROL_ABM, tx_key.sec); gen_context.ao_commitment_in_outputs = true; - // set correct asset_id to the corresponding destination entries + // calculate the amount of asset being burnt using asset_id field in inputs and outputs (sources and destinations) uint64_t amount_of_burned_assets = 0; for (auto& item : ftp.sources) { @@ -2343,7 +2343,7 @@ namespace currency } if (ado.version < ASSET_DESCRIPTOR_BASE_HF5_VER) { - CHECK_AND_ASSERT_THROW_MES(ado.opt_descriptor.has_value(), "Internal error: opt_descriptor unset during ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN for version less then 2"); + CHECK_AND_ASSERT_THROW_MES(ado.opt_descriptor.has_value(), "Internal error: opt_descriptor unset during ASSET_DESCRIPTOR_OPERATION_EMIT for version less then 2"); ado.opt_descriptor->current_supply += amount_of_emitted_asset; } else diff --git a/tests/core_tests/multiassets_test.cpp b/tests/core_tests/multiassets_test.cpp index bef240c0..c7b13844 100644 --- a/tests/core_tests/multiassets_test.cpp +++ b/tests/core_tests/multiassets_test.cpp @@ -1413,7 +1413,6 @@ bool eth_signed_asset_basics::generate(std::vector& events) co m_accounts.resize(TOTAL_ACCS_COUNT); account_base& miner_acc = m_accounts[MINER_ACC_IDX]; miner_acc.generate(); miner_acc.set_createtime(ts); account_base& alice_acc = m_accounts[ALICE_ACC_IDX]; alice_acc.generate(); alice_acc.set_createtime(ts); - miner_acc.generate(); MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, ts); DO_CALLBACK(events, "configure_core"); // default configure_core callback will initialize core runtime config with m_hardforks @@ -1627,7 +1626,6 @@ bool eth_signed_asset_via_rpc::generate(std::vector& events) c account_base& miner_acc = m_accounts[MINER_ACC_IDX]; miner_acc.generate(); miner_acc.set_createtime(ts); account_base& alice_acc = m_accounts[ALICE_ACC_IDX]; alice_acc.generate(); alice_acc.set_createtime(ts); account_base& bob_acc = m_accounts[BOB_ACC_IDX]; bob_acc.generate(); bob_acc.set_createtime(ts); - miner_acc.generate(); MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, ts); DO_CALLBACK(events, "configure_core"); // default configure_core callback will initialize core runtime config with m_hardforks @@ -2420,7 +2418,6 @@ bool several_asset_emit_burn_txs_in_pool::generate(std::vector m_accounts.resize(TOTAL_ACCS_COUNT); account_base& miner_acc = m_accounts[MINER_ACC_IDX]; miner_acc.generate(); miner_acc.set_createtime(ts); account_base& alice_acc = m_accounts[ALICE_ACC_IDX]; alice_acc.generate(); alice_acc.set_createtime(ts); - miner_acc.generate(); MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, ts); // rebuild genesis miner tx From 45cd764e800a73d90a29c3409772659baddb7867 Mon Sep 17 00:00:00 2001 From: sowle Date: Wed, 6 Nov 2024 15:49:58 +0100 Subject: [PATCH 095/106] ui update --- src/gui/qt-daemon/layout | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/qt-daemon/layout b/src/gui/qt-daemon/layout index ad7cc4a5..2feec9ac 160000 --- a/src/gui/qt-daemon/layout +++ b/src/gui/qt-daemon/layout @@ -1 +1 @@ -Subproject commit ad7cc4a52f904284fbe086993ded78f5c85d355e +Subproject commit 2feec9ac7f9d69bc90e900cd2416f311cd4eb877 From 23cd004f36e5ac7103c695965efe8e61a91612b9 Mon Sep 17 00:00:00 2001 From: zano build machine Date: Wed, 6 Nov 2024 18:17:20 +0300 Subject: [PATCH 096/106] === build number: 360 -> 361 === --- src/version.h.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h.in b/src/version.h.in index db985d94..3cdf51c8 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -8,6 +8,6 @@ #define PROJECT_REVISION "0" #define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION -#define PROJECT_VERSION_BUILD_NO 360 +#define PROJECT_VERSION_BUILD_NO 361 #define PROJECT_VERSION_BUILD_NO_STR STRINGIFY_EXPAND(PROJECT_VERSION_BUILD_NO) #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO_STR "[" BUILD_COMMIT_ID "]" From fa35d54fbb8bc49b7bb622dacaa907cbd04488b6 Mon Sep 17 00:00:00 2001 From: sowle Date: Thu, 7 Nov 2024 14:00:56 +0100 Subject: [PATCH 097/106] ui update --- src/gui/qt-daemon/layout | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/qt-daemon/layout b/src/gui/qt-daemon/layout index 2feec9ac..7cd0e5e5 160000 --- a/src/gui/qt-daemon/layout +++ b/src/gui/qt-daemon/layout @@ -1 +1 @@ -Subproject commit 2feec9ac7f9d69bc90e900cd2416f311cd4eb877 +Subproject commit 7cd0e5e54a0d692ea819b9653f60a1cd5512dc2b From 41d87d3e2732aa94f10ad58f10b35acd6b73d01e Mon Sep 17 00:00:00 2001 From: zano build machine Date: Thu, 7 Nov 2024 16:17:45 +0300 Subject: [PATCH 098/106] === build number: 361 -> 362 === --- src/version.h.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h.in b/src/version.h.in index 3cdf51c8..c8aed850 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -8,6 +8,6 @@ #define PROJECT_REVISION "0" #define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION -#define PROJECT_VERSION_BUILD_NO 361 +#define PROJECT_VERSION_BUILD_NO 362 #define PROJECT_VERSION_BUILD_NO_STR STRINGIFY_EXPAND(PROJECT_VERSION_BUILD_NO) #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO_STR "[" BUILD_COMMIT_ID "]" From 7b2388290c94c3d72f4189a0a38fe8c38854658a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=D1=91pa=20Dolgorukov?= <63650851+stepan-dolgorukov@users.noreply.github.com> Date: Thu, 7 Nov 2024 20:13:58 +0500 Subject: [PATCH 099/106] unit_tests: Test the function "check_tx_inputs_keyimages_diff" on different type inputs in the "tx_pool_semantic_validation" test (#478) --- tests/core_tests/tx_validation.cpp | 95 +++++++++++++++++------------- 1 file changed, 54 insertions(+), 41 deletions(-) diff --git a/tests/core_tests/tx_validation.cpp b/tests/core_tests/tx_validation.cpp index 138f8746..14b3db62 100644 --- a/tests/core_tests/tx_validation.cpp +++ b/tests/core_tests/tx_validation.cpp @@ -1711,7 +1711,6 @@ bool tx_pool_semantic_validation::generate(std::vector& events MAKE_GENESIS_BLOCK(events, blk_0, miner, test_core_time::get_time()); DO_CALLBACK(events, "configure_core"); - CHECK_AND_ASSERT_EQ(validate_tx_semantic(transaction{}, CURRENCY_MAX_TRANSACTION_BLOB_SIZE), false); // No inputs. @@ -1798,30 +1797,42 @@ bool tx_pool_semantic_validation::generate(std::vector& events // Equal key images in inputs. { - tx_out_bare output{}; transaction tx{}; - std::array inputs{}; - point_t key_image_point{}; - output.amount = 1; - tx.vout.push_back(output); - CHECK_AND_ASSERT_EQ(key_image_point.from_string("8fc7cbfd1054690767d0c20917a68371b34b190aac5997581641f064b93d1b96"), true); - - for (int position{}; position < 2; ++position) { - auto& input{inputs.at(position)}; + txin_zc_input input_zc{}; + txin_htlc input_htlc{}; + txin_to_key input_to_key{}; + point_t point_key_image{}; - input.k_image = key_image_point.to_key_image(); + CHECK_AND_ASSERT_EQ(point_key_image.from_string("93fa59f43fb9cff98e6867d20cf200c98b29cae406acdbde798ffb3e30d3503a"), true); + input_zc.k_image = point_key_image.to_key_image(); + CHECK_AND_ASSERT_EQ(point_key_image.from_string("ad1226e3fd1be15e26b119fa80380e580a498e5fa3421b63fded89672b526a44"), true); + input_htlc.k_image = point_key_image.to_key_image(); + CHECK_AND_ASSERT_EQ(point_key_image.from_string("8fc7cbfd1054690767d0c20917a68371b34b190aac5997581641f064b93d1b96"), true); + input_to_key.k_image = point_key_image.to_key_image(); + tx.vin.push_back(input_zc); + tx.vin.push_back(input_htlc); + tx.vin.push_back(input_to_key); + } + + { + txin_to_key input{}; + + input.amount = 0; tx.vin.push_back(input); } - CHECK_AND_ASSERT_EQ(tx.vin.at(0).type(), typeid(txin_to_key)); - CHECK_AND_ASSERT_EQ(tx.vin.at(0).type(), tx.vin.at(1).type()); - CHECK_AND_ASSERT_EQ(boost::get(tx.vin.at(0)).k_image, boost::get(tx.vin.at(1)).k_image); - CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false); + for (int8_t step{}; step < 3; ++step) + { + tx.vin.push_back(tx.vin.front()); + CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false); + DO_CALLBACK(events, "mark_invalid_tx"); + ADD_CUSTOM_EVENT(events, tx); + tx.vin.erase(tx.vin.begin(), tx.vin.begin() + 1); + } - DO_CALLBACK(events, "mark_invalid_tx"); - ADD_CUSTOM_EVENT(events, tx); + CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), true); } // Two entries of the same type in extra. @@ -1998,33 +2009,35 @@ bool tx_pool_semantic_validation::generate(std::vector& events // Equal key images in inputs. { - tx_out_bare output{}; - std::array inputs{}; - point_t key_image_point{}; MAKE_TX_FEE(events, tx, miner, miner, MK_TEST_COINS(2), TESTS_DEFAULT_FEE, blk_0r); + { + txin_zc_input input_zc{}; + txin_htlc input_htlc{}; + txin_to_key input_to_key{}; + point_t point_key_image{}; + + CHECK_AND_ASSERT_EQ(point_key_image.from_string("93fa59f43fb9cff98e6867d20cf200c98b29cae406acdbde798ffb3e30d3503a"), true); + input_zc.k_image = point_key_image.to_key_image(); + CHECK_AND_ASSERT_EQ(point_key_image.from_string("ad1226e3fd1be15e26b119fa80380e580a498e5fa3421b63fded89672b526a44"), true); + input_htlc.k_image = point_key_image.to_key_image(); + CHECK_AND_ASSERT_EQ(point_key_image.from_string("8fc7cbfd1054690767d0c20917a68371b34b190aac5997581641f064b93d1b96"), true); + input_to_key.k_image = point_key_image.to_key_image(); + tx.vin.push_back(input_zc); + tx.vin.push_back(input_htlc); + tx.vin.push_back(input_to_key); + } + + for (int8_t step{}; step < 3; ++step) + { + tx.vin.push_back(tx.vin.front()); + CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false); + DO_CALLBACK(events, "mark_invalid_tx"); + ADD_CUSTOM_EVENT(events, tx); + tx.vin.erase(tx.vin.begin(), tx.vin.begin() + 1); + } + CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), true); - output.amount = 1; - tx.vout.push_back(output); - CHECK_AND_ASSERT_EQ(key_image_point.from_string("8fc7cbfd1054690767d0c20917a68371b34b190aac5997581641f064b93d1b96"), true); - - for (int position{}; position < 2; ++position) - { - inputs.at(position).k_image = key_image_point.to_key_image(); - tx.vin.push_back(inputs.at(position)); - } - - { - const auto& input_preceding_last{tx.vin.at(tx.vin.size() - 2u)}; - - CHECK_AND_ASSERT_EQ(tx.vin.back().type(), typeid(txin_to_key)); - CHECK_AND_ASSERT_EQ(tx.vin.back().type(), input_preceding_last.type()); - CHECK_AND_ASSERT_EQ(boost::get(tx.vin.back()).k_image, boost::get(input_preceding_last).k_image); - } - - CHECK_AND_ASSERT_EQ(validate_tx_semantic(tx, CURRENCY_MAX_TRANSACTION_BLOB_SIZE - 1), false); - DO_CALLBACK(events, "mark_invalid_tx"); - ADD_CUSTOM_EVENT(events, tx); } // Two entries of the same type in extra. From fead9075b6cdaec15e70bf77d9c1d5654e781340 Mon Sep 17 00:00:00 2001 From: sowle Date: Sat, 9 Nov 2024 19:42:15 +0100 Subject: [PATCH 100/106] wallet: minor improvements (override keyword) --- src/wallet/wallet_id_adapter.h | 23 ++++++++++++++--------- src/wallet/wallets_manager.h | 6 +++--- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/wallet/wallet_id_adapter.h b/src/wallet/wallet_id_adapter.h index 73b3627b..f13ce0c7 100644 --- a/src/wallet/wallet_id_adapter.h +++ b/src/wallet/wallet_id_adapter.h @@ -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 Boolberry developers // Distributed under the MIT/X11 software license, see the accompanying @@ -29,31 +29,36 @@ struct i_wallet_to_i_backend_adapter: public tools::i_wallet2_callback m_wallet_id(wallet_id) {} - virtual void on_new_block(uint64_t height, const currency::block& block) { + virtual void on_new_block(uint64_t height, const currency::block& block) override + { m_pbackend->on_new_block(m_wallet_id, height, block); } - virtual void on_transfer2(const tools::wallet_public::wallet_transfer_info& wti, const std::list& balances, uint64_t total_mined) { + virtual void on_transfer2(const tools::wallet_public::wallet_transfer_info& wti, const std::list& balances, uint64_t total_mined) override + { m_pbackend->on_transfer2(m_wallet_id, wti, balances, total_mined); } - virtual void on_pos_block_found(const currency::block& wti) { + virtual void on_pos_block_found(const currency::block& wti) override + { m_pbackend->on_pos_block_found(m_wallet_id, wti); } - virtual void on_sync_progress(const uint64_t& progress) { + virtual void on_sync_progress(const uint64_t& progress) override + { m_pbackend->on_sync_progress(m_wallet_id, progress); } - virtual void on_transfer_canceled(const tools::wallet_public::wallet_transfer_info& wti) { + virtual void on_transfer_canceled(const tools::wallet_public::wallet_transfer_info& wti) override + { m_pbackend->on_transfer_canceled(m_wallet_id, wti); } - virtual void on_tor_status_change(const std::string& state) + virtual void on_tor_status_change(const std::string& state) override { m_pbackend->on_tor_status_change(m_wallet_id, state); } - virtual void on_mw_get_wallets(std::vector& wallets) + virtual void on_mw_get_wallets(std::vector& wallets) override { m_pbackend->on_mw_get_wallets(wallets); } - virtual bool on_mw_select_wallet(uint64_t wallet_id) + virtual bool on_mw_select_wallet(uint64_t wallet_id) override { return m_pbackend->on_mw_select_wallet(wallet_id); } diff --git a/src/wallet/wallets_manager.h b/src/wallet/wallets_manager.h index 6fa14328..76a8191e 100644 --- a/src/wallet/wallets_manager.h +++ b/src/wallet/wallets_manager.h @@ -207,10 +207,10 @@ private: virtual bool on_mw_select_wallet(uint64_t wallet_id) override; //----- i_wallet_provider ------ - virtual void lock(); - virtual void unlock(); + virtual void lock() override; + virtual void unlock() override; //#ifndef MOBILE_WALLET_BUILD - virtual std::shared_ptr get_wallet(); + virtual std::shared_ptr get_wallet() override; //#endif //-------- From d28a0e1b13903869efb81d2844a5cce1b410a295 Mon Sep 17 00:00:00 2001 From: sowle Date: Sat, 9 Nov 2024 19:44:28 +0100 Subject: [PATCH 101/106] wallet: minor improvements (override keyword) 2 --- src/wallet/wallet_rpc_server.h | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 2f50e99c..a424e103 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -57,7 +57,9 @@ namespace tools { wallet_provider_simple(std::shared_ptr wallet_ptr) : m_wallet_ptr(wallet_ptr) {} - virtual std::shared_ptr get_wallet() + + // interface i_wallet_provider + virtual std::shared_ptr get_wallet() override { return m_wallet_ptr; } @@ -90,13 +92,16 @@ namespace tools bool run(bool do_mint, bool offline_mode, const currency::account_public_address& miner_address); virtual bool handle_http_request(const epee::net_utils::http::http_request_info& query_info, epee::net_utils::http::http_response_info& response_info, - connection_context& conn_context) { + connection_context& conn_context) + { bool call_found = false; return this->handle_http_request(query_info, response_info, conn_context, call_found, epee::net_utils::http::i_chain_handler::m_empty_documentation); } + // interface i_chain_handler virtual bool handle_http_request(const epee::net_utils::http::http_request_info& query_info, epee::net_utils::http::http_response_info& response_info, - epee::net_utils::connection_context_base& conn_context, bool& call_found, documentation& docs = epee::net_utils::http::i_chain_handler::m_empty_documentation); + epee::net_utils::connection_context_base& conn_context, bool& call_found, documentation& docs = epee::net_utils::http::i_chain_handler::m_empty_documentation) override; + void set_jwt_secret(const std::string& jwt); const std::string& get_jwt_secret(); From 74ef510288c3bbdf0a044a66e761b2270f514234 Mon Sep 17 00:00:00 2001 From: sowle Date: Sat, 9 Nov 2024 19:46:51 +0100 Subject: [PATCH 102/106] wallets_manager: wallet callback lifetime management fixed --- src/wallet/wallets_manager.cpp | 4 +++- src/wallet/wallets_manager.h | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/wallet/wallets_manager.cpp b/src/wallet/wallets_manager.cpp index 426aff27..8859ec8d 100644 --- a/src/wallet/wallets_manager.cpp +++ b/src/wallet/wallets_manager.cpp @@ -1051,7 +1051,8 @@ std::string wallets_manager::open_wallet(const std::wstring& path, const std::st w->set_use_deffered_global_outputs(m_use_deffered_global_outputs); owr.wallet_id = m_wallet_id_counter++; - w->callback(std::shared_ptr(new i_wallet_to_i_backend_adapter(this, owr.wallet_id))); + std::shared_ptr w_cb{new i_wallet_to_i_backend_adapter(this, owr.wallet_id)}; + w->callback(w_cb); if (m_remote_node_mode) { w->set_core_proxy(m_rpc_proxy); @@ -1112,6 +1113,7 @@ std::string wallets_manager::open_wallet(const std::wstring& path, const std::st EXCLUSIVE_CRITICAL_REGION_LOCAL(m_wallets_lock); wallet_vs_options& wo = m_wallets[owr.wallet_id]; **wo.w = w; + wo.w_cb = w_cb; owr.wallet_file_size = w->get_wallet_file_size(); get_wallet_info(wo, owr.wi); init_wallet_entry(wo, owr.wallet_id); diff --git a/src/wallet/wallets_manager.h b/src/wallet/wallets_manager.h index 76a8191e..4e7f73a0 100644 --- a/src/wallet/wallets_manager.h +++ b/src/wallet/wallets_manager.h @@ -55,6 +55,7 @@ public: { currency::core_runtime_config core_conf; epee::locked_object, wallet_lock_time_watching_policy> w; + std::shared_ptr w_cb; // not using locked_object here, cuz w_cb is accessed only via it's wallet -- sowle typedef epee::locked_object, wallet_lock_time_watching_policy>::lock_shared_ptr wallet_lock_object; std::shared_ptr rpc_wrapper; //500 bytes of extra data, we can afford it, to have rpc-like invoke map std::atomic do_mining; From 85c2591cfc82854b28d52b4a308c8c761cb72268 Mon Sep 17 00:00:00 2001 From: sowle Date: Sat, 9 Nov 2024 19:47:46 +0100 Subject: [PATCH 103/106] account: fixed rare checksum oob issue --- src/currency_core/account.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/currency_core/account.cpp b/src/currency_core/account.cpp index 464aaff6..ff194a59 100644 --- a/src/currency_core/account.cpp +++ b/src/currency_core/account.cpp @@ -108,6 +108,9 @@ namespace currency uint64_t h_64 = *reinterpret_cast(&h); uint16_t checksum = h_64 % (checksum_max + 1); + if (checksum == checksum_max) // workaround for incorrect checksum calculation (trying to keep the whole scheme untouched) -- sowle + checksum = 0; + uint8_t auditable_flag = 0; if (m_keys.account_address.flags & ACCOUNT_PUBLIC_ADDRESS_FLAG_AUDITABLE) auditable_flag = 1; @@ -216,6 +219,10 @@ namespace currency h = crypto::cn_fast_hash(&h, sizeof h); uint64_t h_64 = *reinterpret_cast(&h); uint16_t checksum_calculated = h_64 % (checksum_max + 1); + + if (checksum_calculated == checksum_max) // workaround for incorrect checksum calculation (trying to keep the whole scheme untouched) -- sowle + checksum_calculated = 0; + if (checksum != checksum_calculated) { LOG_PRINT_L0("seed phase has invalid checksum: " << checksum_calculated << ", while " << checksum << " is expected, check your words"); @@ -230,6 +237,8 @@ namespace currency if (auditable_flag) m_keys.account_address.flags |= ACCOUNT_PUBLIC_ADDRESS_FLAG_AUDITABLE; + else + m_keys.account_address.flags &= ~ACCOUNT_PUBLIC_ADDRESS_FLAG_AUDITABLE; return true; } From 912392bee7c446af4ba587338ee668ee3fa2cce2 Mon Sep 17 00:00:00 2001 From: sowle Date: Sat, 9 Nov 2024 23:29:31 +0100 Subject: [PATCH 104/106] === build number: 364 -> 365 === --- src/version.h.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h.in b/src/version.h.in index c8aed850..ca3de875 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -8,6 +8,6 @@ #define PROJECT_REVISION "0" #define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION -#define PROJECT_VERSION_BUILD_NO 362 +#define PROJECT_VERSION_BUILD_NO 365 #define PROJECT_VERSION_BUILD_NO_STR STRINGIFY_EXPAND(PROJECT_VERSION_BUILD_NO) #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO_STR "[" BUILD_COMMIT_ID "]" From 2b249ef414336f5f9f624af5139796be00ad0668 Mon Sep 17 00:00:00 2001 From: sowle Date: Sun, 10 Nov 2024 22:18:01 +0100 Subject: [PATCH 105/106] unit_tests: wallet_seed.basic_test fixed to reflect recent changes in password limitations --- tests/unit_tests/wallet_seed_test.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/tests/unit_tests/wallet_seed_test.cpp b/tests/unit_tests/wallet_seed_test.cpp index cfd6b860..b798b7c3 100644 --- a/tests/unit_tests/wallet_seed_test.cpp +++ b/tests/unit_tests/wallet_seed_test.cpp @@ -158,17 +158,18 @@ TEST(wallet_seed, basic_test) { //generate random password std::string pass = epee::string_tools::pod_to_hex(crypto::cn_fast_hash(&j, sizeof(j))); - if (j!= 0 && j < 64) - { - pass.resize(j); - } + pass.resize(std::min(j, (size_t)40)); + //get secured seed std::string secured_seed = acc.get_seed_phrase(pass); - //try to restore it without password(should fail) - currency::account_base acc2; - bool r_fail = acc2.restore_from_seed_phrase(secured_seed, ""); - ASSERT_EQ(r_fail, false); + currency::account_base acc2{}; + if (!pass.empty()) + { + //try to restore it without password(should fail) + bool r_fail = acc2.restore_from_seed_phrase(secured_seed, ""); + ASSERT_EQ(r_fail, false); + } //try to restore it with wrong password bool r_fake_pass = acc2.restore_from_seed_phrase(secured_seed, "fake_password"); @@ -182,7 +183,8 @@ TEST(wallet_seed, basic_test) currency::account_base acc3; bool r_true_res = acc3.restore_from_seed_phrase(secured_seed, pass); ASSERT_EQ(true, r_true_res); - ASSERT_EQ(true, acc3.get_keys() == acc.get_keys()); + r = acc3.get_keys() == acc.get_keys(); + ASSERT_TRUE(r); } } From 099f94075140d25e996912cb919df1d2a1482570 Mon Sep 17 00:00:00 2001 From: sowle Date: Sun, 10 Nov 2024 22:51:07 +0100 Subject: [PATCH 106/106] === set version to 2.0.1.366 === --- src/version.h.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/version.h.in b/src/version.h.in index ca3de875..09597591 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -4,10 +4,10 @@ #define BUILD_COMMIT_ID "@VERSION@" #define PROJECT_MAJOR_VERSION "2" -#define PROJECT_MINOR_VERSION "1" -#define PROJECT_REVISION "0" +#define PROJECT_MINOR_VERSION "0" +#define PROJECT_REVISION "1" #define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION -#define PROJECT_VERSION_BUILD_NO 365 +#define PROJECT_VERSION_BUILD_NO 366 #define PROJECT_VERSION_BUILD_NO_STR STRINGIFY_EXPAND(PROJECT_VERSION_BUILD_NO) #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO_STR "[" BUILD_COMMIT_ID "]"