Skip to content
Open
153 changes: 80 additions & 73 deletions src/currency_core/blockchain_storage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint64_t> seen_heights;
std::unordered_set<uint64_t> 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<uint64_t> seen_heights;
std::unordered_set<uint64_t> 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<COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry> 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<COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry> 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;
}
//------------------------------------------------------------------
Expand Down Expand Up @@ -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<COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry>& outs) const
bool blockchain_storage::collect_all_outs_in_block(uint64_t input_amount, uint64_t height, std::vector<COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry>& outs) const
{
CRITICAL_REGION_LOCAL(m_read_lock);

Expand All @@ -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
Expand All @@ -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;
Expand All @@ -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);
}
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/currency_core/blockchain_storage.h
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ namespace currency
//void get_all_known_block_ids(std::list<crypto::hash> &main, std::list<crypto::hash> &alt, std::list<crypto::hash> &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<COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry>& outs) const;
bool collect_all_outs_in_block(uint64_t input_amount, uint64_t height, std::vector<COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry>& outs) const;

bc_attachment_services_manager& get_attachment_services_manager(){ return m_services_mgr; }

Expand Down
7 changes: 6 additions & 1 deletion src/rpc/core_rpc_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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))
{
Expand Down
31 changes: 24 additions & 7 deletions src/rpc/core_rpc_server_commands_defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint64_t> 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<uint64_t> heights; // array heights derived from decoy selection algorithm, number of heights expected to be not less than minimal ring size
std::vector<request_batch> 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()
};
Expand All @@ -689,14 +698,22 @@ namespace currency
END_KV_SERIALIZE_MAP()
};

struct response
struct blocks_batch
{
std::vector<outputs_in_block> blocks;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(blocks)
END_KV_SERIALIZE_MAP()
};

struct response
{
std::vector<blocks_batch> 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()
};

Expand Down
Loading