forked from lthn/blockchain
decoy selection algo added
This commit is contained in:
parent
5b4a3e3a38
commit
04103dea7f
13 changed files with 2256 additions and 100 deletions
|
|
@ -2660,6 +2660,149 @@ bool blockchain_storage::get_random_outs_for_amounts(const COMMAND_RPC_GET_RANDO
|
|||
return true;
|
||||
}
|
||||
//------------------------------------------------------------------
|
||||
bool blockchain_storage::get_target_outs_for_amount_prezarcanum(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS2::request& req, const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS2::offsets_distribution& details, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& result_outs, std::map<uint64_t, uint64_t>& amounts_to_up_index_limit_cache) const
|
||||
{
|
||||
size_t decoys_count = details.offsets.size();
|
||||
uint64_t amount = details.amount;
|
||||
|
||||
uint64_t outs_container_size = m_db_outputs.get_item_size(details.amount);
|
||||
if (!outs_container_size)
|
||||
{
|
||||
LOG_ERROR("COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS: not outs for amount " << amount << ", wallet should use some real outs when it lookup for some mix, so, at least one out for this amount should exist");
|
||||
return false;//actually this is strange situation, wallet should use some real outs when it lookup for some mix, so, at least one out for this amount should exist
|
||||
}
|
||||
//it is not good idea to use top fresh outs, because it increases possibility of transaction canceling on split
|
||||
//lets find upper bound of not fresh outs
|
||||
size_t up_index_limit = 0;
|
||||
auto it_limit = amounts_to_up_index_limit_cache.find(amount);
|
||||
if (it_limit == amounts_to_up_index_limit_cache.end())
|
||||
{
|
||||
up_index_limit = find_end_of_allowed_index(amount);
|
||||
amounts_to_up_index_limit_cache[up_index_limit];
|
||||
}
|
||||
else
|
||||
{
|
||||
up_index_limit = it_limit->second;
|
||||
}
|
||||
|
||||
CHECK_AND_ASSERT_MES(up_index_limit <= outs_container_size, false, "internal error: find_end_of_allowed_index returned wrong index=" << up_index_limit << ", with amount_outs.size = " << outs_container_size);
|
||||
if (up_index_limit >= decoys_count)
|
||||
{
|
||||
std::set<size_t> used;
|
||||
size_t try_count = 0;
|
||||
for (uint64_t j = 0; j != decoys_count && try_count < up_index_limit;)
|
||||
{
|
||||
size_t g_index = crypto::rand<size_t>() % up_index_limit;
|
||||
if (used.count(g_index))
|
||||
continue;
|
||||
bool added = add_out_to_get_random_outs(result_outs, amount, g_index, decoys_count, req.use_forced_mix_outs, req.height_upper_limit);
|
||||
used.insert(g_index);
|
||||
if (added)
|
||||
++j;
|
||||
++try_count;
|
||||
}
|
||||
if (result_outs.outs.size() < decoys_count)
|
||||
{
|
||||
LOG_PRINT_YELLOW("Not enough inputs for amount " << print_money_brief(amount) << ", needed " << decoys_count << ", added " << result_outs.outs.size() << " good outs from " << up_index_limit << " unlocked of " << outs_container_size << " total", LOG_LEVEL_0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t added = 0;
|
||||
for (size_t i = 0; i != up_index_limit; i++)
|
||||
added += add_out_to_get_random_outs(result_outs, amount, i, decoys_count, req.use_forced_mix_outs, req.height_upper_limit) ? 1 : 0;
|
||||
LOG_PRINT_YELLOW("Not enough inputs for amount " << print_money_brief(amount) << ", needed " << decoys_count << ", added " << added << " good outs from " << up_index_limit << " unlocked of " << outs_container_size << " total - respond with all good outs", LOG_LEVEL_0);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
//------------------------------------------------------------------
|
||||
|
||||
bool blockchain_storage::get_target_outs_for_amount_postzarcanum(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS2::request& req, const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS2::offsets_distribution& details, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& result_outs, std::map<uint64_t, uint64_t>& amounts_to_up_index_limit_cache) const
|
||||
{
|
||||
size_t decoys_count = details.offsets.size();
|
||||
for (auto offset : details.offsets)
|
||||
{
|
||||
//perfectly we would need to find transaction's output on the given height, with the given probability
|
||||
//of being coinbase(coinbase outputs should be included less in decoy selection algorithm)
|
||||
bool is_coinbase = (crypto::rand<uint64_t>() % 101) > req.coinbase_percents ? false : true;
|
||||
|
||||
//TODO: Consider including PoW coinbase to transactions(does it needed?)
|
||||
|
||||
// convert offset to estimated height
|
||||
uint64_t estimated_h = this->get_current_blockchain_size() - 1 - offset;
|
||||
//make sure it's after zc hardfork
|
||||
if (estimated_h < m_core_runtime_config.hard_forks.m_height_the_hardfork_n_active_after[ZANO_HARDFORK_04_ZARCANUM])
|
||||
{
|
||||
LOG_ERROR("Wrong estimated offset(" << offset << "), it hits zone before zarcanum hardfork");
|
||||
return false;
|
||||
}
|
||||
|
||||
#define TARGET_RANDOM_OUTS_SELECTIOM_POOL_MIN 10
|
||||
//try to find output around given H
|
||||
std::vector<uint64_t> selected_global_indexes;
|
||||
|
||||
|
||||
auto process_tx = [&](const crypto::hash& tx_id) {
|
||||
|
||||
auto tx_ptr = m_db_transactions.find(tx_id);
|
||||
CHECK_AND_ASSERT_THROW_MES(tx_ptr, "internal error: tx_id " << tx_id << " around estimated_h = " << estimated_h << " not found in db");
|
||||
//go through tx outputs
|
||||
for (size_t i = 0; i != tx_ptr->tx.vout.size(); i++)
|
||||
{
|
||||
if (tx_ptr->tx.vout[i].type() != typeid(tx_out_zarcanum))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const tx_out_zarcanum& z_out = boost::get<tx_out_zarcanum>(tx_ptr->tx.vout[i]);
|
||||
|
||||
// NOTE: second part of condition (mix_attr >= CURRENCY_TO_KEY_OUT_FORCED_MIX_LOWER_BOUND && ..) might be not accurate
|
||||
// since the wallet might want to request more inputs then it planning to do mixins. For now let's keep it this way and fix
|
||||
// it if we see the problems about it.
|
||||
if (z_out.mix_attr == CURRENCY_TO_KEY_OUT_FORCED_NO_MIX || (z_out.mix_attr >= CURRENCY_TO_KEY_OUT_FORCED_MIX_LOWER_BOUND && z_out.mix_attr < details.offsets.size()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// skip spent outptus
|
||||
if (tx_ptr->m_spent_flags[i])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// add output
|
||||
// note: code that will process selected_global_indes will be revisiting transactions entries to obtain all
|
||||
// needed data, that should work relatively effective because of on-top-of-db cache keep daya unserialized
|
||||
selected_global_indexes.push_back(selected_global_indexes[i]);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
while (selected_global_indexes.size() < TARGET_RANDOM_OUTS_SELECTIOM_POOL_MIN)
|
||||
{
|
||||
auto block_ptr = m_db_blocks.get(estimated_h--);
|
||||
if (is_coinbase && is_pos_block(block_ptr->bl) )
|
||||
{
|
||||
process_tx(get_transaction_hash(block_ptr->bl.miner_tx));
|
||||
}
|
||||
else
|
||||
{
|
||||
//looking for regular output of regular transactions
|
||||
for (auto tx_id : block_ptr->bl.tx_hashes)
|
||||
{
|
||||
process_tx(tx_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//pick up a random output from selected_global_indes
|
||||
uint64_t global_index = selected_global_indexes[crypto::rand<uint64_t>() % selected_global_indexes.size()];
|
||||
bool res = add_out_to_get_random_outs(result_outs, details.amount, global_index, details.offsets.size(), req.use_forced_mix_outs, req.height_upper_limit);
|
||||
CHECK_AND_ASSERT_THROW_MES(res, "Failed to add_out_to_get_random_outs([" << global_index << "]) at postzarcanum era");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
//------------------------------------------------------------------
|
||||
bool blockchain_storage::get_random_outs_for_amounts2(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS2::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS2::response& res)const
|
||||
{
|
||||
CRITICAL_REGION_LOCAL(m_read_lock);
|
||||
|
|
@ -2669,67 +2812,19 @@ bool blockchain_storage::get_random_outs_for_amounts2(const COMMAND_RPC_GET_RAND
|
|||
for (size_t i = 0; i != req.amounts.size(); i++)
|
||||
{
|
||||
uint64_t amount = req.amounts[i].amount;
|
||||
const std::list<uint64_t>& offsets = req.amounts[i].offsets;
|
||||
//const std::vector<uint64_t>& offsets = req.amounts[i].offsets;
|
||||
COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& result_outs = *res.outs.insert(res.outs.end(), COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount());
|
||||
result_outs.amount = amount;
|
||||
|
||||
uint64_t zc_hard_fork_after_h = m_core_runtime_config.hard_forks[ZANO_HARDFORK_04_ZARCANUM];
|
||||
for (auto it = offsets.begin(); it != offsets.end(); it++)
|
||||
if (amount == 0)
|
||||
{
|
||||
uint64_t target_height =
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
uint64_t outs_container_size = m_db_outputs.get_item_size(amount);
|
||||
if (!outs_container_size)
|
||||
{
|
||||
LOG_ERROR("COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS: not outs for amount " << amount << ", wallet should use some real outs when it lookup for some mix, so, at least one out for this amount should exist");
|
||||
continue;//actually this is strange situation, wallet should use some real outs when it lookup for some mix, so, at least one out for this amount should exist
|
||||
}
|
||||
//it is not good idea to use top fresh outs, because it increases possibility of transaction canceling on split
|
||||
//lets find upper bound of not fresh outs
|
||||
size_t up_index_limit = 0;
|
||||
auto it_limit = amounts_to_up_index_limit_cache.find(amount);
|
||||
if (it_limit == amounts_to_up_index_limit_cache.end())
|
||||
{
|
||||
up_index_limit = find_end_of_allowed_index(amount);
|
||||
amounts_to_up_index_limit_cache[up_index_limit];
|
||||
//zarcanum era inputs
|
||||
get_target_outs_for_amount_postzarcanum(req, req.amounts[i], result_outs, amounts_to_up_index_limit_cache);
|
||||
}
|
||||
else
|
||||
{
|
||||
up_index_limit = it_limit->second;
|
||||
}
|
||||
|
||||
CHECK_AND_ASSERT_MES(up_index_limit <= outs_container_size, false, "internal error: find_end_of_allowed_index returned wrong index=" << up_index_limit << ", with amount_outs.size = " << outs_container_size);
|
||||
if (up_index_limit >= req.decoys_count)
|
||||
{
|
||||
std::set<size_t> used;
|
||||
size_t try_count = 0;
|
||||
for (uint64_t j = 0; j != req.decoys_count && try_count < up_index_limit;)
|
||||
{
|
||||
size_t g_index = crypto::rand<size_t>() % up_index_limit;
|
||||
if (used.count(g_index))
|
||||
continue;
|
||||
bool added = add_out_to_get_random_outs(result_outs, amount, g_index, req.decoys_count, req.use_forced_mix_outs, req.height_upper_limit);
|
||||
used.insert(g_index);
|
||||
if (added)
|
||||
++j;
|
||||
++try_count;
|
||||
}
|
||||
if (result_outs.outs.size() < req.decoys_count)
|
||||
{
|
||||
LOG_PRINT_YELLOW("Not enough inputs for amount " << print_money_brief(amount) << ", needed " << req.decoys_count << ", added " << result_outs.outs.size() << " good outs from " << up_index_limit << " unlocked of " << outs_container_size << " total", LOG_LEVEL_0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t added = 0;
|
||||
for (size_t i = 0; i != up_index_limit; i++)
|
||||
added += add_out_to_get_random_outs(result_outs, amount, i, req.decoys_count, req.use_forced_mix_outs, req.height_upper_limit) ? 1 : 0;
|
||||
LOG_PRINT_YELLOW("Not enough inputs for amount " << print_money_brief(amount) << ", needed " << req.decoys_count << ", added " << added << " good outs from " << up_index_limit << " unlocked of " << outs_container_size << " total - respond with all good outs", LOG_LEVEL_0);
|
||||
//zarcanum era inputs
|
||||
get_target_outs_for_amount_prezarcanum(req, req.amounts[i], result_outs, amounts_to_up_index_limit_cache);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -637,6 +637,8 @@ namespace currency
|
|||
bool push_transaction_to_global_outs_index(const transaction& tx, const crypto::hash& tx_id, std::vector<uint64_t>& global_indexes);
|
||||
bool pop_transaction_from_global_index(const transaction& tx, const crypto::hash& tx_id);
|
||||
bool add_out_to_get_random_outs(COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& result_outs, uint64_t amount, size_t i, uint64_t mix_count, bool use_only_forced_to_mix = false, uint64_t height_upper_limit = 0) const;
|
||||
bool get_target_outs_for_amount_prezarcanum(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS2::request& req, const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS2::offsets_distribution& details, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& result_outs, std::map<uint64_t, uint64_t>& amounts_to_up_index_limit_cache) const;
|
||||
bool get_target_outs_for_amount_postzarcanum(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS2::request& req, const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS2::offsets_distribution& details, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& result_outs, std::map<uint64_t, uint64_t>& amounts_to_up_index_limit_cache) const;
|
||||
bool add_block_as_invalid(const block& bl, const crypto::hash& h);
|
||||
bool add_block_as_invalid(const block_extended_info& bei, const crypto::hash& h);
|
||||
size_t find_end_of_allowed_index(uint64_t amount)const;
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ namespace currency
|
|||
MAP_URI_AUTO_BIN2("/getblocks.bin", on_get_blocks, COMMAND_RPC_GET_BLOCKS_FAST)
|
||||
MAP_URI_AUTO_BIN2("/get_o_indexes.bin", on_get_indexes, COMMAND_RPC_GET_TX_GLOBAL_OUTPUTS_INDEXES)
|
||||
MAP_URI_AUTO_BIN2("/getrandom_outs.bin", on_get_random_outs, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS)
|
||||
MAP_URI_AUTO_BIN2("/getrandom_outs2.bin", on_get_random_outs2, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS2)
|
||||
MAP_URI_AUTO_BIN2("/set_maintainers_info.bin", on_set_maintainers_info, COMMAND_RPC_SET_MAINTAINERS_INFO)
|
||||
MAP_URI_AUTO_BIN2("/get_tx_pool.bin", on_get_tx_pool, COMMAND_RPC_GET_TX_POOL)
|
||||
MAP_URI_AUTO_BIN2("/check_keyimages.bin", on_check_keyimages, COMMAND_RPC_CHECK_KEYIMAGES)
|
||||
|
|
|
|||
|
|
@ -429,7 +429,7 @@ namespace currency
|
|||
struct offsets_distribution
|
||||
{
|
||||
uint64_t amount; //if amount is 0 then lookup in post-zarcanum zone only, if not 0 then pre-zarcanum only
|
||||
std::list<uint64_t> offsets; //[i] = height, estimated location where to pickup output of transaction
|
||||
std::vector<uint64_t> offsets; //[i] = height, estimated location where to pickup output of transaction
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(amount)
|
||||
|
|
@ -440,55 +440,20 @@ namespace currency
|
|||
|
||||
struct request
|
||||
{
|
||||
std::list<offsets_distribution> amounts;
|
||||
std::vector<offsets_distribution> amounts;
|
||||
uint64_t height_upper_limit; // if nonzero, all the decoy outputs must be either older than, or the same age as this height
|
||||
bool use_forced_mix_outs;
|
||||
uint64_t coinbase_percents; //from 0 to 100, estimate percents of coinbase outputs included in decoy sets
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(amounts)
|
||||
KV_SERIALIZE(decoys_count)
|
||||
KV_SERIALIZE(height_upper_limit)
|
||||
KV_SERIALIZE(use_forced_mix_outs)
|
||||
KV_SERIALIZE(coinbase_percents)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
|
||||
#pragma pack (push, 1)
|
||||
struct out_entry
|
||||
{
|
||||
out_entry() = default;
|
||||
out_entry(uint64_t global_amount_index, const crypto::public_key& stealth_address)
|
||||
: global_amount_index(global_amount_index), stealth_address(stealth_address), concealing_point{}, amount_commitment{}, blinded_asset_id{}
|
||||
{}
|
||||
out_entry(uint64_t global_amount_index, const crypto::public_key& stealth_address, const crypto::public_key& amount_commitment, const crypto::public_key& concealing_point, const crypto::public_key& blinded_asset_id)
|
||||
: global_amount_index(global_amount_index), stealth_address(stealth_address), concealing_point(concealing_point), amount_commitment(amount_commitment), blinded_asset_id(blinded_asset_id)
|
||||
{}
|
||||
uint64_t global_amount_index;
|
||||
crypto::public_key stealth_address;
|
||||
crypto::public_key concealing_point; // premultiplied by 1/8
|
||||
crypto::public_key amount_commitment; // premultiplied by 1/8
|
||||
crypto::public_key blinded_asset_id; // premultiplied by 1/8
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
struct outs_for_amount
|
||||
{
|
||||
uint64_t amount;
|
||||
std::list<out_entry> outs;
|
||||
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(amount)
|
||||
KV_SERIALIZE_CONTAINER_POD_AS_BLOB(outs)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
|
||||
struct response
|
||||
{
|
||||
std::vector<outs_for_amount> outs;
|
||||
std::string status;
|
||||
BEGIN_KV_SERIALIZE_MAP()
|
||||
KV_SERIALIZE(outs)
|
||||
KV_SERIALIZE(status)
|
||||
END_KV_SERIALIZE_MAP()
|
||||
};
|
||||
typedef COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response response;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,11 @@ namespace tools
|
|||
return invoke_http_bin_remote_command2_update_is_disconnect("/getrandom_outs.bin", req, res);
|
||||
}
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
bool default_http_core_proxy::call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS2(const currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS2::request& req, currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS2::response& res)
|
||||
{
|
||||
return invoke_http_bin_remote_command2_update_is_disconnect("/getrandom_outs2.bin", req, res);
|
||||
}
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
bool default_http_core_proxy::call_COMMAND_RPC_SEND_RAW_TX(const currency::COMMAND_RPC_SEND_RAW_TX::request& req, currency::COMMAND_RPC_SEND_RAW_TX::response& res)
|
||||
{
|
||||
return invoke_http_json_remote_command2_update_is_disconnect("/sendrawtransaction", req, res);
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ namespace tools
|
|||
bool call_COMMAND_RPC_GET_TX_POOL(const currency::COMMAND_RPC_GET_TX_POOL::request& rqt, currency::COMMAND_RPC_GET_TX_POOL::response& rsp) override;
|
||||
bool call_COMMAND_RPC_GET_ALIASES_BY_ADDRESS(const currency::COMMAND_RPC_GET_ALIASES_BY_ADDRESS::request& rqt, currency::COMMAND_RPC_GET_ALIASES_BY_ADDRESS::response& rsp) override;
|
||||
bool call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS(const currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& rqt, currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& rsp) override;
|
||||
bool call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS2(const currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS2::request& rqt, currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS2::response& rsp) override;
|
||||
bool call_COMMAND_RPC_SEND_RAW_TX(const currency::COMMAND_RPC_SEND_RAW_TX::request& rqt, currency::COMMAND_RPC_SEND_RAW_TX::response& rsp) override;
|
||||
bool call_COMMAND_RPC_FORCE_RELAY_RAW_TXS(const currency::COMMAND_RPC_FORCE_RELAY_RAW_TXS::request& rqt, currency::COMMAND_RPC_FORCE_RELAY_RAW_TXS::response& rsp) override;
|
||||
bool call_COMMAND_RPC_GET_ALL_ALIASES(currency::COMMAND_RPC_GET_ALL_ALIASES::response& rsp) override;
|
||||
|
|
|
|||
|
|
@ -58,6 +58,11 @@ namespace tools
|
|||
return m_rpc.on_get_random_outs(req, res, m_cntxt_stub);
|
||||
}
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
bool call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS2(const currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS2::request& req, currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS2::response& res) override
|
||||
{
|
||||
return m_rpc.on_get_random_outs2(req, res, m_cntxt_stub);
|
||||
}
|
||||
//------------------------------------------------------------------------------------------------------------------------------
|
||||
bool call_COMMAND_RPC_SEND_RAW_TX(const currency::COMMAND_RPC_SEND_RAW_TX::request& req, currency::COMMAND_RPC_SEND_RAW_TX::response& res) override
|
||||
{
|
||||
return m_rpc.on_send_raw_tx(req, res, m_cntxt_stub);
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ namespace tools
|
|||
virtual bool call_COMMAND_RPC_GET_TX_POOL(const currency::COMMAND_RPC_GET_TX_POOL::request& rqt, currency::COMMAND_RPC_GET_TX_POOL::response& rsp){ return false; }
|
||||
virtual bool call_COMMAND_RPC_GET_ALIASES_BY_ADDRESS(const currency::COMMAND_RPC_GET_ALIASES_BY_ADDRESS::request& rqt, currency::COMMAND_RPC_GET_ALIASES_BY_ADDRESS::response& rsp){ return false; }
|
||||
virtual bool call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS(const currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& rqt, currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& rsp){ return false; }
|
||||
virtual bool call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS2(const currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS2::request& rqt, currency::COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS2::response& rsp) { return false; }
|
||||
virtual bool call_COMMAND_RPC_SEND_RAW_TX(const currency::COMMAND_RPC_SEND_RAW_TX::request& rqt, currency::COMMAND_RPC_SEND_RAW_TX::response& rsp){ return false; }
|
||||
virtual bool call_COMMAND_RPC_FORCE_RELAY_RAW_TXS(const currency::COMMAND_RPC_FORCE_RELAY_RAW_TXS::request& rqt, currency::COMMAND_RPC_FORCE_RELAY_RAW_TXS::response& rsp){ return false; }
|
||||
virtual bool call_COMMAND_RPC_GET_ALL_ALIASES(currency::COMMAND_RPC_GET_ALL_ALIASES::response& rsp){ return false; }
|
||||
|
|
|
|||
128
src/wallet/decoy_selection.cpp
Normal file
128
src/wallet/decoy_selection.cpp
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
// Copyright (c) 2014-2023 Zano Project
|
||||
// Distributed under the MIT/X11 software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "decoy_selection.h"
|
||||
#include "decoy_selection_default_distribution.hpp"
|
||||
#include "crypto/crypto.h"
|
||||
|
||||
bool scaler::config_scale(uint64_t original, uint64_t scale_to)
|
||||
{
|
||||
m_x_m = original;
|
||||
m_y_m = scale_to;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t scaler::scale(uint64_t h)
|
||||
{
|
||||
double k = double(m_x_m) / m_y_m;
|
||||
double e_pow_minus_k = std::exp(-1 * k);
|
||||
double a = e_pow_minus_k / (k - 1 + e_pow_minus_k);
|
||||
double y = h * a + (1 - std::exp(-1 * (double(h) / double(m_y_m)) )) * m_y_m * (1 - a);
|
||||
return static_cast<uint64_t>(std::round(y));
|
||||
}
|
||||
|
||||
void decoy_selection_generator::init(uint64_t max_h)
|
||||
{
|
||||
load_distribution(g_default_distribution, max_h);
|
||||
m_is_initialized = true;
|
||||
|
||||
}
|
||||
bool decoy_selection_generator::load_distribution_from_file(const char* path)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
#define TWO63 0x8000000000000000u
|
||||
#define TWO64f (TWO63*2.0)
|
||||
|
||||
double map_uint_to_double(uint64_t u) {
|
||||
double y = (double)u;
|
||||
return y / TWO64f;
|
||||
}
|
||||
|
||||
std::vector<uint64_t> decoy_selection_generator::generate_distribution(uint64_t count)
|
||||
{
|
||||
std::vector<uint64_t> res;
|
||||
for (size_t i = 0; i != count; i++)
|
||||
{
|
||||
uint64_t r = 0;
|
||||
crypto::generate_random_bytes(sizeof(r), &r);
|
||||
double r_ = map_uint_to_double(r);
|
||||
auto it = m_distribution_mapping.upper_bound(r_);
|
||||
if (it == m_distribution_mapping.end())
|
||||
{
|
||||
throw(std::runtime_error(std::string("_r not found in m_distribution_mapping: ") + std::to_string(r_) ));
|
||||
}
|
||||
uint64_t h = it->second;
|
||||
if (it != m_distribution_mapping.begin())
|
||||
{
|
||||
uint64_t h_0 = (--it)->second;
|
||||
crypto::generate_random_bytes(sizeof(r), &r);
|
||||
h = h_0 + r % (h - h_0) + 1;
|
||||
}
|
||||
//scale from nominal to max_h
|
||||
res.push_back(h); }
|
||||
return res;
|
||||
}
|
||||
|
||||
uint64_t get_distance(const std::vector<decoy_selection_generator::distribution_entry> entries, size_t i)
|
||||
{
|
||||
if (i == 0)
|
||||
return 1;
|
||||
return entries[i].h - entries[i - 1].h;
|
||||
}
|
||||
|
||||
bool decoy_selection_generator::load_distribution(const std::vector<decoy_selection_generator::distribution_entry>& original_distribution, uint64_t max_h)
|
||||
{
|
||||
|
||||
//do prescale of distribution
|
||||
std::vector<decoy_selection_generator::distribution_entry> derived_distribution;
|
||||
scaler scl;
|
||||
scl.config_scale(original_distribution.back().h, max_h);
|
||||
|
||||
uint64_t last_scaled_h = 0;
|
||||
std::list<double> last_scaled_array;
|
||||
|
||||
|
||||
for (size_t i = 0; i <= original_distribution.size(); i++)
|
||||
{
|
||||
if (i == original_distribution.size() || (scl.scale(original_distribution[i].h) != last_scaled_h && last_scaled_array.size()))
|
||||
{
|
||||
//put avg to data_scaled
|
||||
double summ = 0;
|
||||
for (auto item: last_scaled_array)
|
||||
{
|
||||
summ += item;
|
||||
}
|
||||
double avg = summ / last_scaled_array.size();
|
||||
uint64_t prev_h = scl.scale(original_distribution[i - 1].h);
|
||||
derived_distribution.push_back(decoy_selection_generator::distribution_entry{ prev_h, avg});
|
||||
last_scaled_array.clear();
|
||||
}
|
||||
if (i == original_distribution.size())
|
||||
{
|
||||
break;
|
||||
}
|
||||
last_scaled_array.push_back(original_distribution[i].v);
|
||||
last_scaled_h = scl.scale(original_distribution[i].h);
|
||||
}
|
||||
|
||||
|
||||
double total_v = 0;
|
||||
|
||||
for (size_t i = 0; i != derived_distribution.size(); i++)
|
||||
{
|
||||
total_v += derived_distribution[i].v * get_distance(derived_distribution, i);
|
||||
}
|
||||
|
||||
double summ_current = 0;
|
||||
for (size_t i = 0; i != derived_distribution.size(); i++)
|
||||
{
|
||||
double k = (derived_distribution[i].v * get_distance(derived_distribution, i))/ total_v;
|
||||
summ_current += k;
|
||||
m_distribution_mapping[summ_current] = derived_distribution[i].h;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
53
src/wallet/decoy_selection.h
Normal file
53
src/wallet/decoy_selection.h
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright (c) 2014-2023 Zano Project
|
||||
// Distributed under the MIT/X11 software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include <boost/serialization/list.hpp>
|
||||
#include <boost/serialization/vector.hpp>
|
||||
#include <boost/serialization/deque.hpp>
|
||||
#include <boost/serialization/singleton.hpp>
|
||||
#include <boost/serialization/extended_type_info.hpp>
|
||||
#include <boost/serialization/shared_ptr.hpp>
|
||||
#include <boost/serialization/optional.hpp>
|
||||
#include <atomic>
|
||||
|
||||
|
||||
#include "include_base_utils.h"
|
||||
#include "profile_tools.h"
|
||||
#include "sync_locked_object.h"
|
||||
|
||||
|
||||
|
||||
class scaler
|
||||
{
|
||||
public:
|
||||
//See the graph on https://www.desmos.com/calculator/zfx4bolfqx
|
||||
bool config_scale(uint64_t original, uint64_t scale_to);
|
||||
uint64_t scale(uint64_t h);
|
||||
private:
|
||||
uint64_t m_x_m;
|
||||
uint64_t m_y_m;
|
||||
};
|
||||
|
||||
|
||||
class decoy_selection_generator
|
||||
{
|
||||
public:
|
||||
struct distribution_entry
|
||||
{
|
||||
uint64_t h;
|
||||
double v;
|
||||
};
|
||||
|
||||
void init(uint64_t max_h);
|
||||
bool load_distribution_from_file(const char* path);
|
||||
std::vector<uint64_t> generate_distribution(uint64_t count);
|
||||
bool is_initialized() { return m_is_initialized; }
|
||||
|
||||
private:
|
||||
bool load_distribution(const std::vector<decoy_selection_generator::distribution_entry>& entries, uint64_t max_h);
|
||||
bool m_is_initialized = false;
|
||||
std::map<double, uint64_t> m_distribution_mapping;
|
||||
};
|
||||
1826
src/wallet/decoy_selection_default_distribution.hpp
Normal file
1826
src/wallet/decoy_selection_default_distribution.hpp
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -44,6 +44,7 @@ using namespace epee;
|
|||
#include "currency_core/crypto_config.h"
|
||||
#include "crypto/zarcanum.h"
|
||||
#include "wallet_debug_events_definitions.h"
|
||||
#include "decoy_selection.h"
|
||||
|
||||
using namespace currency;
|
||||
|
||||
|
|
@ -5864,21 +5865,51 @@ bool wallet2::prepare_tx_sources(size_t fake_outputs_count, std::vector<currency
|
|||
COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response daemon_resp = AUTO_VAL_INIT(daemon_resp);
|
||||
if (fake_outputs_count)
|
||||
{
|
||||
COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request req = AUTO_VAL_INIT(req);
|
||||
uint64_t zarcanum_start_from = m_core_runtime_config.hard_forks.m_height_the_hardfork_n_active_after[ZANO_HARDFORK_04_ZARCANUM];
|
||||
uint64_t current_size = m_chain.get_blockchain_current_size();
|
||||
decoy_selection_generator zarcanum_decoy_set_generator;
|
||||
if (current_size - 1 >= zarcanum_start_from)
|
||||
{
|
||||
//in Zarcanum era
|
||||
const uint64_t test_scale_size = current_size - 1 - zarcanum_start_from;
|
||||
zarcanum_decoy_set_generator.init(test_scale_size - 1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS2::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
|
||||
//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_AMOUNTS2::offsets_distribution());
|
||||
COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS2::offsets_distribution& rdisttib = req.amounts.back();
|
||||
|
||||
auto it = m_transfers.begin() + i;
|
||||
WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(it->m_ptx_wallet_info->m_tx.vout.size() > it->m_internal_output_index,
|
||||
"m_internal_output_index = " << it->m_internal_output_index <<
|
||||
" is greater or equal to outputs count = " << it->m_ptx_wallet_info->m_tx.vout.size());
|
||||
req.amounts.push_back(it->is_zc() ? 0 : it->m_amount);
|
||||
|
||||
//check if we have Zarcanum era output of pre-Zarcanum
|
||||
if (it->is_zc())
|
||||
{
|
||||
//Zarcanum era
|
||||
rdisttib.amount = 0;
|
||||
//generate distribution in Zarcanum hardfork
|
||||
THROW_IF_FALSE_WALLET_INT_ERR_EX(zarcanum_decoy_set_generator.is_initialized(), "zarcanum_decoy_set_generator are not initialized");
|
||||
rdisttib.offsets = zarcanum_decoy_set_generator.generate_distribution(fake_outputs_count);
|
||||
}
|
||||
else
|
||||
{
|
||||
//for prezarcanum era use flat distribution
|
||||
rdisttib.amount = it->m_amount;
|
||||
rdisttib.offsets.resize(fake_outputs_count, 0);
|
||||
}
|
||||
}
|
||||
|
||||
bool r = m_core_proxy->call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS(req, daemon_resp);
|
||||
THROW_IF_FALSE_WALLET_EX(r, error::no_connection_to_daemon, "getrandom_outs.bin");
|
||||
bool r = m_core_proxy->call_COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS2(req, daemon_resp);
|
||||
THROW_IF_FALSE_WALLET_EX(r, error::no_connection_to_daemon, "getrandom_outs2.bin");
|
||||
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(),
|
||||
|
|
@ -5887,7 +5918,7 @@ bool wallet2::prepare_tx_sources(size_t fake_outputs_count, std::vector<currency
|
|||
std::vector<COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount> scanty_outs;
|
||||
for(COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& amount_outs : daemon_resp.outs)
|
||||
{
|
||||
if (amount_outs.outs.size() < req.decoys_count)
|
||||
if (amount_outs.outs.size() < fake_outputs_count)
|
||||
{
|
||||
scanty_outs.push_back(amount_outs);
|
||||
}
|
||||
|
|
|
|||
43
tests/unit_tests/decoy_selection.cpp
Normal file
43
tests/unit_tests/decoy_selection.cpp
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) 2019 Zano Project
|
||||
// Distributed under the MIT/X11 software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
#include <algorithm>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "wallet/decoy_selection.h"
|
||||
|
||||
TEST(decoy_selection_test, decoy_selection_test)
|
||||
{
|
||||
const uint64_t test_scale_size = 20000;
|
||||
decoy_selection_generator dsg;
|
||||
dsg.init(test_scale_size - 1);
|
||||
std::map<uint64_t, uint64_t> hits;
|
||||
//std::vector<uint64_t> hits(test_scale_size, 0);
|
||||
|
||||
|
||||
// while (true)
|
||||
// {
|
||||
// std::vector<uint64_t> decoys = dsg.generate_distribution(15);
|
||||
// for (auto d : decoys)
|
||||
// {
|
||||
// hits[d]++;
|
||||
// }
|
||||
//
|
||||
// if (hits[10] > 500)
|
||||
// break;
|
||||
//
|
||||
// }
|
||||
// std::stringstream ss;
|
||||
// for (auto it = hits.begin(); it != hits.end(); it++)
|
||||
// {
|
||||
// //if (hits[i] != 0)
|
||||
// {
|
||||
// ss << it->first << ", " << it->second << ENDL;
|
||||
// }
|
||||
// }
|
||||
// epee::file_io_utils::save_string_to_file("distribution.csv", ss.str());
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
Loading…
Add table
Reference in a new issue