From aea73f46a7a7710d817433dad85d7b41c3af8c22 Mon Sep 17 00:00:00 2001 From: Dmitry Matsiukhov Date: Thu, 30 Oct 2025 23:46:35 +0300 Subject: [PATCH 1/8] add default txs decoy selection alg --- src/wallet/wallet2.cpp | 379 ++++++++++++++++++++++++++--------------- src/wallet/wallet2.h | 10 +- 2 files changed, 250 insertions(+), 139 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 03b915639..d26018057 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -4824,12 +4824,8 @@ bool wallet2::proxy_to_daemon(const std::string& uri, const std::string& body, i return m_core_proxy->call_COMMAND_RPC_INVOKE(uri, body, response_code, response_body); } //---------------------------------------------------------------------------------------------------- -bool wallet2::prepare_pos_zc_input_and_ring(const transfer_details& td, - const currency::tx_out_zarcanum& stake_out, - currency::txin_zc_input& stake_input, - std::vector& decoy_storage, - std::vector& ring, - uint64_t& secret_index) const +bool wallet2::prepare_pos_zc_input_and_ring(const transfer_details& td, const currency::tx_out_zarcanum& stake_out, currency::txin_zc_input& stake_input, + std::vector& decoy_storage, std::vector& ring, uint64_t& secret_index) const { bool r = false; ring.clear(); @@ -4967,6 +4963,203 @@ bool wallet2::prepare_pos_zc_input_and_ring(const transfer_details& td, return true; } +bool wallet2::collect_decoys_for_regular_input(const transfer_details& td, size_t wanted_decoys_count, std::vector& decoy_storage) const +{ + if (td.is_zc()) + return collect_decoys_for_zc_input(td, wanted_decoys_count, decoy_storage); + else + return collect_decoys_for_bare_input(td, wanted_decoys_count, decoy_storage); +} + +bool wallet2::collect_decoys_for_bare_input(const transfer_details& td, size_t wanted_decoys_count, std::vector& decoy_storage) const +{ + decoy_storage.clear(); + + if (this->is_auditable() || wanted_decoys_count == 0) + return true; + + size_t oversample_cnt = (wanted_decoys_count + 1) * 2; + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request req = AUTO_VAL_INIT(req); + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response resp = AUTO_VAL_INIT(resp); + + req.amounts.push_back(td.amount()); + req.decoys_count = oversample_cnt; + req.use_forced_mix_outs = false; + req.height_upper_limit = m_last_pow_block_h; + + bool ok = m_core_proxy->call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS(req, resp); + THROW_IF_FALSE_WALLET_EX(ok, error::no_connection_to_daemon, "get_random_outs.bin(v2)"); + THROW_IF_FALSE_WALLET_EX(resp.outs.size() == 1, error::get_random_outs_error, "wrong outs.size()"); + + const auto& res_for_amount = resp.outs[0]; + std::vector coinbase_candidates; + std::vector noncb_candidates; + + for (const auto& oe : res_for_amount.outs) + { + if (oe.global_amount_index == td.m_global_output_index) + continue; + + if (oe.flags & RANDOM_OUTPUTS_FOR_AMOUNTS_FLAGS_COINBASE) + coinbase_candidates.push_back(oe); + else + noncb_candidates.push_back(oe); + } + + auto rnd_shuffle = [](auto& v) + { + for (size_t i = v.size(); i > 1; --i) + std::swap(v[i - 1], v[crypto::rand() % i]); + }; + + rnd_shuffle(coinbase_candidates); + rnd_shuffle(noncb_candidates); + + bool include_one_noncb = ((crypto::rand() % 100) < WALLET_NONCB_SET_PROB_PERCENT) && !noncb_candidates.empty(); + + std::unordered_set used_gindices; + used_gindices.reserve(wanted_decoys_count + 1); + used_gindices.insert(td.m_global_output_index); + + auto take_next_unique = [&](std::vector& pool, size_t& cursor) -> bool + { + while (cursor < pool.size()) + { + const auto& cand = pool[cursor++]; + if (!used_gindices.count(cand.global_amount_index)) + { + used_gindices.insert(cand.global_amount_index); + decoy_storage.push_back(cand); + return true; + } + } + return false; + }; + + size_t cb_cur = 0, nc_cur = 0; + + if (include_one_noncb) + take_next_unique(noncb_candidates, nc_cur); + + while (decoy_storage.size() < wanted_decoys_count) + { + if (take_next_unique(coinbase_candidates, cb_cur)) + continue; + + if (!take_next_unique(noncb_candidates, nc_cur)) + break; + } + + std::sort(decoy_storage.begin(), decoy_storage.end(), [](const auto& l, const auto& r){ return l.global_amount_index < r.global_amount_index; }); + + return true; +} + +bool wallet2::collect_decoys_for_zc_input( const transfer_details& td, size_t wanted_decoys_count, std::vector& decoy_storage) const +{ + decoy_storage.clear(); + + if (this->is_auditable() || wanted_decoys_count == 0) + return true; + + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4::request decoys_req = AUTO_VAL_INIT(decoys_req); + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4::response decoys_resp = AUTO_VAL_INIT(decoys_resp); + + decoys_req.height_upper_limit = m_last_pow_block_h; + decoys_req.look_up_strategy = LOOK_UP_STRATEGY_REGULAR_TX; + + decoys_req.heights.resize((wanted_decoys_count + 1) * 2); + build_distribution_for_input(decoys_req.heights, td.m_ptx_wallet_info->m_block_height, decoy_selection_generator::dist_kind::regular); + + bool r = m_core_proxy->call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4(decoys_req, decoys_resp); + THROW_IF_FALSE_WALLET_EX(r, error::no_connection_to_daemon, "getrandom_outs4.bin"); + THROW_IF_FALSE_WALLET_EX(decoys_resp.status != API_RETURN_CODE_BUSY, error::daemon_busy, "getrandom_outs4.bin"); + THROW_IF_FALSE_WALLET_EX(decoys_resp.status == API_RETURN_CODE_OK, error::get_random_outs_error, decoys_resp.status); + + bool real_is_post_hf4 = m_core_runtime_config.is_hardfork_active_for_height(ZANO_HARDFORK_04_ZARCANUM, td.m_ptx_wallet_info->m_block_height); + std::vector coinbase_candidates; + std::vector noncb_candidates; + coinbase_candidates.reserve(decoys_resp.blocks.size()); + + for (const auto& blk : decoys_resp.blocks) + { + if (blk.outs.empty()) + continue; + + bool blk_is_post_hf4 = m_core_runtime_config.is_hardfork_active_for_height(ZANO_HARDFORK_04_ZARCANUM, blk.block_height); + + if (real_is_post_hf4) + { + if (!blk_is_post_hf4) + continue; + } + else + { + if (blk_is_post_hf4) + continue; + } + + for (const auto& oe : blk.outs) + { + if (oe.global_amount_index == td.m_global_output_index) + continue; + + if (oe.flags & RANDOM_OUTPUTS_FOR_AMOUNTS_FLAGS_COINBASE) + coinbase_candidates.push_back(oe); + else + noncb_candidates.push_back(oe); + } + } + + auto rnd_shuffle = [](auto& v) + { + for (size_t i = v.size(); i > 1; --i) + std::swap(v[i - 1], v[crypto::rand() % i]); + }; + + rnd_shuffle(coinbase_candidates); + rnd_shuffle(noncb_candidates); + + bool include_one_noncb = ((crypto::rand() % 100) < WALLET_NONCB_SET_PROB_PERCENT) && !noncb_candidates.empty(); + + std::unordered_set used_gindices; + used_gindices.reserve(wanted_decoys_count + 1); + used_gindices.insert(td.m_global_output_index); + + auto take_next_unique = [&](std::vector& pool, size_t& cursor) -> bool + { + while (cursor < pool.size()) + { + const auto& cand = pool[cursor++]; + if (!used_gindices.count(cand.global_amount_index)) + { + used_gindices.insert(cand.global_amount_index); + decoy_storage.push_back(cand); + return true; + } + } + return false; + }; + + size_t cb_cur = 0, nc_cur = 0; + + if (include_one_noncb) + take_next_unique(noncb_candidates, nc_cur); + + while (decoy_storage.size() < wanted_decoys_count) + { + if (take_next_unique(coinbase_candidates, cb_cur)) + continue; + + if (!take_next_unique(noncb_candidates, nc_cur)) + break; + } + + std::sort(decoy_storage.begin(), decoy_storage.end(), + [](const auto& l, const auto& r){ return l.global_amount_index < r.global_amount_index; }); + + return true; +} //---------------------------------------------------------------------------------------------------- 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 { @@ -6771,155 +6964,71 @@ bool wallet2::prepare_tx_sources(size_t fake_outputs_count_, bool use_all_decoys 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_; - [[maybe_unused]] uint64_t zarcanum_start_from = m_core_runtime_config.hard_forks.m_height_the_hardfork_n_active_after[ZANO_HARDFORK_04_ZARCANUM]; - [[maybe_unused]] 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) + for (size_t i = 0; i < selected_indicies.size(); ++i) { - 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"); + uint64_t tr_idx = selected_indicies[i]; + auto it = m_transfers.find(tr_idx); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it != m_transfers.end(), "internal error: idx " << tr_idx << " not found in m_transfers"); + transfer_details& td = it->second; 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.transfer_index = tr_idx; src.amount = td.amount(); src.asset_id = td.get_asset_id(); - size_t fake_outputs_count = fake_outputs_count_; + size_t wanted_decoys_count = fake_outputs_count_; //redefine for hardfork if (td.is_zc() && !this->is_auditable()) - fake_outputs_count = m_core_runtime_config.hf4_minimum_mixins; + wanted_decoys_count = m_core_runtime_config.hf4_minimum_mixins; + std::vector local_decoys; + bool ok = collect_decoys_for_regular_input(td, wanted_decoys_count, local_decoys); + WLT_CHECK_AND_ASSERT_MES(ok, false, "collect_decoys_for_regular_input failed"); + for (const out_entry& daemon_oe : local_decoys) + { + 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() >= wanted_decoys_count) + break; + } - //paste mixin transaction - if (daemon_resp.outs.size()) + if (!use_all_decoys_if_found_less_than_required) { - if (td.is_zc()) + if (src.outputs.size() < wanted_decoys_count) { - //get rid of unneeded - select_decoys(daemon_resp.outs[i], td.m_global_output_index); - } - else - { - //TODO: make sure we have exact count needed - } + std::vector scanty_outs; + { + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount ofa; + ofa.amount = td.amount(); + scanty_outs.push_back(ofa); + } - 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; + THROW_IF_FALSE_WALLET_EX(false, error::not_enough_outs_to_mix, scanty_outs, wanted_decoys_count); } } //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 - }); + { + 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 @@ -6942,7 +7051,7 @@ bool wallet2::prepare_tx_sources(size_t fake_outputs_count_, bool use_all_decoys 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(), get_asset_decimal_point(td.get_asset_id(), CURRENCY_DISPLAY_DECIMAL_POINT)) << " is not a ZC"); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.is_zc(), "transfer #" << tr_idx << ", amount: " << print_money_brief(td.amount(), get_asset_decimal_point(td.get_asset_id(), CURRENCY_DISPLAY_DECIMAL_POINT)) << " 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; @@ -6952,20 +7061,18 @@ bool wallet2::prepare_tx_sources(size_t fake_outputs_count_, bool use_all_decoys #endif VARIANT_SWITCH_END(); - auto interted_it = src.outputs.insert(it_to_insert, real_oe); + auto inserted_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 = inserted_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 << ", "; + ss << "source entry [" << i << "], td_idx: " << tr_idx << ", "; print_source_entry(ss, src); WLT_LOG_L1(ss.str()); } - - ++i; } return true; } diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 528be3796..6755cc373 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -662,9 +662,13 @@ namespace tools void set_genesis(const crypto::hash& genesis_hash); bool 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 prepare_pos_zc_input_and_ring(const transfer_details& td, const currency::tx_out_zarcanum& stake_out, currency::txin_zc_input& stake_input, - std::vector& decoy_storage, - std::vector& ring, - uint64_t& secret_index) const; + std::vector& decoy_storage, std::vector& ring, uint64_t& secret_index) const; + bool collect_decoys_for_regular_input(const transfer_details& td, size_t wanted_decoys_count, + std::vector& decoy_storage) const; + bool collect_decoys_for_bare_input(const transfer_details& td, size_t wanted_decoys_count, + std::vector& decoy_storage) const; + bool collect_decoys_for_zc_input(const transfer_details& td, size_t wanted_decoys_count, + std::vector& decoy_storage) const; void process_new_blockchain_entry(const currency::block& b, const currency::block_direct_data_entry& bche, const crypto::hash& bl_id, From 385abcc78c56f23dbf55ceb864b27fbe10a6c40f Mon Sep 17 00:00:00 2001 From: Dmitry Matsiukhov Date: Fri, 31 Oct 2025 01:49:46 +0300 Subject: [PATCH 2/8] refactoring --- src/wallet/wallet2.cpp | 359 ++++++++++++++++------------------------- 1 file changed, 142 insertions(+), 217 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index d26018057..5b3d9443f 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -4824,24 +4824,135 @@ bool wallet2::proxy_to_daemon(const std::string& uri, const std::string& body, i return m_core_proxy->call_COMMAND_RPC_INVOKE(uri, body, response_code, response_body); } //---------------------------------------------------------------------------------------------------- +using out_entry = currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry; + +template +void rnd_shuffle(vec_t& v) +{ + for (size_t i = v.size(); i > 1; --i) + std::swap(v[i - 1], v[crypto::rand() % i]); +} + +void pick_decoys_from_pools(std::vector& coinbase_candidates, std::vector& noncb_candidates, + size_t wanted_decoys_count, uint64_t real_gindex, std::vector& decoy_storage_out) +{ + decoy_storage_out.clear(); + + rnd_shuffle(coinbase_candidates); + rnd_shuffle(noncb_candidates); + + bool include_one_noncb = ((crypto::rand() % 100) < WALLET_NONCB_SET_PROB_PERCENT) && !noncb_candidates.empty(); + + std::unordered_set used_gindices; + used_gindices.reserve(wanted_decoys_count + 1); + used_gindices.insert(real_gindex); + + auto take_next_unique = [&](std::vector& pool, size_t& cursor) -> bool + { + while (cursor < pool.size()) + { + const auto& cand = pool[cursor++]; + if (!used_gindices.count(cand.global_amount_index)) + { + used_gindices.insert(cand.global_amount_index); + decoy_storage_out.push_back(cand); + return true; + } + } + return false; + }; + + size_t cb_cur = 0, nc_cur = 0; + + if (include_one_noncb) + take_next_unique(noncb_candidates, nc_cur); + + while (decoy_storage_out.size() < wanted_decoys_count) + { + if (take_next_unique(coinbase_candidates, cb_cur)) + continue; + + if (!take_next_unique(noncb_candidates, nc_cur)) + break; + } + + std::sort(decoy_storage_out.begin(), decoy_storage_out.end(), + [](const out_entry& l, const out_entry& r) + { + return l.global_amount_index < r.global_amount_index; + } + ); +} + +void build_pools_from_outs_for_amount(const std::list& resp_outs, uint64_t real_gindex, std::vector& coinbase_candidates, std::vector& noncb_candidates) +{ + coinbase_candidates.clear(); + noncb_candidates.clear(); + + for (const auto& oe : resp_outs) + { + if (oe.global_amount_index == real_gindex) + continue; + + if (oe.flags & RANDOM_OUTPUTS_FOR_AMOUNTS_FLAGS_COINBASE) + coinbase_candidates.push_back(oe); + else + noncb_candidates.push_back(oe); + } +} + +template +void build_pools_from_blocks(const currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4::response& resp4, uint64_t real_gindex, const tools::wallet2& self, + is_post_hf4_fn&& is_post_hf4, block_filter_fn&& block_allowed, entry_filter_fn&& entry_allowed, + std::vector& coinbase_candidates, std::vector& noncb_candidates) +{ + coinbase_candidates.clear(); + noncb_candidates.clear(); + + for (const auto& blk : resp4.blocks) + { + if (blk.outs.empty()) + continue; + + bool blk_is_post_hf4 = is_post_hf4(blk.block_height); + + if (!block_allowed(blk.block_height, blk_is_post_hf4)) + continue; + + for (const auto& oe : blk.outs) + { + if (!entry_allowed(oe)) + continue; + + if (oe.global_amount_index == real_gindex) + continue; + + if (oe.flags & RANDOM_OUTPUTS_FOR_AMOUNTS_FLAGS_COINBASE) + coinbase_candidates.push_back(oe); + else + noncb_candidates.push_back(oe); + } + } +} + bool wallet2::prepare_pos_zc_input_and_ring(const transfer_details& td, const currency::tx_out_zarcanum& stake_out, currency::txin_zc_input& stake_input, std::vector& decoy_storage, std::vector& ring, uint64_t& secret_index) const { bool r = false; ring.clear(); - secret_index = 0; // index of the real stake output + secret_index = 0; // index of the real stake output stake_input.key_offsets.clear(); decoy_storage.clear(); - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4::response decoys_resp = AUTO_VAL_INIT(decoys_resp); - // 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; if (required_decoys_count > 0 && !is_auditable()) { - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4::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 + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4::request decoys_req = AUTO_VAL_INIT(decoys_req); + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4::response decoys_resp = AUTO_VAL_INIT(decoys_resp); + + 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.look_up_strategy = LOOK_UP_STRATEGY_POS_COINBASE; decoys_req.heights.resize((required_decoys_count + 1) * 2); // request outs by heights distribution build_distribution_for_input(decoys_req.heights, td.m_ptx_wallet_info->m_block_height, decoy_selection_generator::dist_kind::coinbase); @@ -4853,82 +4964,19 @@ bool wallet2::prepare_pos_zc_input_and_ring(const transfer_details& td, const cu 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_CMN_ERR_EX(!decoys_resp.blocks.empty(), "daemon returned no decoy blocks for PoS"); - std::vector coinbase_candidates; - std::vector noncb_candidates; - coinbase_candidates.reserve(decoys_resp.blocks.size()); + std::vector coinbase_candidates; + std::vector noncb_candidates; - for (const auto& blk : decoys_resp.blocks) - { - if (blk.outs.empty()) - continue; - - for (const auto& oe : blk.outs) - { - const bool is_real = (oe.global_amount_index == td.m_global_output_index); - if (is_real) - continue; - if (!m_core_runtime_config.is_hardfork_active_for_height(ZANO_HARDFORK_04_ZARCANUM, blk.block_height)) - continue; - - if (oe.flags & RANDOM_OUTPUTS_FOR_AMOUNTS_FLAGS_COINBASE) - coinbase_candidates.push_back(oe); - else - noncb_candidates.push_back(oe); - } - } - - bool include_one_noncb = ((crypto::rand() % 100) < WALLET_NONCB_SET_PROB_PERCENT) && !noncb_candidates.empty(); - - auto rnd_shuffle = [](auto& v) - { - for (size_t i = v.size(); i > 1; --i) - std::swap(v[i - 1], v[crypto::rand() % i]); - }; - rnd_shuffle(coinbase_candidates); - rnd_shuffle(noncb_candidates); - - decoy_storage.reserve(required_decoys_count + 1); // +1 real - - std::unordered_set used_gindices; - used_gindices.reserve(required_decoys_count + 1); - used_gindices.insert(td.m_global_output_index); - - auto take_next_unique = [&](std::vector& out_pool, size_t& cursor) -> bool - { - while (cursor < out_pool.size()) - { - const auto& cand = out_pool[cursor++]; - if (!used_gindices.count(cand.global_amount_index)) - { - used_gindices.insert(cand.global_amount_index); - decoy_storage.push_back(cand); - return true; - } - } - return false; - }; - - size_t cb_cur = 0, nc_cur = 0; - - if (include_one_noncb) - take_next_unique(noncb_candidates, nc_cur); - - // get the rest from coinbase if its out, fallback to non-сoinbase - while (decoy_storage.size() < required_decoys_count) - { - if (take_next_unique(coinbase_candidates, cb_cur)) - continue; + build_pools_from_blocks(decoys_resp, td.m_global_output_index, *this, + /* is_post_hf4 block = */ [&](uint64_t h) -> bool { return m_core_runtime_config.is_hardfork_active_for_height(ZANO_HARDFORK_04_ZARCANUM, h); }, + /* block_allowed = */ [&](uint64_t blk_height, bool blk_is_post_hf4) -> bool { return blk_is_post_hf4; }, // PoS stacke only zcarcanum-era outputs + /* entry_allowed = */ [&](const out_entry& oe) -> bool { (void)oe; return true; }, + coinbase_candidates, noncb_candidates + ); - // coinbase is out of stock, adding non-coinbase - if (!take_next_unique(noncb_candidates, nc_cur)) - break; // have no more decoys - } + pick_decoys_from_pools(coinbase_candidates, noncb_candidates, required_decoys_count, td.m_global_output_index, decoy_storage); - WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(decoy_storage.size() == required_decoys_count, - "for PoS stake got less decoys than required: picked=" << decoy_storage.size() - << " < " << required_decoys_count - << " (coinbase_candidates=" << coinbase_candidates.size() - << ", noncb_pool=" << noncb_candidates.size() << ")"); + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(decoy_storage.size() == required_decoys_count, "for PoS stake got less decoys than required: picked=" << decoy_storage.size() << " < " << required_decoys_count << " (coinbase_candidates=" << coinbase_candidates.size() << ", noncb_pool=" << noncb_candidates.size() << ")"); // add real decoy_storage.emplace_back(td.m_global_output_index, stake_out.stealth_address, stake_out.amount_commitment, stake_out.concealing_point, stake_out.blinded_asset_id); @@ -4991,66 +5039,11 @@ bool wallet2::collect_decoys_for_bare_input(const transfer_details& td, size_t w THROW_IF_FALSE_WALLET_EX(ok, error::no_connection_to_daemon, "get_random_outs.bin(v2)"); THROW_IF_FALSE_WALLET_EX(resp.outs.size() == 1, error::get_random_outs_error, "wrong outs.size()"); - const auto& res_for_amount = resp.outs[0]; - std::vector coinbase_candidates; - std::vector noncb_candidates; - - for (const auto& oe : res_for_amount.outs) - { - if (oe.global_amount_index == td.m_global_output_index) - continue; - - if (oe.flags & RANDOM_OUTPUTS_FOR_AMOUNTS_FLAGS_COINBASE) - coinbase_candidates.push_back(oe); - else - noncb_candidates.push_back(oe); - } - - auto rnd_shuffle = [](auto& v) - { - for (size_t i = v.size(); i > 1; --i) - std::swap(v[i - 1], v[crypto::rand() % i]); - }; - - rnd_shuffle(coinbase_candidates); - rnd_shuffle(noncb_candidates); - - bool include_one_noncb = ((crypto::rand() % 100) < WALLET_NONCB_SET_PROB_PERCENT) && !noncb_candidates.empty(); - - std::unordered_set used_gindices; - used_gindices.reserve(wanted_decoys_count + 1); - used_gindices.insert(td.m_global_output_index); - - auto take_next_unique = [&](std::vector& pool, size_t& cursor) -> bool - { - while (cursor < pool.size()) - { - const auto& cand = pool[cursor++]; - if (!used_gindices.count(cand.global_amount_index)) - { - used_gindices.insert(cand.global_amount_index); - decoy_storage.push_back(cand); - return true; - } - } - return false; - }; - - size_t cb_cur = 0, nc_cur = 0; - - if (include_one_noncb) - take_next_unique(noncb_candidates, nc_cur); - - while (decoy_storage.size() < wanted_decoys_count) - { - if (take_next_unique(coinbase_candidates, cb_cur)) - continue; - - if (!take_next_unique(noncb_candidates, nc_cur)) - break; - } + std::vector coinbase_candidates; + std::vector noncb_candidates; - std::sort(decoy_storage.begin(), decoy_storage.end(), [](const auto& l, const auto& r){ return l.global_amount_index < r.global_amount_index; }); + build_pools_from_outs_for_amount(resp.outs[0].outs, td.m_global_output_index, coinbase_candidates, noncb_candidates); + pick_decoys_from_pools(coinbase_candidates, noncb_candidates, wanted_decoys_count, td.m_global_output_index, decoy_storage); return true; } @@ -5066,10 +5059,10 @@ bool wallet2::collect_decoys_for_zc_input( const transfer_details& td, size_t wa COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4::response decoys_resp = AUTO_VAL_INIT(decoys_resp); decoys_req.height_upper_limit = m_last_pow_block_h; - decoys_req.look_up_strategy = LOOK_UP_STRATEGY_REGULAR_TX; - + decoys_req.look_up_strategy = LOOK_UP_STRATEGY_REGULAR_TX; decoys_req.heights.resize((wanted_decoys_count + 1) * 2); - build_distribution_for_input(decoys_req.heights, td.m_ptx_wallet_info->m_block_height, decoy_selection_generator::dist_kind::regular); + + build_distribution_for_input(decoys_req.heights,td.m_ptx_wallet_info->m_block_height,decoy_selection_generator::dist_kind::regular); bool r = m_core_proxy->call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4(decoys_req, decoys_resp); THROW_IF_FALSE_WALLET_EX(r, error::no_connection_to_daemon, "getrandom_outs4.bin"); @@ -5077,86 +5070,18 @@ bool wallet2::collect_decoys_for_zc_input( const transfer_details& td, size_t wa THROW_IF_FALSE_WALLET_EX(decoys_resp.status == API_RETURN_CODE_OK, error::get_random_outs_error, decoys_resp.status); bool real_is_post_hf4 = m_core_runtime_config.is_hardfork_active_for_height(ZANO_HARDFORK_04_ZARCANUM, td.m_ptx_wallet_info->m_block_height); - std::vector coinbase_candidates; - std::vector noncb_candidates; - coinbase_candidates.reserve(decoys_resp.blocks.size()); - for (const auto& blk : decoys_resp.blocks) - { - if (blk.outs.empty()) - continue; - - bool blk_is_post_hf4 = m_core_runtime_config.is_hardfork_active_for_height(ZANO_HARDFORK_04_ZARCANUM, blk.block_height); + std::vector coinbase_candidates; + std::vector noncb_candidates; - if (real_is_post_hf4) - { - if (!blk_is_post_hf4) - continue; - } - else - { - if (blk_is_post_hf4) - continue; - } - - for (const auto& oe : blk.outs) - { - if (oe.global_amount_index == td.m_global_output_index) - continue; - - if (oe.flags & RANDOM_OUTPUTS_FOR_AMOUNTS_FLAGS_COINBASE) - coinbase_candidates.push_back(oe); - else - noncb_candidates.push_back(oe); - } - } - - auto rnd_shuffle = [](auto& v) - { - for (size_t i = v.size(); i > 1; --i) - std::swap(v[i - 1], v[crypto::rand() % i]); - }; - - rnd_shuffle(coinbase_candidates); - rnd_shuffle(noncb_candidates); - - bool include_one_noncb = ((crypto::rand() % 100) < WALLET_NONCB_SET_PROB_PERCENT) && !noncb_candidates.empty(); - - std::unordered_set used_gindices; - used_gindices.reserve(wanted_decoys_count + 1); - used_gindices.insert(td.m_global_output_index); - - auto take_next_unique = [&](std::vector& pool, size_t& cursor) -> bool - { - while (cursor < pool.size()) - { - const auto& cand = pool[cursor++]; - if (!used_gindices.count(cand.global_amount_index)) - { - used_gindices.insert(cand.global_amount_index); - decoy_storage.push_back(cand); - return true; - } - } - return false; - }; - - size_t cb_cur = 0, nc_cur = 0; - - if (include_one_noncb) - take_next_unique(noncb_candidates, nc_cur); - - while (decoy_storage.size() < wanted_decoys_count) - { - if (take_next_unique(coinbase_candidates, cb_cur)) - continue; - - if (!take_next_unique(noncb_candidates, nc_cur)) - break; - } + build_pools_from_blocks(decoys_resp, td.m_global_output_index, *this, + /* is_post_hf4 block = */ [&](uint64_t h) -> bool { return m_core_runtime_config.is_hardfork_active_for_height(ZANO_HARDFORK_04_ZARCANUM, h); }, + /* block_allowed = */ [&](uint64_t blk_height, bool blk_is_post_hf4) -> bool { return (real_is_post_hf4 == blk_is_post_hf4); }, // if real output is post-HF4 -> take only post-HF4 blocks + /* entry_allowed = */ [&](const out_entry& oe) -> bool { (void)oe; return true; }, // for regular zc-input we don't need extra restrictions + coinbase_candidates, noncb_candidates + ); - std::sort(decoy_storage.begin(), decoy_storage.end(), - [](const auto& l, const auto& r){ return l.global_amount_index < r.global_amount_index; }); + pick_decoys_from_pools(coinbase_candidates, noncb_candidates, wanted_decoys_count, td.m_global_output_index, decoy_storage); return true; } From dc17783f3941f6a977f6101c4c4d427d1ef7aeb9 Mon Sep 17 00:00:00 2001 From: Dmitry Matsiukhov Date: Sun, 2 Nov 2025 23:32:34 +0300 Subject: [PATCH 3/8] fix shuffle, sort, out_entry --- src/wallet/wallet2.cpp | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 5b3d9443f..e449b6f72 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -64,6 +64,9 @@ using namespace currency; #undef LOG_DEFAULT_CHANNEL #define LOG_DEFAULT_CHANNEL "wallet" ENABLE_CHANNEL_BY_DEFAULT("wallet") + +using out_entry = currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry; + namespace tools { wallet2::wallet2() @@ -4824,22 +4827,13 @@ bool wallet2::proxy_to_daemon(const std::string& uri, const std::string& body, i return m_core_proxy->call_COMMAND_RPC_INVOKE(uri, body, response_code, response_body); } //---------------------------------------------------------------------------------------------------- -using out_entry = currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry; - -template -void rnd_shuffle(vec_t& v) -{ - for (size_t i = v.size(); i > 1; --i) - std::swap(v[i - 1], v[crypto::rand() % i]); -} - void pick_decoys_from_pools(std::vector& coinbase_candidates, std::vector& noncb_candidates, size_t wanted_decoys_count, uint64_t real_gindex, std::vector& decoy_storage_out) { decoy_storage_out.clear(); - rnd_shuffle(coinbase_candidates); - rnd_shuffle(noncb_candidates); + std::shuffle(coinbase_candidates.begin(), coinbase_candidates.end(), crypto::uniform_random_bit_generator()); + std::shuffle(noncb_candidates.begin(), noncb_candidates.end(), crypto::uniform_random_bit_generator()); bool include_one_noncb = ((crypto::rand() % 100) < WALLET_NONCB_SET_PROB_PERCENT) && !noncb_candidates.empty(); @@ -4875,13 +4869,6 @@ void pick_decoys_from_pools(std::vector& coinbase_candidates, std::ve if (!take_next_unique(noncb_candidates, nc_cur)) break; } - - std::sort(decoy_storage_out.begin(), decoy_storage_out.end(), - [](const out_entry& l, const out_entry& r) - { - return l.global_amount_index < r.global_amount_index; - } - ); } void build_pools_from_outs_for_amount(const std::list& resp_outs, uint64_t real_gindex, std::vector& coinbase_candidates, std::vector& noncb_candidates) @@ -6947,6 +6934,7 @@ bool wallet2::prepare_tx_sources(size_t fake_outputs_count_, bool use_all_decoys } } + std::sort(local_decoys.begin(), local_decoys.end(), [](const out_entry& l, const out_entry& r) { return l.global_amount_index < r.global_amount_index; }); //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) { From c0442eac6197e247142ee3a5e1423f7c78e62ba4 Mon Sep 17 00:00:00 2001 From: Dmitry Matsiukhov Date: Fri, 7 Nov 2025 03:03:40 +0300 Subject: [PATCH 4/8] work batching version --- src/currency_core/blockchain_storage.cpp | 144 +++++++------ src/currency_core/blockchain_storage.h | 2 +- src/rpc/core_rpc_server.cpp | 7 +- src/rpc/core_rpc_server_commands_defs.h | 31 ++- src/wallet/wallet2.cpp | 253 ++++++++++++----------- src/wallet/wallet2.h | 7 - tests/core_tests/chaingen.cpp | 121 ++++++----- 7 files changed, 310 insertions(+), 255 deletions(-) diff --git a/src/currency_core/blockchain_storage.cpp b/src/currency_core/blockchain_storage.cpp index c121e9056..d004eaf3c 100644 --- a/src/currency_core/blockchain_storage.cpp +++ b/src/currency_core/blockchain_storage.cpp @@ -3135,96 +3135,104 @@ bool blockchain_storage::get_random_outs_for_amounts4(const COMMAND_RPC_GET_RAND const uint64_t top_block_height = get_current_blockchain_size() - CURRENCY_MINED_MONEY_UNLOCK_WINDOW; const uint64_t height_limit = (req.height_upper_limit && req.height_upper_limit <= top_block_height) ? req.height_upper_limit : top_block_height; + res.blocks_batches.clear(); + res.blocks_batches.reserve(req.batches.size()); - std::unordered_set seen_heights; - std::unordered_set picked_heights; - seen_heights.reserve(req.heights.size()); - picked_heights.reserve(req.heights.size()); - - res.blocks.clear(); - res.blocks.reserve(req.heights.size()); - - auto search_pass = [&](const std::string& strategy) + for(size_t i = 0; i < req.batches.size(); ++i) { - seen_heights.clear(); - for (uint64_t seed_height_original : req.heights) - { - uint64_t seed_height = seed_height_original; - if (seed_height > height_limit) - seed_height = height_limit; + std::unordered_set seen_heights; + std::unordered_set picked_heights; + seen_heights.reserve(req.batches[i].heights.size()); + picked_heights.reserve(req.batches[i].heights.size()); - uint64_t delta = 0; - int step_direction = +1; + res.blocks_batches.emplace_back(); + auto& out_blocks = res.blocks_batches.back().blocks; + out_blocks.reserve(req.batches[i].heights.size()); - while (true) + auto search_pass = [&](const std::string& strategy) + { + seen_heights.clear(); + for (uint64_t seed_height_original : req.batches[i].heights) { - bool inside_range = false; - uint64_t candidate_height = 0; + uint64_t seed_height = seed_height_original; + if (seed_height > height_limit) + seed_height = height_limit; - if (step_direction > 0) + uint64_t delta = 0; + int step_direction = +1; + + while (true) { - if (seed_height + delta <= height_limit) + bool inside_range = false; + uint64_t candidate_height = 0; + + if (step_direction > 0) { - candidate_height = seed_height + delta; - inside_range = true; + if (seed_height + delta <= height_limit) + { + candidate_height = seed_height + delta; + inside_range = true; + } } - } - else - { - if (seed_height >= delta) + else { - candidate_height = seed_height - delta; - inside_range = true; + if (seed_height >= delta) + { + candidate_height = seed_height - delta; + inside_range = true; + } } - } - if (inside_range) - { - if (!picked_heights.count(candidate_height) && seen_heights.insert(candidate_height).second) + if (inside_range) { - if (is_block_fit_for_strategy(candidate_height, strategy)) + if (!picked_heights.count(candidate_height) && seen_heights.insert(candidate_height).second) { - std::vector oe; - collect_all_outs_in_block(candidate_height, oe); - picked_heights.insert(candidate_height); - - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4::outputs_in_block blk_outs{}; - blk_outs.block_height = candidate_height; - blk_outs.outs = std::move(oe); - res.blocks.push_back(std::move(blk_outs)); - break; // found for this seed + if (is_block_fit_for_strategy(candidate_height, strategy)) + { + std::vector oe; + collect_all_outs_in_block(req.batches[i].input_amount, candidate_height, oe); + picked_heights.insert(candidate_height); + + if(!oe.empty()) + { + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4::outputs_in_block blk_outs{}; + blk_outs.block_height = candidate_height; + blk_outs.outs = std::move(oe); + out_blocks.emplace_back(std::move(blk_outs)); + break; // found for this seed + } + } } } - } - // change direction - step_direction = -step_direction; - if (step_direction > 0) - ++delta; + // change direction + step_direction = -step_direction; + if (step_direction > 0) + ++delta; + + // out of diapason from both sides or exceeded radius limit + const bool out_of_right = (seed_height + delta) > height_limit; + const bool out_of_left = (seed_height < delta); + if ((out_of_right && out_of_left) || (delta > MAX_SEARCH_DELTA_HEIGHT)) + { + break; + } + } - // out of diapason from both sides or exceeded radius limit - const bool out_of_right = (seed_height + delta) > height_limit; - const bool out_of_left = (seed_height < delta); - if ((out_of_right && out_of_left) || (delta > MAX_SEARCH_DELTA_HEIGHT)) + // early exit - enough found + if (out_blocks.size() >= req.batches[i].heights.size()) { break; } } + }; - // early exit - enough found - if (res.blocks.size() >= req.heights.size()) - { - break; - } + search_pass(req.look_up_strategy); + if(out_blocks.size() == 0 && req.look_up_strategy != LOOK_UP_STRATEGY_REGULAR_TX) + { + search_pass(LOOK_UP_STRATEGY_REGULAR_TX); } - }; - - search_pass(req.look_up_strategy); - if(res.blocks.size() == 0 && req.look_up_strategy != LOOK_UP_STRATEGY_REGULAR_TX) - { - search_pass(LOOK_UP_STRATEGY_REGULAR_TX); } - return true; } //------------------------------------------------------------------ @@ -8796,7 +8804,7 @@ bool blockchain_storage::is_block_fit_for_strategy(uint64_t h, const std::string } } //------------------------------------------------------------------ -bool blockchain_storage::collect_all_outs_in_block(uint64_t height, std::vector& outs) const +bool blockchain_storage::collect_all_outs_in_block(uint64_t input_amount, uint64_t height, std::vector& outs) const { CRITICAL_REGION_LOCAL(m_read_lock); @@ -8815,7 +8823,6 @@ bool blockchain_storage::collect_all_outs_in_block(uint64_t height, std::vector< CHECK_AND_ASSERT_MES(this->get_tx_outputs_gindexs(txid, gidx), false, "failed to get_tx_outputs_gindexs() for tx_id " << txid); CHECK_AND_ASSERT_MES(gidx.size() == tx.vout.size(), false, "gidx size (" << gidx.size() << ") != tx vout size (" << tx.vout.size() << ") for tx_id " << txid); - for (size_t i = 0; i < tx.vout.size(); ++i) { uint64_t amount = 0; @@ -8832,7 +8839,8 @@ bool blockchain_storage::collect_all_outs_in_block(uint64_t height, std::vector< VARIANT_SWITCH_END(); COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry oen{}; - if (this->build_random_out_entry(amount, gidx[i], mix_count, /*use_only_forced_to_mix=*/false, /*height_upper_limit=*/0, oen)) + if (this->build_random_out_entry(amount, gidx[i], mix_count, /*use_only_forced_to_mix=*/false, /*height_upper_limit=*/0, oen) && + amount == input_amount) // for pre-zc inputs { outs.emplace_back(oen); } diff --git a/src/currency_core/blockchain_storage.h b/src/currency_core/blockchain_storage.h index 183e33e85..2926447ba 100644 --- a/src/currency_core/blockchain_storage.h +++ b/src/currency_core/blockchain_storage.h @@ -255,7 +255,7 @@ namespace currency //void get_all_known_block_ids(std::list &main, std::list &alt, std::list &invalid) const; bool is_pre_hardfork_tx_freeze_period_active() const; bool is_block_fit_for_strategy(uint64_t h, const std::string& strategy) const; - bool collect_all_outs_in_block(uint64_t height, std::vector& outs) const; + bool collect_all_outs_in_block(uint64_t input_amount, uint64_t height, std::vector& outs) const; bc_attachment_services_manager& get_attachment_services_manager(){ return m_services_mgr; } diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index e1ceeacd0..9f62f06ab 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -491,7 +491,12 @@ namespace currency bool core_rpc_server::on_get_random_outs4(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4::response& res, connection_context& cntx) { CHECK_CORE_READY(); - CHECK_RPC_LIMITS(req.heights.size(), RPC_LIMIT_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS); + size_t total_heights = 0; + for(size_t i = 0; i < req.batches.size(); ++i) + { + total_heights += req.batches[i].heights.size(); + CHECK_RPC_LIMITS(total_heights, RPC_LIMIT_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS); + } res.status = API_RETURN_CODE_FAIL; if (!m_core.get_blockchain_storage().get_random_outs_for_amounts4(req, res)) { diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index fbcdadcea..1e858189d 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -658,17 +658,26 @@ namespace currency struct COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4 { DOC_COMMAND("Version 4 of the command to retrieve random decoy outputs for specified amounts, focusing on either pre-zarcanum or post-zarcanum zones based on the amount value."); + struct request_batch + { + uint64_t input_amount; + std::vector heights; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(input_amount) DOC_DSCR("Amount to be processed in the batch.") DOC_EXMP_AUTO(1000000) DOC_END + KV_SERIALIZE(heights) DOC_DSCR("Array of heights to be processed in the batch.") DOC_EXMP_AUTO(1) DOC_END + END_KV_SERIALIZE_MAP() + }; struct request { - std::vector heights; // array heights derived from decoy selection algorithm, number of heights expected to be not less than minimal ring size + std::vector batches; // multiple amounts with heights to be processed in a single call uint64_t height_upper_limit; // if nonzero, all the decoy outputs must be either older than, or the same age as this height std::string look_up_strategy; // LOOK_UP_STRATEGY_REGULAR_TX or LOOK_UP_STRATEGY_POS_COINBASE BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(heights) DOC_DSCR("array heights derived from decoy selection algorithm, number of heights expected to be not less than minimal ring size") DOC_EXMP_AGGR(1, 2, 3, 4) DOC_END - KV_SERIALIZE(height_upper_limit) DOC_DSCR("Maximum blockchain height from which decoys can be taken. If nonzero, decoys must be at this height or older.") DOC_EXMP(2555000) DOC_END - KV_SERIALIZE(look_up_strategy) DOC_DSCR("LOOK_UP_STRATEGY_REGULAR_TX or LOOK_UP_STRATEGY_POS_COINBASE") DOC_EXMP("LOOK_UP_STRATEGY_REGULAR_TX") DOC_END + KV_SERIALIZE(batches) DOC_DSCR("List of request batches, each containing an amount and corresponding heights to be processed.") DOC_EXMP_AUTO(1) DOC_END + KV_SERIALIZE(height_upper_limit) DOC_DSCR("Maximum blockchain height from which decoys can be taken. If nonzero, decoys must be at this height or older.") DOC_EXMP(2555000) DOC_END + KV_SERIALIZE(look_up_strategy) DOC_DSCR("LOOK_UP_STRATEGY_REGULAR_TX or LOOK_UP_STRATEGY_POS_COINBASE") DOC_EXMP("LOOK_UP_STRATEGY_REGULAR_TX") DOC_END END_KV_SERIALIZE_MAP() }; @@ -685,14 +694,22 @@ namespace currency END_KV_SERIALIZE_MAP() }; - struct response + struct blocks_batch { std::vector blocks; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(blocks) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::vector blocks_batches; // batches x blocks std::string status; BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(blocks) DOC_DSCR("Blocks collected by node") DOC_EXMP_AUTO(1) DOC_END - KV_SERIALIZE(status) DOC_DSCR("Status of the call.") DOC_EXMP(API_RETURN_CODE_OK) DOC_END + KV_SERIALIZE(blocks_batches) DOC_DSCR("Blocks collected by node") DOC_EXMP_AUTO(1) DOC_END + KV_SERIALIZE(status) DOC_DSCR("Status of the call.") DOC_EXMP(API_RETURN_CODE_OK) DOC_END END_KV_SERIALIZE_MAP() }; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index e449b6f72..920b4ebd7 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -4889,14 +4889,14 @@ void build_pools_from_outs_for_amount(const std::list& resp_outs, uin } template -void build_pools_from_blocks(const currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4::response& resp4, uint64_t real_gindex, const tools::wallet2& self, - is_post_hf4_fn&& is_post_hf4, block_filter_fn&& block_allowed, entry_filter_fn&& entry_allowed, +void build_pools_from_blocks(const std::vector& blocks, + uint64_t real_gindex, is_post_hf4_fn&& is_post_hf4, block_filter_fn&& block_allowed, entry_filter_fn&& entry_allowed, std::vector& coinbase_candidates, std::vector& noncb_candidates) { coinbase_candidates.clear(); noncb_candidates.clear(); - for (const auto& blk : resp4.blocks) + for (const auto& blk : blocks) { if (blk.outs.empty()) continue; @@ -4941,20 +4941,24 @@ bool wallet2::prepare_pos_zc_input_and_ring(const transfer_details& td, const cu 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.look_up_strategy = LOOK_UP_STRATEGY_POS_COINBASE; - decoys_req.heights.resize((required_decoys_count + 1) * 2); // request outs by heights distribution - build_distribution_for_input(decoys_req.heights, td.m_ptx_wallet_info->m_block_height, decoy_selection_generator::dist_kind::coinbase); - + // for pos miner tx we request only one batch of decoys + decoys_req.batches.resize(1); + { + auto& batch = decoys_req.batches.at(0); + batch.input_amount = 0; + batch.heights.resize((required_decoys_count + 1) * 2); // request outs by heights distribution + build_distribution_for_input(batch.heights, td.m_ptx_wallet_info->m_block_height, decoy_selection_generator::dist_kind::coinbase); + } r = m_core_proxy->call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4(decoys_req, decoys_resp); // TODO @#@# do we need these exceptions? THROW_IF_FALSE_WALLET_EX(r, error::no_connection_to_daemon, "getrandom_outs4.bin"); THROW_IF_FALSE_WALLET_EX(decoys_resp.status != API_RETURN_CODE_BUSY, error::daemon_busy, "getrandom_outs4.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_CMN_ERR_EX(!decoys_resp.blocks.empty(), "daemon returned no decoy blocks for PoS"); - + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(!decoys_resp.blocks_batches.empty() && !decoys_resp.blocks_batches[0].blocks.empty(), "daemon returned no decoy blocks for PoS"); std::vector coinbase_candidates; std::vector noncb_candidates; - build_pools_from_blocks(decoys_resp, td.m_global_output_index, *this, + build_pools_from_blocks(decoys_resp.blocks_batches.at(0).blocks, td.m_global_output_index, /* is_post_hf4 block = */ [&](uint64_t h) -> bool { return m_core_runtime_config.is_hardfork_active_for_height(ZANO_HARDFORK_04_ZARCANUM, h); }, /* block_allowed = */ [&](uint64_t blk_height, bool blk_is_post_hf4) -> bool { return blk_is_post_hf4; }, // PoS stacke only zcarcanum-era outputs /* entry_allowed = */ [&](const out_entry& oe) -> bool { (void)oe; return true; }, @@ -4997,81 +5001,6 @@ bool wallet2::prepare_pos_zc_input_and_ring(const transfer_details& td, const cu return true; } - -bool wallet2::collect_decoys_for_regular_input(const transfer_details& td, size_t wanted_decoys_count, std::vector& decoy_storage) const -{ - if (td.is_zc()) - return collect_decoys_for_zc_input(td, wanted_decoys_count, decoy_storage); - else - return collect_decoys_for_bare_input(td, wanted_decoys_count, decoy_storage); -} - -bool wallet2::collect_decoys_for_bare_input(const transfer_details& td, size_t wanted_decoys_count, std::vector& decoy_storage) const -{ - decoy_storage.clear(); - - if (this->is_auditable() || wanted_decoys_count == 0) - return true; - - size_t oversample_cnt = (wanted_decoys_count + 1) * 2; - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request req = AUTO_VAL_INIT(req); - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response resp = AUTO_VAL_INIT(resp); - - req.amounts.push_back(td.amount()); - req.decoys_count = oversample_cnt; - req.use_forced_mix_outs = false; - req.height_upper_limit = m_last_pow_block_h; - - bool ok = m_core_proxy->call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS(req, resp); - THROW_IF_FALSE_WALLET_EX(ok, error::no_connection_to_daemon, "get_random_outs.bin(v2)"); - THROW_IF_FALSE_WALLET_EX(resp.outs.size() == 1, error::get_random_outs_error, "wrong outs.size()"); - - std::vector coinbase_candidates; - std::vector noncb_candidates; - - build_pools_from_outs_for_amount(resp.outs[0].outs, td.m_global_output_index, coinbase_candidates, noncb_candidates); - pick_decoys_from_pools(coinbase_candidates, noncb_candidates, wanted_decoys_count, td.m_global_output_index, decoy_storage); - - return true; -} - -bool wallet2::collect_decoys_for_zc_input( const transfer_details& td, size_t wanted_decoys_count, std::vector& decoy_storage) const -{ - decoy_storage.clear(); - - if (this->is_auditable() || wanted_decoys_count == 0) - return true; - - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4::request decoys_req = AUTO_VAL_INIT(decoys_req); - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4::response decoys_resp = AUTO_VAL_INIT(decoys_resp); - - decoys_req.height_upper_limit = m_last_pow_block_h; - decoys_req.look_up_strategy = LOOK_UP_STRATEGY_REGULAR_TX; - decoys_req.heights.resize((wanted_decoys_count + 1) * 2); - - build_distribution_for_input(decoys_req.heights,td.m_ptx_wallet_info->m_block_height,decoy_selection_generator::dist_kind::regular); - - bool r = m_core_proxy->call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4(decoys_req, decoys_resp); - THROW_IF_FALSE_WALLET_EX(r, error::no_connection_to_daemon, "getrandom_outs4.bin"); - THROW_IF_FALSE_WALLET_EX(decoys_resp.status != API_RETURN_CODE_BUSY, error::daemon_busy, "getrandom_outs4.bin"); - THROW_IF_FALSE_WALLET_EX(decoys_resp.status == API_RETURN_CODE_OK, error::get_random_outs_error, decoys_resp.status); - - bool real_is_post_hf4 = m_core_runtime_config.is_hardfork_active_for_height(ZANO_HARDFORK_04_ZARCANUM, td.m_ptx_wallet_info->m_block_height); - - std::vector coinbase_candidates; - std::vector noncb_candidates; - - build_pools_from_blocks(decoys_resp, td.m_global_output_index, *this, - /* is_post_hf4 block = */ [&](uint64_t h) -> bool { return m_core_runtime_config.is_hardfork_active_for_height(ZANO_HARDFORK_04_ZARCANUM, h); }, - /* block_allowed = */ [&](uint64_t blk_height, bool blk_is_post_hf4) -> bool { return (real_is_post_hf4 == blk_is_post_hf4); }, // if real output is post-HF4 -> take only post-HF4 blocks - /* entry_allowed = */ [&](const out_entry& oe) -> bool { (void)oe; return true; }, // for regular zc-input we don't need extra restrictions - coinbase_candidates, noncb_candidates - ); - - pick_decoys_from_pools(coinbase_candidates, noncb_candidates, wanted_decoys_count, td.m_global_output_index, decoy_storage); - - return true; -} //---------------------------------------------------------------------------------------------------- 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 { @@ -6873,37 +6802,135 @@ bool wallet2::prepare_tx_sources(size_t fake_outputs_count, 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; + using out_entry = COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry; + using tx_output_entry = currency::tx_source_entry::output_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 + uint64_t hf4_height = m_core_runtime_config.hard_forks.get_height_the_hardfork_active_after(ZANO_HARDFORK_04_ZARCANUM); + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4::request req4 = AUTO_VAL_INIT(req4); + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4::response resp4 = AUTO_VAL_INIT(resp4); + + req4.height_upper_limit = m_last_pow_block_h; + req4.look_up_strategy = LOOK_UP_STRATEGY_REGULAR_TX; + + // amount -> batch index in req4/resp4 + std::unordered_map amount2batch; + amount2batch.reserve(selected_indicies.size() + 1); + + auto ensure_batch = [&](uint64_t amount_key) -> size_t + { + auto it = amount2batch.find(amount_key); + if (it != amount2batch.end()) + return it->second; + size_t idx = req4.batches.size(); + req4.batches.resize(idx + 1); + req4.batches[idx].input_amount = amount_key; // 0 => ZC + amount2batch.emplace(amount_key, idx); + return idx; + }; + + struct item_ctx + { + size_t sel_pos; + uint64_t tr_idx; + transfer_details* td; + size_t wanted; + bool need_decoys; + bool real_is_post; + uint64_t amount_key; + size_t batch_idx; + }; + std::vector items; + items.reserve(selected_indicies.size()); + for (size_t i = 0; i < selected_indicies.size(); ++i) { uint64_t tr_idx = selected_indicies[i]; - auto it = m_transfers.find(tr_idx); WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it != m_transfers.end(), "internal error: idx " << tr_idx << " not found in m_transfers"); transfer_details& td = it->second; + size_t wanted = fake_outputs_count_; + //redefine for hardfork + if (td.is_zc() && !this->is_auditable()) + wanted = m_core_runtime_config.hf4_minimum_mixins; + + bool need_decoys = (!this->is_auditable() && wanted > 0); + + uint64_t amount_key = td.is_zc() ? uint64_t(0) : td.amount(); + size_t batch_idx = size_t(-1); + if (need_decoys) + { + batch_idx = ensure_batch(amount_key); + const size_t overs = (wanted + 1) * 2; + std::vector tmp_heights(overs); + uint64_t distrib_max_height = (td.is_zc() || td.m_ptx_wallet_info->m_block_height < hf4_height) ? td.m_ptx_wallet_info->m_block_height : hf4_height; + build_distribution_for_input(tmp_heights, distrib_max_height, decoy_selection_generator::dist_kind::regular); + + auto& heights_req = req4.batches[batch_idx].heights; + heights_req.reserve(heights_req.size() + tmp_heights.size()); + heights_req.insert(heights_req.end(), tmp_heights.begin(), tmp_heights.end()); + } + + bool real_is_post = m_core_runtime_config.is_hardfork_active_for_height(ZANO_HARDFORK_04_ZARCANUM, td.m_ptx_wallet_info->m_block_height); + + items.push_back(item_ctx{i, tr_idx, &td, wanted, need_decoys, real_is_post, amount_key, batch_idx}); + } + + // if atleast one input wanted decoys - we make a single request + if (!req4.batches.empty()) + { + resp4.blocks_batches.clear(); + resp4.blocks_batches.reserve(req4.batches.size()); + + bool ok = m_core_proxy->call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4(req4, resp4); + THROW_IF_FALSE_WALLET_EX(ok, error::no_connection_to_daemon, "getrandom_outs4.bin"); + THROW_IF_FALSE_WALLET_EX(resp4.status != API_RETURN_CODE_BUSY, error::daemon_busy, "getrandom_outs4.bin"); + THROW_IF_FALSE_WALLET_EX(resp4.status == API_RETURN_CODE_OK, error::get_random_outs_error, resp4.status); + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(resp4.blocks_batches.size() == req4.batches.size(), "daemon returned mismatched blocks_batches size"); + } + else + { + resp4.blocks_batches.clear(); + } + + // construct sources by distributing decoys across the inputs + for (const auto& itx : items) + { + const transfer_details& td = *itx.td; + sources.push_back(AUTO_VAL_INIT(currency::tx_source_entry())); currency::tx_source_entry& src = sources.back(); - src.transfer_index = tr_idx; + src.transfer_index = itx.tr_idx; src.amount = td.amount(); src.asset_id = td.get_asset_id(); - size_t wanted_decoys_count = fake_outputs_count_; - //redefine for hardfork - if (td.is_zc() && !this->is_auditable()) - wanted_decoys_count = m_core_runtime_config.hf4_minimum_mixins; std::vector local_decoys; - bool ok = collect_decoys_for_regular_input(td, wanted_decoys_count, local_decoys); - WLT_CHECK_AND_ASSERT_MES(ok, false, "collect_decoys_for_regular_input failed"); + + if (itx.need_decoys) + { + auto found = amount2batch.find(itx.amount_key); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(found != amount2batch.end(), "batch not found for amount_key"); + size_t bidx = found->second; + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(bidx < resp4.blocks_batches.size(), "blocks_batches index OOB"); + + const auto& blocks = resp4.blocks_batches[bidx].blocks; + + std::vector coinbase_candidates, noncb_candidates; + + build_pools_from_blocks(blocks, td.m_global_output_index, + /* is_post_hf4 */ [&](uint64_t h) { return m_core_runtime_config.is_hardfork_active_for_height(ZANO_HARDFORK_04_ZARCANUM, h); }, + /* block_allowed */ [&](uint64_t /*blk_height*/, bool blk_is_post_hf4) { return (itx.real_is_post == blk_is_post_hf4); }, + /* entry_allowed */ [&](const out_entry& oe) { (void)oe; return true; }, + coinbase_candidates, noncb_candidates); + + pick_decoys_from_pools(coinbase_candidates, noncb_candidates, itx.wanted, td.m_global_output_index, local_decoys); + } + + std::sort(local_decoys.begin(), local_decoys.end(), [](const out_entry& l, const out_entry& r) { return l.global_amount_index < r.global_amount_index; }); + // move decoys to src.outputs (no more than itx.wanted) for (const out_entry& daemon_oe : local_decoys) { if (td.m_global_output_index == daemon_oe.global_amount_index) @@ -6915,33 +6942,29 @@ bool wallet2::prepare_tx_sources(size_t fake_outputs_count_, bool use_all_decoys 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() >= wanted_decoys_count) + if (src.outputs.size() >= itx.wanted) break; } if (!use_all_decoys_if_found_less_than_required) { - if (src.outputs.size() < wanted_decoys_count) + if (src.outputs.size() < itx.wanted) { std::vector scanty_outs; - { - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount ofa; - ofa.amount = td.amount(); - scanty_outs.push_back(ofa); - } - - THROW_IF_FALSE_WALLET_EX(false, error::not_enough_outs_to_mix, scanty_outs, wanted_decoys_count); + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount ofa; + ofa.amount = td.amount(); + scanty_outs.push_back(ofa); + THROW_IF_FALSE_WALLET_EX(false, error::not_enough_outs_to_mix, scanty_outs, itx.wanted); } } - std::sort(local_decoys.begin(), local_decoys.end(), [](const out_entry& l, const out_entry& r) { return l.global_amount_index < r.global_amount_index; }); //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 - }); + { + 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 @@ -6964,7 +6987,7 @@ bool wallet2::prepare_tx_sources(size_t fake_outputs_count_, bool use_all_decoys 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 #" << tr_idx << ", amount: " << print_money_brief(td.amount(), get_asset_decimal_point(td.get_asset_id(), CURRENCY_DISPLAY_DECIMAL_POINT)) << " is not a ZC"); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.is_zc(), "transfer #" << itx.tr_idx << ", amount: " << print_money_brief(td.amount(), get_asset_decimal_point(td.get_asset_id(), CURRENCY_DISPLAY_DECIMAL_POINT)) << " 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; @@ -6982,7 +7005,7 @@ bool wallet2::prepare_tx_sources(size_t fake_outputs_count_, bool use_all_decoys if (epee::log_space::get_set_log_detalisation_level() >= LOG_LEVEL_1) { std::stringstream ss; - ss << "source entry [" << i << "], td_idx: " << tr_idx << ", "; + ss << "source entry [" << itx.sel_pos << "], td_idx: " << itx.tr_idx << ", "; print_source_entry(ss, src); WLT_LOG_L1(ss.str()); } diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 6755cc373..5ad8a524b 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -663,12 +663,6 @@ namespace tools bool 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 prepare_pos_zc_input_and_ring(const transfer_details& td, const currency::tx_out_zarcanum& stake_out, currency::txin_zc_input& stake_input, std::vector& decoy_storage, std::vector& ring, uint64_t& secret_index) const; - bool collect_decoys_for_regular_input(const transfer_details& td, size_t wanted_decoys_count, - std::vector& decoy_storage) const; - bool collect_decoys_for_bare_input(const transfer_details& td, size_t wanted_decoys_count, - std::vector& decoy_storage) const; - bool collect_decoys_for_zc_input(const transfer_details& td, size_t wanted_decoys_count, - std::vector& decoy_storage) const; void process_new_blockchain_entry(const currency::block& b, const currency::block_direct_data_entry& bche, const crypto::hash& bl_id, @@ -950,7 +944,6 @@ namespace tools void wti_to_json_line(std::ostream& ss, const wallet_public::wallet_transfer_info& wti, size_t index) const; - /* !!!!! IMPORTAN !!!!! diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index 6f54c8ecd..e8e48b9c7 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -450,7 +450,8 @@ bool test_generator::build_wallets(const blockchain_vector& blockchain, bool call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4(const currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4::request& req, currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4::response& rsp) override { - rsp.blocks.clear(); + rsp.blocks_batches.clear(); + rsp.blocks_batches.reserve(req.batches.size()); // Current "visible" height in the generator const uint64_t top = m_blockchain.empty() ? 0 : (m_blockchain.size() - 1); @@ -479,7 +480,7 @@ bool test_generator::build_wallets(const blockchain_vector& blockchain, }; auto gather_from_tx = [&](uint64_t h, const currency::transaction& tx, const crypto::hash& txid, bool is_coinbase, - bool is_pos_block_flag, std::vector& outs_vec) + bool is_pos_block_flag, std::vector& outs_vec) { const auto& gidx = get_tx_gindex_from_map(txid, m_txs_outs); if (gidx.size() != tx.vout.size()) @@ -535,8 +536,9 @@ bool test_generator::build_wallets(const blockchain_vector& blockchain, } }; - // collect block at height h and add to rsp.blocks if outs are present - auto collect_from_height = [&](uint64_t h, std::unordered_set& seen_set) -> size_t + // collect block at height h and add to out_blocks if outs are present + auto collect_from_height = [&](uint64_t h, std::unordered_set& seen_set, + std::vector& out_blocks) -> size_t { if (h == 0 && hul_exclusive == 0) return 0; @@ -562,75 +564,82 @@ bool test_generator::build_wallets(const blockchain_vector& blockchain, currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4::outputs_in_block ob{}; ob.block_height = h; ob.outs = std::move(outs); - rsp.blocks.emplace_back(std::move(ob)); - return rsp.blocks.back().outs.size(); + out_blocks.emplace_back(std::move(ob)); + return out_blocks.back().outs.size(); } return 0; }; - // Only req.heights, no fallbacks - std::unordered_set seen; - seen.reserve(req.heights.size() * 3); - - for (uint64_t seed_h : req.heights) + for (size_t bi = 0; bi < req.batches.size(); ++bi) { - uint64_t h = seed_h > hul_exclusive ? hul_exclusive : seed_h; - collect_from_height(h, seen); - } - - auto sum_outs = [&]() -> uint64_t { - uint64_t s = 0; - for (const auto& b : rsp.blocks) s += static_cast(b.outs.size()); - return s; - }; + rsp.blocks_batches.emplace_back(); + auto& out_blocks = rsp.blocks_batches.back().blocks; + out_blocks.reserve(req.batches[bi].heights.size()); - uint64_t total = sum_outs(); - const uint64_t target = static_cast(req.heights.size()); + std::unordered_set seen; + seen.reserve(req.batches[bi].heights.size() * 3); - // Fallback 2 - fill the gaps between the seeds - if (total < target) - { - std::vector seeds; - seeds.reserve(req.heights.size()); - for (uint64_t sh : req.heights) + // only req.batches[bi].heights, no fallbacks + for (uint64_t seed_h : req.batches[bi].heights) { - uint64_t hh = sh > hul_exclusive ? hul_exclusive : sh; - if (hh <= hul_exclusive) - seeds.push_back(hh); + uint64_t h = seed_h > hul_exclusive ? hul_exclusive : seed_h; + collect_from_height(h, seen, out_blocks); } - std::sort(seeds.begin(), seeds.end()); - seeds.erase(std::unique(seeds.begin(), seeds.end()), seeds.end()); - for (size_t i = 0; i + 1 < seeds.size() && total < target; ++i) + auto sum_outs = [&]() -> uint64_t { + uint64_t s = 0; + for (const auto& b : out_blocks) s += static_cast(b.outs.size()); + return s; + }; + + uint64_t total = sum_outs(); + const uint64_t target = static_cast(req.batches[bi].heights.size()); + + // Fallback 2 - fill the gaps between the seeds + if (total < target) { - uint64_t a = seeds[i]; - uint64_t b = seeds[i + 1]; - if (a + 1 >= b) - continue; + std::vector seeds; + seeds.reserve(req.batches[bi].heights.size()); + for (uint64_t sh : req.batches[bi].heights) + { + uint64_t hh = sh > hul_exclusive ? hul_exclusive : sh; + if (hh <= hul_exclusive) + seeds.push_back(hh); + } + std::sort(seeds.begin(), seeds.end()); + seeds.erase(std::unique(seeds.begin(), seeds.end()), seeds.end()); - // go from the closest to a to b (or vice versa; determinism is important) - for (uint64_t h = a + 1; h < b && total < target; ++h) + for (size_t i = 0; i + 1 < seeds.size() && total < target; ++i) { - size_t added = collect_from_height(h, seen); - if (added) - total += added; + uint64_t a = seeds[i]; + uint64_t b = seeds[i + 1]; + if (a + 1 >= b) + continue; + + // go from the closest to a to b (or vice versa; determinism is important) + for (uint64_t h = a + 1; h < b && total < target; ++h) + { + size_t added = collect_from_height(h, seen, out_blocks); + if (added) + total += added; + } } } - } - // Fallback 3 - global passage through the remaining heights - if (total < target) - { - // from hul_exclusive down to 1 (0 — genesis) - for (uint64_t h = hul_exclusive; h >= 1 && total < target; --h) + // Fallback 3 - global passage through the remaining heights + if (total < target) { - if (seen.count(h)) - continue; - size_t added = collect_from_height(h, seen); - if (added) - total += added; - if (h == 1) - break; + // from hul_exclusive down to 1 (0 — genesis) + for (uint64_t h = hul_exclusive; h >= 1 && total < target; --h) + { + if (seen.count(h)) + continue; + size_t added = collect_from_height(h, seen, out_blocks); + if (added) + total += added; + if (h == 1) + break; + } } } From dd0bbd9b63a7270bcc0201fd8d831765b075840d Mon Sep 17 00:00:00 2001 From: Dmitry Matsiukhov Date: Mon, 10 Nov 2025 16:20:58 +0300 Subject: [PATCH 5/8] refactoring batching --- src/wallet/wallet2.cpp | 388 +++++++++++++++++++++-------------------- src/wallet/wallet2.h | 19 +- 2 files changed, 219 insertions(+), 188 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 920b4ebd7..1f5d21946 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -66,6 +66,7 @@ using namespace currency; ENABLE_CHANNEL_BY_DEFAULT("wallet") using out_entry = currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry; +using tx_output_entry = currency::tx_source_entry::output_entry; namespace tools { @@ -4922,6 +4923,200 @@ void build_pools_from_blocks(const std::vector& heights, size_t oversample, uint64_t max_height, decoy_selection_generator::dist_kind kind) const +{ + if (oversample == 0) + return; + std::vector tmp(oversample); + build_distribution_for_input(tmp, max_height, kind); + heights.reserve(heights.size() + tmp.size()); + heights.insert(heights.end(), tmp.begin(), tmp.end()); +} + +void wallet2::plan_decoy_batches_for_sources( size_t fake_outputs_count_, const std::vector& selected_indices, uint64_t hf4_height, + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4::request& req4, std::vector& plans) const +{ + req4.height_upper_limit = m_last_pow_block_h; + req4.look_up_strategy = LOOK_UP_STRATEGY_REGULAR_TX; + + plans.clear(); + plans.reserve(selected_indices.size()); + + // local map: amount_key -> batch index + std::unordered_map batch_index_by_amount; + batch_index_by_amount.reserve(selected_indices.size() + 1); + + auto ensure_batch = [&](uint64_t amount_key) -> size_t + { + auto it = batch_index_by_amount.find(amount_key); + if (it != batch_index_by_amount.end()) + return it->second; + const size_t idx = req4.batches.size(); + req4.batches.resize(idx + 1); + req4.batches[idx].input_amount = amount_key; // 0 => ZC + batch_index_by_amount.emplace(amount_key, idx); + return idx; + }; + + for (size_t pos = 0; pos < selected_indices.size(); ++pos) + { + const uint64_t tr_idx = selected_indices[pos]; + auto it = m_transfers.find(tr_idx); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it != m_transfers.end(), "internal error: idx " << tr_idx << " not found in m_transfers"); + const transfer_details& td = it->second; + + size_t target_outputs = fake_outputs_count_; + if (td.is_zc() && !this->is_auditable()) + target_outputs = m_core_runtime_config.hf4_minimum_mixins; + + const bool needs_decoys = (!this->is_auditable() && target_outputs > 0); + const uint64_t amount_key = td.is_zc() ? uint64_t(0) : td.amount(); + size_t batch_idx = SIZE_MAX; + + if (needs_decoys) + { + batch_idx = ensure_batch(amount_key); + const size_t overs = (target_outputs + 1) * 2; + const uint64_t max_h = (td.is_zc() || td.m_ptx_wallet_info->m_block_height < hf4_height) + ? td.m_ptx_wallet_info->m_block_height + : hf4_height; + + append_heights_with_distribution(req4.batches[batch_idx].heights, overs, max_h, decoy_selection_generator::dist_kind::regular); + } + + const bool real_is_post = m_core_runtime_config.is_hardfork_active_for_height(ZANO_HARDFORK_04_ZARCANUM, td.m_ptx_wallet_info->m_block_height); + + plans.push_back(mix_input_plan + { + /*selection_pos*/ pos, + /*transfer_index*/ tr_idx, + /*td*/ &td, + /*target_decoy_count*/ target_outputs, + /*needs_decoys*/ needs_decoys, + /*is_real_output_post_hf4*/ real_is_post, + /*batch_key*/ amount_key, + /*batch_idx*/ batch_idx + }); + } +} +//---------------------------------------------------------------------------------------------------- +void wallet2::distribute_decoys_and_build_sources(const currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4::response& resp4, const std::vector& plans, + bool use_all_decoys_if_found_less_than_required, std::vector& sources) const +{ + for (const mix_input_plan& plan : plans) + { + const transfer_details& td = *plan.td; + + sources.push_back(AUTO_VAL_INIT(currency::tx_source_entry())); + currency::tx_source_entry& src = sources.back(); + src.transfer_index = plan.transfer_index; + src.amount = td.amount(); + src.asset_id = td.get_asset_id(); + + std::vector local_decoys; + + if (plan.needs_decoys) + { + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(plan.batch_idx != SIZE_MAX, "batch_idx is not set for a plan that needs decoys"); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(plan.batch_idx < resp4.blocks_batches.size(), "blocks_batches index OOB"); + const auto& blocks = resp4.blocks_batches[plan.batch_idx].blocks; + + std::vector coinbase_candidates, noncb_candidates; + build_pools_from_blocks( + blocks, td.m_global_output_index, + /* is_post_hf4 */ [&](uint64_t h) { return m_core_runtime_config.is_hardfork_active_for_height(ZANO_HARDFORK_04_ZARCANUM, h); }, + /* block_allowed */ [&](uint64_t /*blk_h*/, bool blk_is_post_hf4) { return (plan.is_real_output_post_hf4 == blk_is_post_hf4); }, + /* entry_allowed */ [&](const out_entry& /*oe*/) { return true; }, + coinbase_candidates, noncb_candidates); + + pick_decoys_from_pools(coinbase_candidates, noncb_candidates, plan.target_decoy_count, td.m_global_output_index, local_decoys); + } + + std::sort(local_decoys.begin(), local_decoys.end(), + [](const out_entry& l, const out_entry& r) { return l.global_amount_index < r.global_amount_index; }); + + for (const out_entry& oe_d : local_decoys) + { + if (td.m_global_output_index == oe_d.global_amount_index) + continue; + + tx_output_entry oe = AUTO_VAL_INIT(oe); + oe.amount_commitment = oe_d.amount_commitment; + oe.concealing_point = oe_d.concealing_point; + oe.out_reference = oe_d.global_amount_index; + oe.stealth_address = oe_d.stealth_address; + oe.blinded_asset_id = oe_d.blinded_asset_id; // TODO @#@# BAD DESIGN, consider refactoring -- sowle + src.outputs.push_back(oe); + if (src.outputs.size() >= plan.target_decoy_count) + break; + } + + if (!use_all_decoys_if_found_less_than_required && src.outputs.size() < plan.target_decoy_count) + { + std::vector scanty_outs; + COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount ofa; + ofa.amount = td.amount(); + scanty_outs.push_back(ofa); + THROW_IF_FALSE_WALLET_EX(false, error::not_enough_outs_to_mix, scanty_outs, plan.target_decoy_count); + } + + //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 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_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; + 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 #" << plan.transfer_index + << ", amount: " << print_money_brief(td.amount(), get_asset_decimal_point(td.get_asset_id(), CURRENCY_DISPLAY_DECIMAL_POINT)) + << " 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_THROW_IF_FALSE_WALLET_INT_ERR_EX(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(), "real_out_asset_id_blinding_mask doesn't match real_oe.blinded_asset_id"); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(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(), "real_out_amount_blinding_mask doesn't match real_oe.amount_commitment"); +#endif + VARIANT_SWITCH_END(); + + auto inserted_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 = inserted_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 [" << plan.selection_pos << "], td_idx: " << plan.transfer_index << ", "; + print_source_entry(ss, src); + WLT_LOG_L1(ss.str()); + } + } +} + bool wallet2::prepare_pos_zc_input_and_ring(const transfer_details& td, const currency::tx_out_zarcanum& stake_out, currency::txin_zc_input& stake_input, std::vector& decoy_storage, std::vector& ring, uint64_t& secret_index) const { @@ -6800,83 +6995,18 @@ bool wallet2::prepare_tx_sources(size_t fake_outputs_count, std::vector& sources, const std::vector& 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_indices) { - using out_entry = COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry; - using tx_output_entry = currency::tx_source_entry::output_entry; + prefetch_global_indicies_if_needed(selected_indices); - prefetch_global_indicies_if_needed(selected_indicies); + const uint64_t hf4_height = m_core_runtime_config.hard_forks.get_height_the_hardfork_active_after(ZANO_HARDFORK_04_ZARCANUM); - uint64_t hf4_height = m_core_runtime_config.hard_forks.get_height_the_hardfork_active_after(ZANO_HARDFORK_04_ZARCANUM); COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4::request req4 = AUTO_VAL_INIT(req4); COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4::response resp4 = AUTO_VAL_INIT(resp4); - req4.height_upper_limit = m_last_pow_block_h; - req4.look_up_strategy = LOOK_UP_STRATEGY_REGULAR_TX; - - // amount -> batch index in req4/resp4 - std::unordered_map amount2batch; - amount2batch.reserve(selected_indicies.size() + 1); + std::vector plans; + plan_decoy_batches_for_sources(fake_outputs_count_, selected_indices, hf4_height, req4, plans); - auto ensure_batch = [&](uint64_t amount_key) -> size_t - { - auto it = amount2batch.find(amount_key); - if (it != amount2batch.end()) - return it->second; - size_t idx = req4.batches.size(); - req4.batches.resize(idx + 1); - req4.batches[idx].input_amount = amount_key; // 0 => ZC - amount2batch.emplace(amount_key, idx); - return idx; - }; - - struct item_ctx - { - size_t sel_pos; - uint64_t tr_idx; - transfer_details* td; - size_t wanted; - bool need_decoys; - bool real_is_post; - uint64_t amount_key; - size_t batch_idx; - }; - std::vector items; - items.reserve(selected_indicies.size()); - - for (size_t i = 0; i < selected_indicies.size(); ++i) - { - uint64_t tr_idx = selected_indicies[i]; - auto it = m_transfers.find(tr_idx); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it != m_transfers.end(), "internal error: idx " << tr_idx << " not found in m_transfers"); - transfer_details& td = it->second; - - size_t wanted = fake_outputs_count_; - //redefine for hardfork - if (td.is_zc() && !this->is_auditable()) - wanted = m_core_runtime_config.hf4_minimum_mixins; - - bool need_decoys = (!this->is_auditable() && wanted > 0); - - uint64_t amount_key = td.is_zc() ? uint64_t(0) : td.amount(); - size_t batch_idx = size_t(-1); - if (need_decoys) - { - batch_idx = ensure_batch(amount_key); - const size_t overs = (wanted + 1) * 2; - std::vector tmp_heights(overs); - uint64_t distrib_max_height = (td.is_zc() || td.m_ptx_wallet_info->m_block_height < hf4_height) ? td.m_ptx_wallet_info->m_block_height : hf4_height; - build_distribution_for_input(tmp_heights, distrib_max_height, decoy_selection_generator::dist_kind::regular); - - auto& heights_req = req4.batches[batch_idx].heights; - heights_req.reserve(heights_req.size() + tmp_heights.size()); - heights_req.insert(heights_req.end(), tmp_heights.begin(), tmp_heights.end()); - } - - bool real_is_post = m_core_runtime_config.is_hardfork_active_for_height(ZANO_HARDFORK_04_ZARCANUM, td.m_ptx_wallet_info->m_block_height); - - items.push_back(item_ctx{i, tr_idx, &td, wanted, need_decoys, real_is_post, amount_key, batch_idx}); - } // if atleast one input wanted decoys - we make a single request if (!req4.batches.empty()) @@ -6895,125 +7025,9 @@ bool wallet2::prepare_tx_sources(size_t fake_outputs_count_, bool use_all_decoys resp4.blocks_batches.clear(); } - // construct sources by distributing decoys across the inputs - for (const auto& itx : items) - { - const transfer_details& td = *itx.td; - - sources.push_back(AUTO_VAL_INIT(currency::tx_source_entry())); - currency::tx_source_entry& src = sources.back(); - - src.transfer_index = itx.tr_idx; - src.amount = td.amount(); - src.asset_id = td.get_asset_id(); - - std::vector local_decoys; - - if (itx.need_decoys) - { - auto found = amount2batch.find(itx.amount_key); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(found != amount2batch.end(), "batch not found for amount_key"); - size_t bidx = found->second; - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(bidx < resp4.blocks_batches.size(), "blocks_batches index OOB"); - - const auto& blocks = resp4.blocks_batches[bidx].blocks; - - std::vector coinbase_candidates, noncb_candidates; - - build_pools_from_blocks(blocks, td.m_global_output_index, - /* is_post_hf4 */ [&](uint64_t h) { return m_core_runtime_config.is_hardfork_active_for_height(ZANO_HARDFORK_04_ZARCANUM, h); }, - /* block_allowed */ [&](uint64_t /*blk_height*/, bool blk_is_post_hf4) { return (itx.real_is_post == blk_is_post_hf4); }, - /* entry_allowed */ [&](const out_entry& oe) { (void)oe; return true; }, - coinbase_candidates, noncb_candidates); - - pick_decoys_from_pools(coinbase_candidates, noncb_candidates, itx.wanted, td.m_global_output_index, local_decoys); - } - - std::sort(local_decoys.begin(), local_decoys.end(), [](const out_entry& l, const out_entry& r) { return l.global_amount_index < r.global_amount_index; }); - // move decoys to src.outputs (no more than itx.wanted) - for (const out_entry& daemon_oe : local_decoys) - { - 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() >= itx.wanted) - break; - } - - if (!use_all_decoys_if_found_less_than_required) - { - if (src.outputs.size() < itx.wanted) - { - std::vector scanty_outs; - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount ofa; - ofa.amount = td.amount(); - scanty_outs.push_back(ofa); - THROW_IF_FALSE_WALLET_EX(false, error::not_enough_outs_to_mix, scanty_outs, itx.wanted); - } - } - - //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_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 #" << itx.tr_idx << ", amount: " << print_money_brief(td.amount(), get_asset_decimal_point(td.get_asset_id(), CURRENCY_DISPLAY_DECIMAL_POINT)) << " 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 inserted_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 = inserted_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 [" << itx.sel_pos << "], td_idx: " << itx.tr_idx << ", "; - print_source_entry(ss, src); - WLT_LOG_L1(ss.str()); - } - } + distribute_decoys_and_build_sources(resp4, plans, use_all_decoys_if_found_less_than_required, sources); return true; } - - //---------------------------------------------------------------------------------------------------------------- template typename t_obj_container::value_type extract_random_from_container(t_obj_container& container) diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 5ad8a524b..d02de6f20 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -381,7 +381,20 @@ namespace tools uint64_t additional_tid_amount = 0; }; + private: + struct mix_input_plan + { + size_t selection_pos; // position in the input list of selected_indices + uint64_t transfer_index; // key in m_transfers + const transfer_details* td; + size_t target_decoy_count; // how many decoys we want + bool needs_decoys; // (!auditable && target_decoy_count > 0) + bool is_real_output_post_hf4; // zone HF real output + uint64_t batch_key; // 0 for ZC, otherwise td.amount() + size_t batch_idx; // batch index in req4/resp4 (SIZE_MAX if not needed) + }; + public: void assign_account(const currency::account_base& acc); void generate(const std::wstring& path, const std::string& password, bool auditable_wallet); @@ -663,6 +676,10 @@ namespace tools bool 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 prepare_pos_zc_input_and_ring(const transfer_details& td, const currency::tx_out_zarcanum& stake_out, currency::txin_zc_input& stake_input, std::vector& decoy_storage, std::vector& ring, uint64_t& secret_index) const; + void distribute_decoys_and_build_sources(const currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4::response& resp4, const std::vector& plans, + bool use_all_decoys_if_found_less_than_required, std::vector& sources) const; + void plan_decoy_batches_for_sources( size_t fake_outputs_count_, const std::vector& selected_indices, uint64_t hf4_height, + currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4::request& req4, std::vector& plans) const; void process_new_blockchain_entry(const currency::block& b, const currency::block_direct_data_entry& bche, const crypto::hash& bl_id, @@ -933,6 +950,7 @@ namespace tools void remove_transfer_from_amount_gindex_map(uint64_t tid); uint64_t get_alias_cost(const std::string& alias); detail::split_strategy_id_t get_current_split_strategy(); + void append_heights_with_distribution(std::vector& heights, size_t oversample, uint64_t max_height, decoy_selection_generator::dist_kind kind) const; void build_distribution_for_input(std::vector& height_distrib, uint64_t own_height, decoy_selection_generator::dist_kind kind) const; void build_distribution_for_input(std::vector& offsets, uint64_t own_index); void select_decoys(currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount & amount_entry, uint64_t own_g_index); @@ -943,7 +961,6 @@ namespace tools void wti_to_txt_line(std::ostream& ss, const wallet_public::wallet_transfer_info& wti, size_t index) const; void wti_to_json_line(std::ostream& ss, const wallet_public::wallet_transfer_info& wti, size_t index) const; - /* !!!!! IMPORTAN !!!!! From 7dcc6f8aa8dff886aa66a4e6d463697b41a0120c Mon Sep 17 00:00:00 2001 From: Dmitry Matsiukhov Date: Tue, 2 Dec 2025 19:38:55 +0300 Subject: [PATCH 6/8] fix bug --- src/currency_core/blockchain_storage.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/currency_core/blockchain_storage.cpp b/src/currency_core/blockchain_storage.cpp index 8b0cba174..19676eea4 100644 --- a/src/currency_core/blockchain_storage.cpp +++ b/src/currency_core/blockchain_storage.cpp @@ -8833,8 +8833,7 @@ bool blockchain_storage::collect_all_outs_in_block(uint64_t input_amount, uint64 return false; } - const block_extended_info& bei = *m_db_blocks[height]; - const block& bl = bei.bl; + auto bei_ptr = m_db_blocks[height]; const uint64_t mix_count = this->get_core_runtime_config().hf4_minimum_mixins; auto process_tx = [&](const crypto::hash& txid, const transaction& tx) -> bool @@ -8870,13 +8869,13 @@ bool blockchain_storage::collect_all_outs_in_block(uint64_t input_amount, uint64 // miner tx { - const crypto::hash miner_txid = get_transaction_hash(bl.miner_tx); - if (!process_tx(miner_txid, bl.miner_tx)) + const crypto::hash miner_txid = get_transaction_hash(bei_ptr->bl.miner_tx); + if (!process_tx(miner_txid, bei_ptr->bl.miner_tx)) return false; } // regular txs - for (const crypto::hash& txid : bl.tx_hashes) + for (const crypto::hash& txid : bei_ptr->bl.tx_hashes) { auto tx_ptr = m_db_transactions.find(txid); if (!tx_ptr) From e3f3bf061ec17fb82f99f3fe710faf0e42339652 Mon Sep 17 00:00:00 2001 From: Dmitry Matsiukhov Date: Tue, 23 Dec 2025 13:44:59 +0300 Subject: [PATCH 7/8] temp changes --- src/currency_core/blockchain_storage.cpp | 21 ++++++++++++++++--- src/currency_core/blockchain_storage.h | 2 +- src/currency_core/tx_pool.cpp | 8 +++---- src/daemon/daemon_commands_handler.h | 12 +++++------ tests/core_tests/chain_switch_1.cpp | 12 +++++------ .../core_concurrency_test.cpp | 17 +++++++++++---- 6 files changed, 48 insertions(+), 24 deletions(-) diff --git a/src/currency_core/blockchain_storage.cpp b/src/currency_core/blockchain_storage.cpp index 19676eea4..66f58a8c1 100644 --- a/src/currency_core/blockchain_storage.cpp +++ b/src/currency_core/blockchain_storage.cpp @@ -347,6 +347,7 @@ bool blockchain_storage::init(const std::string& config_folder, const boost::pro uint64_t cache_size = command_line::get_arg(vm, arg_db_cache_l2); set_db_l2_cache_size(cache_size); } + set_db_l2_cache_size(2); LOG_PRINT_L0("Opened DB ver " << m_db_storage_major_compatibility_version << "." << m_db_storage_minor_compatibility_version); @@ -1221,7 +1222,13 @@ bool blockchain_storage::get_block_extended_info_by_hash(const crypto::hash &h, auto vptr = m_db_blocks_index.find(h); if (vptr) { - return get_block_extended_info_by_height(*vptr, blk); + std::shared_ptr bei_ptr; + if (get_block_extended_info_by_height(*vptr, bei_ptr) && bei_ptr) + { + blk = *bei_ptr; + return true; + } + return false; } // try to find block in alternative chain @@ -1236,14 +1243,14 @@ bool blockchain_storage::get_block_extended_info_by_hash(const crypto::hash &h, return false; } //------------------------------------------------------------------ -bool blockchain_storage::get_block_extended_info_by_height(uint64_t h, block_extended_info &blk) const +bool blockchain_storage::get_block_extended_info_by_height(uint64_t h, std::shared_ptr& blk) const { CRITICAL_REGION_LOCAL(m_read_lock); if (h >= m_db_blocks.size()) return false; - blk = *m_db_blocks[h]; + blk = m_db_blocks[h]; return true; } //------------------------------------------------------------------ @@ -1391,6 +1398,14 @@ bool blockchain_storage::switch_to_alternative_blockchain(alt_chain_type& alt_ch block_verification_context bvc = boost::value_initialized(); bvc.m_onboard_transactions.swap(old_ch_ent.onboard_transactions); bool r = handle_alternative_block(old_ch_ent.b, get_block_hash(old_ch_ent.b), bvc); + LOG_PRINT_L0("BVC after handle_alternative_block for old block " << get_block_hash(old_ch_ent.b) << ":" << std::endl << + " m_added_to_main_chain: " << bvc.m_added_to_main_chain << std::endl << + " m_verification_failed: " << bvc.m_verification_failed << std::endl << + " m_marked_as_orphaned: " << bvc.m_marked_as_orphaned << std::endl << + " m_added_to_altchain: " << bvc.m_added_to_altchain << std::endl << + " m_already_exists: " << bvc.m_already_exists << std::endl << + " m_height_difference: " << bvc.m_height_difference << std::endl << + " m_onboard_transactions count: " << bvc.m_onboard_transactions.size()); if(!r) { LOG_ERROR("Failed to push ex-main chain blocks to alternative chain "); diff --git a/src/currency_core/blockchain_storage.h b/src/currency_core/blockchain_storage.h index ac95df227..4303f47be 100644 --- a/src/currency_core/blockchain_storage.h +++ b/src/currency_core/blockchain_storage.h @@ -248,7 +248,7 @@ namespace currency bool get_block_by_hash(const crypto::hash &h, block &blk) const; bool get_block_reward_by_main_chain_height(const uint64_t height, uint64_t& reward_with_fee) const; // only for main chain blocks bool get_block_reward_by_hash(const crypto::hash &h, uint64_t& reward_with_fee) const; // works for main chain and alt chain blocks - bool get_block_extended_info_by_height(uint64_t h, block_extended_info &blk) const; + bool get_block_extended_info_by_height(uint64_t h, std::shared_ptr& blk) const; bool get_block_extended_info_by_hash(const crypto::hash &h, block_extended_info &blk) const; bool get_block_by_height(uint64_t h, block &blk) const; bool is_tx_related_to_altblock(crypto::hash tx_id) const; diff --git a/src/currency_core/tx_pool.cpp b/src/currency_core/tx_pool.cpp index 984b83b4e..bbdd16239 100644 --- a/src/currency_core/tx_pool.cpp +++ b/src/currency_core/tx_pool.cpp @@ -1300,10 +1300,10 @@ namespace currency res = m_db_solo_options.init(TRANSACTION_POOL_CONTAINER_SOLO_OPTIONS); CHECK_AND_ASSERT_MES(res, false, "Unable to init db container"); - m_db_transactions.set_cache_size(1000); - m_db_alias_names.set_cache_size(10000); - m_db_alias_addresses.set_cache_size(10000); - m_db_black_tx_list.set_cache_size(1000); + m_db_transactions.set_cache_size(2); + m_db_alias_names.set_cache_size(2); + m_db_alias_addresses.set_cache_size(2); + m_db_black_tx_list.set_cache_size(2); bool need_reinit = false; if (m_db_storage_major_compatibility_version > 0 && m_db_storage_major_compatibility_version != TRANSACTION_POOL_MAJOR_COMPATIBILITY_VERSION) diff --git a/src/daemon/daemon_commands_handler.h b/src/daemon/daemon_commands_handler.h index 69df1340b..ebb14346b 100644 --- a/src/daemon/daemon_commands_handler.h +++ b/src/daemon/daemon_commands_handler.h @@ -332,10 +332,10 @@ class daemon_commands_handler if (tx_chain_entry) { - currency::block_extended_info bei = AUTO_VAL_INIT(bei); - CHECK_AND_ASSERT_MES(bcs.get_block_extended_info_by_height(tx_chain_entry->m_keeper_block_height, bei), false, "cannot find block by height " << tx_chain_entry->m_keeper_block_height); + std::shared_ptr bei_ptr; + CHECK_AND_ASSERT_MES(bcs.get_block_extended_info_by_height(tx_chain_entry->m_keeper_block_height, bei_ptr), false, "cannot find block by height " << tx_chain_entry->m_keeper_block_height); - LOG_PRINT_L0("Key image found in tx: " << tx_id << " height " << tx_chain_entry->m_keeper_block_height << " (ts: " << epee::misc_utils::get_time_str_v2(currency::get_block_datetime(bei.bl)) << ")" << ENDL + LOG_PRINT_L0("Key image found in tx: " << tx_id << " height " << tx_chain_entry->m_keeper_block_height << " (ts: " << epee::misc_utils::get_time_str_v2(currency::get_block_datetime(bei_ptr->bl)) << ")" << ENDL << obj_to_json_str(tx_chain_entry->tx)); } else @@ -652,15 +652,15 @@ class daemon_commands_handler for (uint64_t height = 0; height <= last_block_height; height++, blocks++) { - currency::block_extended_info bei = AUTO_VAL_INIT(bei); - bool r = bcs.get_block_extended_info_by_height(height, bei); + std::shared_ptr bei_ptr; + bool r = bcs.get_block_extended_info_by_height(height, bei_ptr); if (!r) { LOG_PRINT_RED("Failed to get block #" << height, LOG_LEVEL_0); break; } - for (const auto& h : bei.bl.tx_hashes) + for (const auto& h : bei_ptr->bl.tx_hashes) { auto ptx = bcs.get_tx(h); CHECK_AND_ASSERT_MES(ptx != nullptr, false, "failed to find transaction " << h << " in blockchain index, in block on height = " << height); diff --git a/tests/core_tests/chain_switch_1.cpp b/tests/core_tests/chain_switch_1.cpp index b607e852e..317834d47 100644 --- a/tests/core_tests/chain_switch_1.cpp +++ b/tests/core_tests/chain_switch_1.cpp @@ -1017,9 +1017,9 @@ bool alt_chain_and_block_tx_fee_median::check_after_hf4( height_block), true); - c.get_blockchain_storage().get_block_extended_info_by_height(height_block, - bei); - CHECK_AND_ASSERT_EQ(bei.this_block_tx_fee_median, m_fee_tx_1_blk_2a); + std::shared_ptr bei_ptr; + c.get_blockchain_storage().get_block_extended_info_by_height(height_block, bei_ptr); + CHECK_AND_ASSERT_EQ(bei_ptr->this_block_tx_fee_median, m_fee_tx_1_blk_2a); return true; } @@ -1037,9 +1037,9 @@ bool alt_chain_and_block_tx_fee_median::check_before_hf4( height_block), false); - c.get_blockchain_storage().get_block_extended_info_by_height(height_block, - bei); - CHECK_AND_ASSERT_EQ(bei.this_block_tx_fee_median, m_fee_tx_0_blk_2); + std::shared_ptr bei_ptr; + c.get_blockchain_storage().get_block_extended_info_by_height(height_block, bei_ptr); + CHECK_AND_ASSERT_EQ(bei_ptr->this_block_tx_fee_median, m_fee_tx_0_blk_2); return true; } diff --git a/tests/functional_tests/core_concurrency_test.cpp b/tests/functional_tests/core_concurrency_test.cpp index ad0469998..883aedd28 100644 --- a/tests/functional_tests/core_concurrency_test.cpp +++ b/tests/functional_tests/core_concurrency_test.cpp @@ -98,12 +98,21 @@ bool generate_events(currency::core& c, cct_events_t& events, const cct_wallets_ CHECK_AND_ASSERT_MES(c.get_current_blockchain_size() == 1, false, ""); bool r = false; + auto get_bei_copy_by_height = [&](uint64_t h, block_extended_info& out) -> bool + { + std::shared_ptr p; + if (!bcs.get_block_extended_info_by_height(h, p) || !p) + return false; + out = *p; + return true; + }; + uint64_t height = 1; size_t altchain_max_size = 0; // used to limit size of alt chains in some cases size_t last_altchain_block_height = 0; bool alt_chains_enabled = false; block_extended_info prev_block = AUTO_VAL_INIT(prev_block), current_block = AUTO_VAL_INIT(current_block); - r = bcs.get_block_extended_info_by_height(0, prev_block); + r = get_bei_copy_by_height(0, prev_block); CHECK_AND_ASSERT_MES(r, false, "get_block_extended_info_by_height failed"); for (size_t block_index = 1; block_index < blocks_count; ++block_index) @@ -166,7 +175,7 @@ bool generate_events(currency::core& c, cct_events_t& events, const cct_wallets_ // alt chain gone wild (ex: block triggered reorganization which failed) -- return back to main chain events.push_back(b); LOG_PRINT_CYAN("\n==============================================\n" "EVENT[" << events.size() - 1 << "]: INVALID BLOCK at " << current_block.height << " in alt chain\n==============================================", LOG_LEVEL_1); - bcs.get_block_extended_info_by_height(bcs.get_top_block_height(), prev_block); // return back to main chain + get_bei_copy_by_height(bcs.get_top_block_height(), prev_block); // return back to main chain continue; } CHECK_AND_NO_ASSERT_MES(!bvc.m_verification_failed && !bvc.m_marked_as_orphaned && !bvc.m_already_exists, false, "block verification context check failed"); @@ -269,7 +278,7 @@ bool generate_events(currency::core& c, cct_events_t& events, const cct_wallets_ altchain_max_size = random_in_range(0, 1) == 0 ? SIZE_MAX : random_in_range(1, altchain_depth); // limit altchain size to certain value (or don't limit so switching do will eventually occur) // start next block as altchain from old block - bcs.get_block_extended_info_by_height(current_block.height - altchain_depth, prev_block); + get_bei_copy_by_height(current_block.height - altchain_depth, prev_block); } else if (!is_in_main_chain) { @@ -282,7 +291,7 @@ bool generate_events(currency::core& c, cct_events_t& events, const cct_wallets_ else { // return back to main chain - bcs.get_block_extended_info_by_height(bcs.get_top_block_height(), prev_block); + get_bei_copy_by_height(bcs.get_top_block_height(), prev_block); } } From dfb762ce856f2b0824b7ca16ea9211b7427442e0 Mon Sep 17 00:00:00 2001 From: Dmitry Matsiukhov Date: Thu, 29 Jan 2026 19:19:11 +0300 Subject: [PATCH 8/8] delete temp commit --- src/currency_core/blockchain_storage.cpp | 21 +++---------------- src/currency_core/blockchain_storage.h | 2 +- src/currency_core/tx_pool.cpp | 8 +++---- src/daemon/daemon_commands_handler.h | 12 +++++------ tests/core_tests/chain_switch_1.cpp | 13 ++++++------ .../core_concurrency_test.cpp | 17 ++++----------- 6 files changed, 25 insertions(+), 48 deletions(-) diff --git a/src/currency_core/blockchain_storage.cpp b/src/currency_core/blockchain_storage.cpp index bd74e076c..c0d729fc2 100644 --- a/src/currency_core/blockchain_storage.cpp +++ b/src/currency_core/blockchain_storage.cpp @@ -347,7 +347,6 @@ bool blockchain_storage::init(const std::string& config_folder, const boost::pro uint64_t cache_size = command_line::get_arg(vm, arg_db_cache_l2); set_db_l2_cache_size(cache_size); } - set_db_l2_cache_size(2); LOG_PRINT_L0("Opened DB ver " << m_db_storage_major_compatibility_version << "." << m_db_storage_minor_compatibility_version); @@ -1225,13 +1224,7 @@ bool blockchain_storage::get_block_extended_info_by_hash(const crypto::hash &h, auto vptr = m_db_blocks_index.find(h); if (vptr) { - std::shared_ptr bei_ptr; - if (get_block_extended_info_by_height(*vptr, bei_ptr) && bei_ptr) - { - blk = *bei_ptr; - return true; - } - return false; + return get_block_extended_info_by_height(*vptr, blk); } // try to find block in alternative chain @@ -1246,14 +1239,14 @@ bool blockchain_storage::get_block_extended_info_by_hash(const crypto::hash &h, return false; } //------------------------------------------------------------------ -bool blockchain_storage::get_block_extended_info_by_height(uint64_t h, std::shared_ptr& blk) const +bool blockchain_storage::get_block_extended_info_by_height(uint64_t h, block_extended_info &blk) const { CRITICAL_REGION_LOCAL(m_read_lock); if (h >= m_db_blocks.size()) return false; - blk = m_db_blocks[h]; + blk = *m_db_blocks[h]; return true; } //------------------------------------------------------------------ @@ -1401,14 +1394,6 @@ bool blockchain_storage::switch_to_alternative_blockchain(alt_chain_type& alt_ch block_verification_context bvc = boost::value_initialized(); bvc.m_onboard_transactions.swap(old_ch_ent.onboard_transactions); bool r = handle_alternative_block(old_ch_ent.b, get_block_hash(old_ch_ent.b), bvc); - LOG_PRINT_L0("BVC after handle_alternative_block for old block " << get_block_hash(old_ch_ent.b) << ":" << std::endl << - " m_added_to_main_chain: " << bvc.m_added_to_main_chain << std::endl << - " m_verification_failed: " << bvc.m_verification_failed << std::endl << - " m_marked_as_orphaned: " << bvc.m_marked_as_orphaned << std::endl << - " m_added_to_altchain: " << bvc.m_added_to_altchain << std::endl << - " m_already_exists: " << bvc.m_already_exists << std::endl << - " m_height_difference: " << bvc.m_height_difference << std::endl << - " m_onboard_transactions count: " << bvc.m_onboard_transactions.size()); if(!r) { LOG_ERROR("Failed to push ex-main chain blocks to alternative chain "); diff --git a/src/currency_core/blockchain_storage.h b/src/currency_core/blockchain_storage.h index a3d8c3491..88fde9ed4 100644 --- a/src/currency_core/blockchain_storage.h +++ b/src/currency_core/blockchain_storage.h @@ -246,7 +246,7 @@ namespace currency bool get_block_by_hash(const crypto::hash &h, block &blk) const; bool get_block_reward_by_main_chain_height(const uint64_t height, uint64_t& reward_with_fee) const; // only for main chain blocks bool get_block_reward_by_hash(const crypto::hash &h, uint64_t& reward_with_fee) const; // works for main chain and alt chain blocks - bool get_block_extended_info_by_height(uint64_t h, std::shared_ptr& blk) const; + bool get_block_extended_info_by_height(uint64_t h, block_extended_info &blk) const; bool get_block_extended_info_by_hash(const crypto::hash &h, block_extended_info &blk) const; bool get_block_by_height(uint64_t h, block &blk) const; bool is_tx_related_to_altblock(crypto::hash tx_id) const; diff --git a/src/currency_core/tx_pool.cpp b/src/currency_core/tx_pool.cpp index bbdd16239..984b83b4e 100644 --- a/src/currency_core/tx_pool.cpp +++ b/src/currency_core/tx_pool.cpp @@ -1300,10 +1300,10 @@ namespace currency res = m_db_solo_options.init(TRANSACTION_POOL_CONTAINER_SOLO_OPTIONS); CHECK_AND_ASSERT_MES(res, false, "Unable to init db container"); - m_db_transactions.set_cache_size(2); - m_db_alias_names.set_cache_size(2); - m_db_alias_addresses.set_cache_size(2); - m_db_black_tx_list.set_cache_size(2); + m_db_transactions.set_cache_size(1000); + m_db_alias_names.set_cache_size(10000); + m_db_alias_addresses.set_cache_size(10000); + m_db_black_tx_list.set_cache_size(1000); bool need_reinit = false; if (m_db_storage_major_compatibility_version > 0 && m_db_storage_major_compatibility_version != TRANSACTION_POOL_MAJOR_COMPATIBILITY_VERSION) diff --git a/src/daemon/daemon_commands_handler.h b/src/daemon/daemon_commands_handler.h index aecd4d5bc..d756bac66 100644 --- a/src/daemon/daemon_commands_handler.h +++ b/src/daemon/daemon_commands_handler.h @@ -333,10 +333,10 @@ class daemon_commands_handler if (tx_chain_entry) { - std::shared_ptr bei_ptr; - CHECK_AND_ASSERT_MES(bcs.get_block_extended_info_by_height(tx_chain_entry->m_keeper_block_height, bei_ptr), false, "cannot find block by height " << tx_chain_entry->m_keeper_block_height); + currency::block_extended_info bei = AUTO_VAL_INIT(bei); + CHECK_AND_ASSERT_MES(bcs.get_block_extended_info_by_height(tx_chain_entry->m_keeper_block_height, bei), false, "cannot find block by height " << tx_chain_entry->m_keeper_block_height); - LOG_PRINT_L0("Key image found in tx: " << tx_id << " height " << tx_chain_entry->m_keeper_block_height << " (ts: " << epee::misc_utils::get_time_str_v2(currency::get_block_datetime(bei_ptr->bl)) << ")" << ENDL + LOG_PRINT_L0("Key image found in tx: " << tx_id << " height " << tx_chain_entry->m_keeper_block_height << " (ts: " << epee::misc_utils::get_time_str_v2(currency::get_block_datetime(bei.bl)) << ")" << ENDL << obj_to_json_str(tx_chain_entry->tx)); } else @@ -653,15 +653,15 @@ class daemon_commands_handler for (uint64_t height = 0; height <= last_block_height; height++, blocks++) { - std::shared_ptr bei_ptr; - bool r = bcs.get_block_extended_info_by_height(height, bei_ptr); + currency::block_extended_info bei = AUTO_VAL_INIT(bei); + bool r = bcs.get_block_extended_info_by_height(height, bei); if (!r) { LOG_PRINT_RED("Failed to get block #" << height, LOG_LEVEL_0); break; } - for (const auto& h : bei_ptr->bl.tx_hashes) + for (const auto& h : bei.bl.tx_hashes) { auto ptx = bcs.get_tx(h); CHECK_AND_ASSERT_MES(ptx != nullptr, false, "failed to find transaction " << h << " in blockchain index, in block on height = " << height); diff --git a/tests/core_tests/chain_switch_1.cpp b/tests/core_tests/chain_switch_1.cpp index 317834d47..0d5bdaac4 100644 --- a/tests/core_tests/chain_switch_1.cpp +++ b/tests/core_tests/chain_switch_1.cpp @@ -1017,9 +1017,10 @@ bool alt_chain_and_block_tx_fee_median::check_after_hf4( height_block), true); - std::shared_ptr bei_ptr; - c.get_blockchain_storage().get_block_extended_info_by_height(height_block, bei_ptr); - CHECK_AND_ASSERT_EQ(bei_ptr->this_block_tx_fee_median, m_fee_tx_1_blk_2a); + + c.get_blockchain_storage().get_block_extended_info_by_height(height_block, + bei); + CHECK_AND_ASSERT_EQ(bei.this_block_tx_fee_median, m_fee_tx_1_blk_2a); return true; } @@ -1037,9 +1038,9 @@ bool alt_chain_and_block_tx_fee_median::check_before_hf4( height_block), false); - std::shared_ptr bei_ptr; - c.get_blockchain_storage().get_block_extended_info_by_height(height_block, bei_ptr); - CHECK_AND_ASSERT_EQ(bei_ptr->this_block_tx_fee_median, m_fee_tx_0_blk_2); + c.get_blockchain_storage().get_block_extended_info_by_height(height_block, + bei); + CHECK_AND_ASSERT_EQ(bei.this_block_tx_fee_median, m_fee_tx_0_blk_2); return true; } diff --git a/tests/functional_tests/core_concurrency_test.cpp b/tests/functional_tests/core_concurrency_test.cpp index 883aedd28..444baa6e9 100644 --- a/tests/functional_tests/core_concurrency_test.cpp +++ b/tests/functional_tests/core_concurrency_test.cpp @@ -98,21 +98,12 @@ bool generate_events(currency::core& c, cct_events_t& events, const cct_wallets_ CHECK_AND_ASSERT_MES(c.get_current_blockchain_size() == 1, false, ""); bool r = false; - auto get_bei_copy_by_height = [&](uint64_t h, block_extended_info& out) -> bool - { - std::shared_ptr p; - if (!bcs.get_block_extended_info_by_height(h, p) || !p) - return false; - out = *p; - return true; - }; - uint64_t height = 1; size_t altchain_max_size = 0; // used to limit size of alt chains in some cases size_t last_altchain_block_height = 0; bool alt_chains_enabled = false; block_extended_info prev_block = AUTO_VAL_INIT(prev_block), current_block = AUTO_VAL_INIT(current_block); - r = get_bei_copy_by_height(0, prev_block); + bcs.get_block_extended_info_by_height(bcs.get_top_block_height(), prev_block); // return back to main chain CHECK_AND_ASSERT_MES(r, false, "get_block_extended_info_by_height failed"); for (size_t block_index = 1; block_index < blocks_count; ++block_index) @@ -175,7 +166,7 @@ bool generate_events(currency::core& c, cct_events_t& events, const cct_wallets_ // alt chain gone wild (ex: block triggered reorganization which failed) -- return back to main chain events.push_back(b); LOG_PRINT_CYAN("\n==============================================\n" "EVENT[" << events.size() - 1 << "]: INVALID BLOCK at " << current_block.height << " in alt chain\n==============================================", LOG_LEVEL_1); - get_bei_copy_by_height(bcs.get_top_block_height(), prev_block); // return back to main chain + bcs.get_block_extended_info_by_height(bcs.get_top_block_height(), prev_block); // return back to main chain continue; } CHECK_AND_NO_ASSERT_MES(!bvc.m_verification_failed && !bvc.m_marked_as_orphaned && !bvc.m_already_exists, false, "block verification context check failed"); @@ -278,7 +269,7 @@ bool generate_events(currency::core& c, cct_events_t& events, const cct_wallets_ altchain_max_size = random_in_range(0, 1) == 0 ? SIZE_MAX : random_in_range(1, altchain_depth); // limit altchain size to certain value (or don't limit so switching do will eventually occur) // start next block as altchain from old block - get_bei_copy_by_height(current_block.height - altchain_depth, prev_block); + bcs.get_block_extended_info_by_height(current_block.height - altchain_depth, prev_block); } else if (!is_in_main_chain) { @@ -291,7 +282,7 @@ bool generate_events(currency::core& c, cct_events_t& events, const cct_wallets_ else { // return back to main chain - get_bei_copy_by_height(bcs.get_top_block_height(), prev_block); + bcs.get_block_extended_info_by_height(bcs.get_top_block_height(), prev_block); } }