diff --git a/src/currency_core/blockchain_storage.cpp b/src/currency_core/blockchain_storage.cpp index 16994f487..c0d729fc2 100644 --- a/src/currency_core/blockchain_storage.cpp +++ b/src/currency_core/blockchain_storage.cpp @@ -3173,96 +3173,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)) + // 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; + } + } + + // 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; } //------------------------------------------------------------------ @@ -8736,7 +8744,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); @@ -8745,8 +8753,7 @@ bool blockchain_storage::collect_all_outs_in_block(uint64_t height, std::vector< 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 @@ -8755,7 +8762,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; @@ -8772,7 +8778,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); } @@ -8782,13 +8789,13 @@ bool blockchain_storage::collect_all_outs_in_block(uint64_t height, std::vector< // 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) diff --git a/src/currency_core/blockchain_storage.h b/src/currency_core/blockchain_storage.h index 424d2c6fa..88fde9ed4 100644 --- a/src/currency_core/blockchain_storage.h +++ b/src/currency_core/blockchain_storage.h @@ -253,7 +253,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 c865cae99..f02bb4726 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -500,7 +500,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 f9969d289..b4b54633c 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -662,17 +662,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() }; @@ -689,14 +698,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 ae0e114d2..86c74fe2e 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -61,6 +61,10 @@ 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; +using tx_output_entry = currency::tx_source_entry::output_entry; + namespace tools { wallet2::wallet2() @@ -4730,111 +4734,341 @@ 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 +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) { - bool r = false; - ring.clear(); - secret_index = 0; // index of the real stake output - stake_input.key_offsets.clear(); - decoy_storage.clear(); + decoy_storage_out.clear(); - COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS4::response decoys_resp = AUTO_VAL_INIT(decoys_resp); + 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()); - // 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; + bool include_one_noncb = ((crypto::rand() % 100) < WALLET_NONCB_SET_PROB_PERCENT) && !noncb_candidates.empty(); - if (required_decoys_count > 0 && !is_auditable()) + 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 { - 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 - 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); + 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; + }; - 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"); + 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; + } +} + +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(); - std::vector coinbase_candidates; - std::vector noncb_candidates; - coinbase_candidates.reserve(decoys_resp.blocks.size()); + for (const auto& oe : resp_outs) + { + if (oe.global_amount_index == real_gindex) + continue; - for (const auto& blk : decoys_resp.blocks) + 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 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 : 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 (blk.outs.empty()) + if (!entry_allowed(oe)) 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.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); - } + 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(); +void wallet2::append_heights_with_distribution(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; + }; - auto rnd_shuffle = [](auto& v) + 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) { - 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); + 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; - decoy_storage.reserve(required_decoys_count + 1); // +1 real + append_heights_with_distribution(req4.batches[batch_idx].heights, overs, max_h, decoy_selection_generator::dist_kind::regular); + } - std::unordered_set used_gindices; - used_gindices.reserve(required_decoys_count + 1); - used_gindices.insert(td.m_global_output_index); + 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); - auto take_next_unique = [&](std::vector& out_pool, size_t& cursor) -> bool + plans.push_back(mix_input_plan { - 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; - }; + /*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; - size_t cb_cur = 0, nc_cur = 0; + 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(); - if (include_one_noncb) - take_next_unique(noncb_candidates, nc_cur); + std::vector local_decoys; - // get the rest from coinbase if its out, fallback to non-сoinbase - while (decoy_storage.size() < required_decoys_count) + if (plan.needs_decoys) { - if (take_next_unique(coinbase_candidates, cb_cur)) + 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; - // coinbase is out of stock, adding non-coinbase - if (!take_next_unique(noncb_candidates, nc_cur)) - break; // have no more decoys + 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; } - 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() << ")"); + 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 +{ + bool r = false; + ring.clear(); + secret_index = 0; // index of the real stake output + stake_input.key_offsets.clear(); + decoy_storage.clear(); + + // 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); + 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; + // 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_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.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; }, + coinbase_candidates, noncb_candidates + ); + + 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() << ")"); // 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); @@ -4868,7 +5102,6 @@ bool wallet2::prepare_pos_zc_input_and_ring(const transfer_details& td, const cu 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 { @@ -6714,211 +6947,39 @@ 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) { - typedef COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry out_entry; - typedef currency::tx_source_entry::output_entry tx_output_entry; + prefetch_global_indicies_if_needed(selected_indices); - 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(); + const uint64_t hf4_height = m_core_runtime_config.hard_forks.get_height_the_hardfork_active_after(ZANO_HARDFORK_04_ZARCANUM); - 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(); + 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); - 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"); + std::vector plans; + plan_decoy_batches_for_sources(fake_outputs_count_, selected_indices, hf4_height, req4, plans); - 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 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()); - 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); - } - } + 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"); } - - //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) + else { - auto it = m_transfers.find(J); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it != m_transfers.end(), "internal error: J " << J << " not found in m_transfers"); - - - sources.push_back(AUTO_VAL_INIT(currency::tx_source_entry())); - currency::tx_source_entry& src = sources.back(); - transfer_details& td = it->second; - src.transfer_index = J; - src.amount = td.amount(); - src.asset_id = td.get_asset_id(); - size_t fake_outputs_count = fake_outputs_count_; - //redefine for hardfork - if (td.is_zc() && !this->is_auditable()) - fake_outputs_count = m_core_runtime_config.hf4_minimum_mixins; - - - //paste mixin transaction - if (daemon_resp.outs.size()) - { - if (td.is_zc()) - { - //get rid of unneeded - select_decoys(daemon_resp.outs[i], td.m_global_output_index); - } - else - { - //TODO: make sure we have exact count needed - } - - daemon_resp.outs[i].outs.sort([](const out_entry& a, const out_entry& b) {return a.global_amount_index < b.global_amount_index; }); - for (out_entry& daemon_oe : daemon_resp.outs[i].outs) - { - if (td.m_global_output_index == daemon_oe.global_amount_index) - continue; - tx_output_entry oe = AUTO_VAL_INIT(oe); - oe.amount_commitment = daemon_oe.amount_commitment; - oe.concealing_point = daemon_oe.concealing_point; - oe.out_reference = daemon_oe.global_amount_index; - oe.stealth_address = daemon_oe.stealth_address; - oe.blinded_asset_id = daemon_oe.blinded_asset_id; // TODO @#@# BAD DESIGN, consider refactoring -- sowle - src.outputs.push_back(oe); - if (src.outputs.size() >= fake_outputs_count) - break; - } - } - - //paste real transaction to the random index - auto it_to_insert = std::find_if(src.outputs.begin(), src.outputs.end(), [&](const tx_output_entry& a) - { - if (a.out_reference.type().hash_code() == typeid(uint64_t).hash_code()) - return static_cast(boost::get(a.out_reference) >= td.m_global_output_index); - return false; // TODO: implement deterministics real output placement in case there're ref_by_id outs - }); - //size_t real_index = src.outputs.size() ? (rand() % src.outputs.size() ):0; - tx_output_entry real_oe = AUTO_VAL_INIT(real_oe); - real_oe.out_reference = td.m_global_output_index; // TODO: use ref_by_id when neccessary - VARIANT_SWITCH_BEGIN(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index]); - VARIANT_CASE_CONST(tx_out_bare, o) - { - VARIANT_SWITCH_BEGIN(o.target); - VARIANT_CASE_CONST(txout_to_key, o) - real_oe.stealth_address = o.key; - VARIANT_CASE_OTHER() - { - WLT_THROW_IF_FALSE_WITH_CODE(false, - "Internal error: unexpected type of target: " << o.target.type().name(), - API_RETURN_CODE_INTERNAL_ERROR); - } - VARIANT_SWITCH_END(); - } - VARIANT_CASE_CONST(tx_out_zarcanum, o) - real_oe.amount_commitment = o.amount_commitment; // TODO @#@# consider using shorter code like in sweep_below() (or better reuse it) - real_oe.concealing_point = o.concealing_point; - real_oe.stealth_address = o.stealth_address; - real_oe.blinded_asset_id = o.blinded_asset_id; - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.is_zc(), "transfer #" << J << ", amount: " << print_money_brief(td.amount(), 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 interted_it = src.outputs.insert(it_to_insert, real_oe); - src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_ptx_wallet_info->m_tx); - src.real_output = interted_it - src.outputs.begin(); - src.real_output_in_tx_index = td.m_internal_output_index; - - if (epee::log_space::get_set_log_detalisation_level() >= LOG_LEVEL_1) - { - std::stringstream ss; - ss << "source entry [" << i << "], td_idx: " << J << ", "; - print_source_entry(ss, src); - WLT_LOG_L1(ss.str()); - } - - ++i; + resp4.blocks_batches.clear(); } + + 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 bc60fbd1b..88321c5fc 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -359,7 +359,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); @@ -651,9 +664,11 @@ 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; + 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, @@ -923,6 +938,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); @@ -935,8 +951,6 @@ namespace tools bool send_block_via_socks5(const currency::block& bl); - - /* !!!!! IMPORTAN !!!!! diff --git a/tests/core_tests/chain_switch_1.cpp b/tests/core_tests/chain_switch_1.cpp index b607e852e..0d5bdaac4 100644 --- a/tests/core_tests/chain_switch_1.cpp +++ b/tests/core_tests/chain_switch_1.cpp @@ -1017,6 +1017,7 @@ 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); diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index 5b4b7a6ef..036007c10 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; + } } } diff --git a/tests/functional_tests/core_concurrency_test.cpp b/tests/functional_tests/core_concurrency_test.cpp index ad0469998..444baa6e9 100644 --- a/tests/functional_tests/core_concurrency_test.cpp +++ b/tests/functional_tests/core_concurrency_test.cpp @@ -103,7 +103,7 @@ bool generate_events(currency::core& c, cct_events_t& events, const cct_wallets_ 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); + 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)