diff --git a/src/common/error_codes.h b/src/common/error_codes.h index 7b77fec3..313a9aad 100644 --- a/src/common/error_codes.h +++ b/src/common/error_codes.h @@ -40,4 +40,5 @@ #define API_RETURN_CODE_TX_IS_TOO_BIG "TX_IS_TOO_BIG" #define API_RETURN_CODE_TX_REJECTED "TX_REJECTED" #define API_RETURN_CODE_HTLC_ORIGIN_HASH_MISSMATCHED "HTLC_ORIGIN_HASH_MISSMATCHED" -#define API_RETURN_CODE_WRAP "WRAP" \ No newline at end of file +#define API_RETURN_CODE_WRAP "WRAP" +#define API_RETURN_CODE_MISSING_ZC_INPUTS "MISSING_ZC_INPUTS" \ No newline at end of file diff --git a/src/currency_core/currency_format_utils.cpp b/src/currency_core/currency_format_utils.cpp index 8d5f0b37..c323d7d5 100644 --- a/src/currency_core/currency_format_utils.cpp +++ b/src/currency_core/currency_format_utils.cpp @@ -2096,7 +2096,7 @@ namespace currency } } - + size_t thirdparty_zc_inputs_count = zc_inputs_count; // // INs // @@ -2284,7 +2284,7 @@ namespace currency for(size_t destination_index = 0; destination_index < shuffled_dsts.size(); ++destination_index, ++output_index) { tx_destination_entry& dst_entr = shuffled_dsts[destination_index]; - if (all_inputs_are_obviously_native_coins && gen_context.ao_asset_id == currency::null_pkey) + if (!append_mode && all_inputs_are_obviously_native_coins && gen_context.ao_asset_id == currency::null_pkey) dst_entr.flags |= tx_destination_entry_flags::tdef_explicit_native_asset_id; // all inputs are obviously native coins -- all outputs must have explicit asset ids (unless there's an asset emission) CHECK_AND_ASSERT_MES(dst_entr.amount > 0, false, "Destination with wrong amount: " << dst_entr.amount); // <<-- TODO @#@# consider removing this check @@ -2376,6 +2376,7 @@ namespace currency // ring signatures (per-input proofs) r = false; + size_t curren_zc_index = thirdparty_zc_inputs_count; for (size_t i_ = 0; i_ != sources.size(); i_++) { size_t i_mapped = inputs_mapping[i_]; @@ -2390,7 +2391,8 @@ namespace currency // blinding_masks_sum is supposed to be sum(mask of all tx output) - sum(masks of all pseudo out commitments) r = generate_ZC_sig(tx_hash_for_signature, i_ + input_starter_index, source_entry, in_contexts[i_mapped], sender_account_keys, flags, gen_context, tx, i_ + 1 == sources.size()); CHECK_AND_ASSERT_MES(r, false, "generate_ZC_sigs failed"); - gen_context.input_amounts[i_ + input_starter_index] = source_entry.amount; + gen_context.zc_input_amounts[curren_zc_index] = source_entry.amount; + curren_zc_index++; } else { diff --git a/src/currency_core/currency_format_utils_transactions.h b/src/currency_core/currency_format_utils_transactions.h index 24600d5f..e4f16d1f 100644 --- a/src/currency_core/currency_format_utils_transactions.h +++ b/src/currency_core/currency_format_utils_transactions.h @@ -225,7 +225,7 @@ namespace currency asset_id_blinding_masks.resize(outs_count); amounts.resize(outs_count); amount_blinding_masks.resize(outs_count); - input_amounts.resize(zc_ins_count); + zc_input_amounts.resize(zc_ins_count); } // TODO @#@# reconsider this check -- sowle @@ -253,7 +253,7 @@ namespace currency std::vector pseudo_outs_blinded_asset_ids; // generate_asset_surjection_proof crypto::scalar_vec_t pseudo_outs_plus_real_out_blinding_masks; // r_pi + r'_j // generate_asset_surjection_proof std::vector real_zc_ins_asset_ids; // H_i // generate_asset_surjection_proof - std::vector input_amounts; // all inputs, including non ZC + std::vector zc_input_amounts; // ZC only input amounts // common data: inputs crypto::point_t pseudo_out_amount_commitments_sum = crypto::c_point_0; // generate_tx_balance_proof generate_ZC_sig @@ -282,7 +282,7 @@ namespace currency KV_SERIALIZE_CONTAINER_POD_AS_BLOB(pseudo_outs_blinded_asset_ids) KV_SERIALIZE_CONTAINER_POD_AS_BLOB(pseudo_outs_plus_real_out_blinding_masks) KV_SERIALIZE_CONTAINER_POD_AS_BLOB(real_zc_ins_asset_ids) - KV_SERIALIZE(input_amounts) + KV_SERIALIZE(zc_input_amounts) KV_SERIALIZE_POD_AS_HEX_STRING(pseudo_out_amount_commitments_sum) KV_SERIALIZE_POD_AS_HEX_STRING(pseudo_out_amount_blinding_masks_sum) KV_SERIALIZE_POD_AS_HEX_STRING(real_in_asset_id_blinding_mask_x_amount_sum) @@ -308,7 +308,7 @@ namespace currency FIELD(pseudo_outs_blinded_asset_ids) FIELD((std::vector&)(pseudo_outs_plus_real_out_blinding_masks)) FIELD(real_zc_ins_asset_ids) - FIELD(input_amounts) + FIELD(zc_input_amounts) FIELD(pseudo_out_amount_commitments_sum) FIELD(pseudo_out_amount_blinding_masks_sum) FIELD(real_in_asset_id_blinding_mask_x_amount_sum) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index e57a0b0b..54226571 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -42,6 +42,8 @@ using namespace epee; using namespace currency; +#define SET_CONTEXT_OBJ_FOR_SCOPE(name, obj) m_current_context.name = &obj; \ + auto COMBINE(auto_scope_var_, __LINE__) = epee::misc_utils::create_scope_leave_handler([&]() { m_current_context.name = nullptr; }); #define MINIMUM_REQUIRED_WALLET_FREE_SPACE_BYTES (100*1024*1024) // 100 MB @@ -5139,7 +5141,7 @@ bool wallet2::get_ionic_swap_proposal_info(const wallet_public::ionic_swap_propo 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.input_amounts[zc_current_index]; + amount = ionic_context.gen_context.zc_input_amounts[zc_current_index]; zc_current_index++; mx = boost::get(tx.vin[i]).key_offsets.size() - 1; } @@ -5280,6 +5282,8 @@ bool wallet2::accept_ionic_swap_proposal(const wallet_public::ionic_swap_proposa ftp.gen_context = ionic_context.gen_context; prepare_transaction(construct_param, ftp, msc); + + try { finalize_transaction(ftp, result_tx, one_time_key, true); @@ -5895,6 +5899,38 @@ bool wallet2::get_actual_offers(std::list& 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[*it_in_amount].is_zc()) + { + 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, "Missing ZC inputs for TX_FLAG_SIGNATURE_MODE_SEPARATE operation"); + return true; +} +//---------------------------------------------------------------------------------------------------- bool wallet2::select_indices_for_transfer(assets_selection_context& needed_money_map, uint64_t fake_outputs_count, std::vector& selected_indexes) { bool res = true; @@ -5906,6 +5942,24 @@ bool wallet2::select_indices_for_transfer(assets_selection_context& needed_money item.second.found_amount = select_indices_for_transfer(selected_indexes, asset_cashe_it->second, item.second.needed_amount, fake_outputs_count); WLT_THROW_IF_FALSE_WALLET_EX_MES(item.second.found_amount >= item.second.needed_amount, error::not_enough_money, "", item.second.found_amount, item.second.needed_amount, 0, item.first); } + 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; + break; + } + } + if (!found_zc_input) + { + expand_selection_with_zc_input(needed_money_map, fake_outputs_count, selected_indexes); + } + } + + return res; } //---------------------------------------------------------------------------------------------------- @@ -5913,6 +5967,7 @@ uint64_t wallet2::select_indices_for_transfer(std::vector& selected_in { WLT_LOG_GREEN("Selecting indices for transfer of " << print_money_brief(needed_money) << " with " << fake_outputs_count << " fake outs, found_free_amounts.size()=" << found_free_amounts.size() << "...", LOG_LEVEL_0); uint64_t found_money = 0; + //uint64_t found_zc_input = false; std::string selected_amounts_str; while (found_money < needed_money && found_free_amounts.size()) { @@ -5934,6 +5989,7 @@ uint64_t wallet2::select_indices_for_transfer(std::vector& selected_in found_free_amounts.erase(it); } + WLT_LOG_GREEN("Found " << print_money_brief(found_money) << " as " << selected_indexes.size() << " out(s): " << selected_amounts_str << ", found_free_amounts.size()=" << found_free_amounts.size(), LOG_LEVEL_0); return found_money; } @@ -6057,11 +6113,16 @@ bool wallet2::read_money_transfer2_details_from_tx(const transaction& tx, const else if (i.type() == typeid(currency::txin_zc_input)) { const currency::txin_zc_input& in_zc = boost::get(i); - auto it = m_key_images.find(in_zc.k_image); //should we panic if image not found? - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it != m_key_images.end(), "[read_money_transfer2_details_from_tx]Unknown key image in tx: " << get_transaction_hash(tx)); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it->second < m_transfers.size(), "[read_money_transfer2_details_from_tx]Index out of range for key image in tx: " << get_transaction_hash(tx)); - wtd.spn.push_back(m_transfers[it->second].amount()); + //@zoidberg: nope! + if (m_key_images.count(in_zc.k_image)) + { + auto it = m_key_images.find(in_zc.k_image); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it != m_key_images.end(), "[read_money_transfer2_details_from_tx]Unknown key image in tx: " << get_transaction_hash(tx)); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it->second < m_transfers.size(), "[read_money_transfer2_details_from_tx]Index out of range for key image in tx: " << get_transaction_hash(tx)); + wtd.spn.push_back(m_transfers[it->second].amount()); + } + } } return true; @@ -6314,14 +6375,19 @@ void wallet2::prepare_tx_destinations(uint64_t needed_money, //---------------------------------------------------------------------------------------------------- void wallet2::prepare_transaction(construct_tx_param& ctp, currency::finalize_tx_param& ftp, const mode_separate_context& mode_separatemode_separate) { + + 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, mode_separatemode_separate); + TIME_MEASURE_START_MS(get_needed_money_time); + const currency::transaction& tx_for_mode_separate = mode_separatemode_separate.tx_for_mode_separate; assets_selection_context needed_money_map = get_needed_money(ctp.fee, ctp.dsts); // // 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"); @@ -6329,6 +6395,7 @@ void wallet2::prepare_transaction(construct_tx_param& ctp, currency::finalize_tx { needed_money_map[el.asset_id].needed_amount += el.amount; } + ctp.need_at_least_1_zc = true; } TIME_MEASURE_FINISH_MS(get_needed_money_time); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 4bbe3302..b09fd5e8 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -300,6 +300,7 @@ namespace tools uint8_t tx_outs_attr = 0; bool shuffle = false; bool perform_packing = false; + bool need_at_least_1_zc = false; }; struct mode_separate_context @@ -469,6 +470,21 @@ namespace tools crypto::hash related_tx_id = currency::null_hash; // tx id which caused money lock, if any (ex: escrow proposal transport tx) }; + /* + This might be not the best solution so far, but after discussion with @sowle we came up to conclusion + that passing great variety of arguments through the long call stack of different member functions of the wallet will + only complicate codebase and make it harder to understand. + current_operation_context will keep pointers to some useful data, and every function that use it, should + make sure(!!!) that pointer got nulled before pointed object got destroyed, likely by using SET_CONTEXT_OBJ_FOR_SCOPE macro + + */ + struct current_operation_context + { + construct_tx_param* pconstruct_tx_param = nullptr; + currency::finalize_tx_param* pfinalize_tx_param = nullptr; + const mode_separate_context* pmode_separate_context = nullptr; + }; + typedef std::unordered_multimap payment_container; @@ -1109,7 +1125,7 @@ private: bool is_in_hardfork_zone(uint64_t hardfork_index) const; uint8_t out_get_mixin_attr(const currency::tx_out_v& out_t); const crypto::public_key& out_get_pub_key(const currency::tx_out_v& out_t, std::list& htlc_info_list); - + bool expand_selection_with_zc_input(assets_selection_context& needed_money_map, uint64_t fake_outputs_count, std::vector& selected_indexes); void push_alias_info_to_extra_according_to_hf_status(const currency::extra_alias_entry& ai, std::vector& extra); void remove_transfer_from_amount_gindex_map(uint64_t tid); @@ -1174,6 +1190,8 @@ private: mutable uint64_t m_current_wallet_file_size; bool m_use_deffered_global_outputs; bool m_disable_tor_relay; + + mutable current_operation_context m_current_context; //this needed to access wallets state in coretests, for creating abnormal blocks and tranmsactions friend class test_generator; diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index 3d118c92..9e8426c0 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -380,6 +380,17 @@ namespace tools uint64_t m_fee; crypto::public_key m_asset_id; }; + + struct no_zc_inputs : public transfer_error + { + no_zc_inputs(const std::string& /*v*/): transfer_error(std::string(""), API_RETURN_CODE_MISSING_ZC_INPUTS) + {} + + virtual const char* what() const noexcept + { + return API_RETURN_CODE_MISSING_ZC_INPUTS; + } + }; //---------------------------------------------------------------------------------------------------- struct not_enough_outs_to_mix : public transfer_error { diff --git a/tests/core_tests/ionic_swap_tests.cpp b/tests/core_tests/ionic_swap_tests.cpp index 0fe5f725..013176d2 100644 --- a/tests/core_tests/ionic_swap_tests.cpp +++ b/tests/core_tests/ionic_swap_tests.cpp @@ -67,6 +67,14 @@ bool ionic_swap_basic_test::c1(currency::core& c, size_t ev_index, const std::ve alice_acc.generate(); std::shared_ptr alice_wlt = init_playtime_test_wallet(events, c, alice_acc); alice_wlt->get_account().set_createtime(0); + + + currency::account_base bob_acc; + bob_acc.generate(); + std::shared_ptr bob_wlt = init_playtime_test_wallet(events, c, bob_acc); + bob_wlt->get_account().set_createtime(0); + + miner_wlt->refresh(); currency::asset_descriptor_base adb = AUTO_VAL_INIT(adb); @@ -76,7 +84,7 @@ bool ionic_swap_basic_test::c1(currency::core& c, size_t ev_index, const std::ve adb.decimal_point = 12; std::vector destinations(2); - destinations[0].addr.push_back(miner_wlt->get_account().get_public_address()); + destinations[0].addr.push_back(bob_wlt->get_account().get_public_address()); destinations[0].amount = AMOUNT_ASSETS_TO_TRANSFER_MULTIASSETS_BASIC; destinations[0].asset_id = currency::null_pkey; destinations[1].addr.push_back(alice_wlt->get_account().get_public_address()); @@ -94,16 +102,17 @@ bool ionic_swap_basic_test::c1(currency::core& c, size_t ev_index, const std::ve currency::transaction res_tx = AUTO_VAL_INIT(res_tx); miner_wlt->transfer(COIN, alice_wlt->get_account().get_public_address(), res_tx); + miner_wlt->transfer(COIN, bob_wlt->get_account().get_public_address(), res_tx); r = mine_next_pow_blocks_in_playtime(miner_wlt->get_account().get_public_address(), c, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_blocks_in_playtime failed"); - miner_wlt->refresh(); + bob_wlt->refresh(); alice_wlt->refresh(); uint64_t mined = 0; std::unordered_map balances; - miner_wlt->balance(balances, mined); + bob_wlt->balance(balances, mined); auto it_asset = balances.find(asset_id); auto it_native = balances.find(currency::native_coin_asset_id); @@ -111,7 +120,7 @@ bool ionic_swap_basic_test::c1(currency::core& c, size_t ev_index, const std::ve CHECK_AND_ASSERT_MES(it_asset != balances.end() && it_native != balances.end(), false, "Failed to find needed asset in result balances"); CHECK_AND_ASSERT_MES(it_asset->second.total == AMOUNT_ASSETS_TO_TRANSFER_MULTIASSETS_BASIC, false, "Failed to find needed asset in result balances"); - CHECK_AND_ASSERT_MES(it_native->second.total == uint64_t(17517226)*COIN - COIN, false, "Failed to find needed asset in result balances"); + CHECK_AND_ASSERT_MES(it_native->second.total == COIN, false, "Failed to find needed asset in result balances"); uint64_t mined_balance = it_native->second.total; @@ -126,9 +135,9 @@ bool ionic_swap_basic_test::c1(currency::core& c, size_t ev_index, const std::ve CHECK_AND_ASSERT_MES(it_asset->second.total == AMOUNT_ASSETS_TO_TRANSFER_MULTIASSETS_BASIC, false, "Failed to find needed asset in result balances"); const uint64_t assets_to_exchange = 10 * COIN; - const uint64_t native_tokens_to_exchange = 1 * COIN; + const uint64_t native_tokens_to_exchange = COIN/2; - //alice_wlt want to trade with miner_wlt, to exchange 10.0 TCT to 1.0 ZANO + //alice_wlt want to trade with bob_wlt, to exchange 10.0 TCT to 1.0 ZANO view::ionic_swap_proposal_info proposal_details = AUTO_VAL_INIT(proposal_details); proposal_details.expiration_time = alice_wlt->get_core_runtime_config().get_core_time() + 10 * 60; proposal_details.fee_paid_by_a = TESTS_DEFAULT_FEE; @@ -137,10 +146,10 @@ bool ionic_swap_basic_test::c1(currency::core& c, size_t ev_index, const std::ve proposal_details.to_alice.push_back(view::asset_funds{ currency::native_coin_asset_id , native_tokens_to_exchange }); tools::wallet_public::ionic_swap_proposal proposal = AUTO_VAL_INIT(proposal); - alice_wlt->create_ionic_swap_proposal(proposal_details, miner_wlt->get_account().get_public_address(), proposal); + alice_wlt->create_ionic_swap_proposal(proposal_details, bob_wlt->get_account().get_public_address(), proposal); view::ionic_swap_proposal_info proposal_decoded_info = AUTO_VAL_INIT(proposal_decoded_info); - miner_wlt->get_ionic_swap_proposal_info(proposal, proposal_decoded_info); + bob_wlt->get_ionic_swap_proposal_info(proposal, proposal_decoded_info); //Validate proposal if (proposal_decoded_info.to_bob != proposal_details.to_bob @@ -153,13 +162,13 @@ bool ionic_swap_basic_test::c1(currency::core& c, size_t ev_index, const std::ve } currency::transaction res_tx2 = AUTO_VAL_INIT(res_tx2); - r = miner_wlt->accept_ionic_swap_proposal(proposal, res_tx2); + r = bob_wlt->accept_ionic_swap_proposal(proposal, res_tx2); CHECK_AND_ASSERT_MES(r, false, "Failed to accept ionic proposal"); r = mine_next_pow_blocks_in_playtime(miner_wlt->get_account().get_public_address(), c, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_blocks_in_playtime failed"); - miner_wlt->refresh(); + bob_wlt->refresh(); alice_wlt->refresh(); @@ -170,15 +179,18 @@ bool ionic_swap_basic_test::c1(currency::core& c, size_t ev_index, const std::ve it_native = balances.find(currency::native_coin_asset_id); CHECK_AND_ASSERT_MES(it_asset != balances.end(), false, "Failed to find needed asset in result balances"); - CHECK_AND_ASSERT_MES(it_native->second.total == native_tokens_to_exchange, false, "Failed to find needed asset in result balances"); + CHECK_AND_ASSERT_MES(it_native->second.total == native_tokens_to_exchange + COIN - TESTS_DEFAULT_FEE, false, "Failed to find needed asset in result balances"); CHECK_AND_ASSERT_MES(it_asset->second.total == AMOUNT_ASSETS_TO_TRANSFER_MULTIASSETS_BASIC - assets_to_exchange, false, "Failed to find needed asset in result balances"); balances.clear(); - miner_wlt->balance(balances, mined); + bob_wlt->balance(balances, mined); + it_asset = balances.find(asset_id); + it_native = balances.find(currency::native_coin_asset_id); + CHECK_AND_ASSERT_MES(it_asset != balances.end(), false, "Failed to find needed asset in result balances"); - CHECK_AND_ASSERT_MES(it_native->second.total == mined_balance - native_tokens_to_exchange, false, "Failed to find needed asset in result balances"); + CHECK_AND_ASSERT_MES(it_native->second.total == COIN - native_tokens_to_exchange, false, "Failed to find needed asset in result balances"); CHECK_AND_ASSERT_MES(it_asset->second.total == AMOUNT_ASSETS_TO_TRANSFER_MULTIASSETS_BASIC + assets_to_exchange, false, "Failed to find needed asset in result balances");