|
|
|
|
@@ -94,7 +94,9 @@ using namespace cryptonote;
|
|
|
|
|
#define MULTISIG_UNSIGNED_TX_PREFIX "Monero multisig unsigned tx set\001"
|
|
|
|
|
|
|
|
|
|
#define RECENT_OUTPUT_RATIO (0.5) // 50% of outputs are from the recent zone
|
|
|
|
|
#define RECENT_OUTPUT_ZONE ((time_t)(1.8 * 86400)) // last 1.8 day makes up the recent zone (taken from monerolink.pdf, Miller et al)
|
|
|
|
|
#define RECENT_OUTPUT_DAYS (1.8) // last 1.8 day makes up the recent zone (taken from monerolink.pdf, Miller et al)
|
|
|
|
|
#define RECENT_OUTPUT_ZONE ((time_t)(RECENT_OUTPUT_DAYS * 86400))
|
|
|
|
|
#define RECENT_OUTPUT_BLOCKS (RECENT_OUTPUT_DAYS * 720)
|
|
|
|
|
|
|
|
|
|
#define FEE_ESTIMATE_GRACE_BLOCKS 10 // estimate fee valid for that many blocks
|
|
|
|
|
|
|
|
|
|
@@ -107,6 +109,12 @@ using namespace cryptonote;
|
|
|
|
|
|
|
|
|
|
#define MULTISIG_EXPORT_FILE_MAGIC "Monero multisig export\001"
|
|
|
|
|
|
|
|
|
|
#define SEGREGATION_FORK_HEIGHT 1564965
|
|
|
|
|
#define TESTNET_SEGREGATION_FORK_HEIGHT 1000000
|
|
|
|
|
#define STAGENET_SEGREGATION_FORK_HEIGHT 1000000
|
|
|
|
|
#define SEGREGATION_FORK_VICINITY 1500 /* blocks */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace
|
|
|
|
|
{
|
|
|
|
|
std::string get_default_ringdb_path()
|
|
|
|
|
@@ -652,6 +660,8 @@ wallet2::wallet2(network_type nettype, bool restricted):
|
|
|
|
|
m_confirm_backlog_threshold(0),
|
|
|
|
|
m_confirm_export_overwrite(true),
|
|
|
|
|
m_auto_low_priority(true),
|
|
|
|
|
m_segregate_pre_fork_outputs(true),
|
|
|
|
|
m_key_reuse_mitigation2(true),
|
|
|
|
|
m_is_initialized(false),
|
|
|
|
|
m_restricted(restricted),
|
|
|
|
|
is_old_file_format(false),
|
|
|
|
|
@@ -2328,6 +2338,8 @@ bool wallet2::get_output_distribution(uint64_t &start_height, std::vector<uint64
|
|
|
|
|
cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::request req = AUTO_VAL_INIT(req);
|
|
|
|
|
cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response res = AUTO_VAL_INIT(res);
|
|
|
|
|
req.amounts.push_back(0);
|
|
|
|
|
req.from_height = 0;
|
|
|
|
|
req.cumulative = true;
|
|
|
|
|
m_daemon_rpc_mutex.lock();
|
|
|
|
|
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_distribution", req, res, m_http_client, rpc_timeout);
|
|
|
|
|
m_daemon_rpc_mutex.unlock();
|
|
|
|
|
@@ -2563,6 +2575,12 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable
|
|
|
|
|
value2.SetUint(m_nettype);
|
|
|
|
|
json.AddMember("nettype", value2, json.GetAllocator());
|
|
|
|
|
|
|
|
|
|
value2.SetInt(m_segregate_pre_fork_outputs ? 1 : 0);
|
|
|
|
|
json.AddMember("segregate_pre_fork_outputs", value2, json.GetAllocator());
|
|
|
|
|
|
|
|
|
|
value2.SetInt(m_key_reuse_mitigation2 ? 1 : 0);
|
|
|
|
|
json.AddMember("key_reuse_mitigation2", value2, json.GetAllocator());
|
|
|
|
|
|
|
|
|
|
// Serialize the JSON object
|
|
|
|
|
rapidjson::StringBuffer buffer;
|
|
|
|
|
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
|
|
|
|
@@ -5656,6 +5674,20 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
|
|
|
|
|
|
|
|
|
|
if (fake_outputs_count > 0)
|
|
|
|
|
{
|
|
|
|
|
uint64_t segregation_fork_height;
|
|
|
|
|
switch (m_nettype)
|
|
|
|
|
{
|
|
|
|
|
case TESTNET: segregation_fork_height = TESTNET_SEGREGATION_FORK_HEIGHT; break;
|
|
|
|
|
case STAGENET: segregation_fork_height = STAGENET_SEGREGATION_FORK_HEIGHT; break;
|
|
|
|
|
case MAINNET: segregation_fork_height = SEGREGATION_FORK_HEIGHT; break;
|
|
|
|
|
default: THROW_WALLET_EXCEPTION(error::wallet_internal_error, "Invalid network type");
|
|
|
|
|
}
|
|
|
|
|
// check whether we're shortly after the fork
|
|
|
|
|
uint64_t height;
|
|
|
|
|
boost::optional<std::string> result = m_node_rpc_proxy.get_height(height);
|
|
|
|
|
throw_on_rpc_response_error(result, "get_info");
|
|
|
|
|
bool is_shortly_after_segregation_fork = height >= segregation_fork_height && height < segregation_fork_height + SEGREGATION_FORK_VICINITY;
|
|
|
|
|
|
|
|
|
|
// get histogram for the amounts we need
|
|
|
|
|
cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request req_t = AUTO_VAL_INIT(req_t);
|
|
|
|
|
cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response resp_t = AUTO_VAL_INIT(resp_t);
|
|
|
|
|
@@ -5673,6 +5705,50 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
|
|
|
|
|
THROW_WALLET_EXCEPTION_IF(resp_t.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram");
|
|
|
|
|
THROW_WALLET_EXCEPTION_IF(resp_t.status != CORE_RPC_STATUS_OK, error::get_histogram_error, resp_t.status);
|
|
|
|
|
|
|
|
|
|
// if we want to segregate fake outs pre or post fork, get distribution
|
|
|
|
|
std::unordered_map<uint64_t, std::pair<uint64_t, uint64_t>> segregation_limit;
|
|
|
|
|
if (m_segregate_pre_fork_outputs || m_key_reuse_mitigation2)
|
|
|
|
|
{
|
|
|
|
|
cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::request req_t = AUTO_VAL_INIT(req_t);
|
|
|
|
|
cryptonote::COMMAND_RPC_GET_OUTPUT_DISTRIBUTION::response resp_t = AUTO_VAL_INIT(resp_t);
|
|
|
|
|
for(size_t idx: selected_transfers)
|
|
|
|
|
req_t.amounts.push_back(m_transfers[idx].is_rct() ? 0 : m_transfers[idx].amount());
|
|
|
|
|
std::sort(req_t.amounts.begin(), req_t.amounts.end());
|
|
|
|
|
auto end = std::unique(req_t.amounts.begin(), req_t.amounts.end());
|
|
|
|
|
req_t.amounts.resize(std::distance(req_t.amounts.begin(), end));
|
|
|
|
|
req_t.from_height = segregation_fork_height >= RECENT_OUTPUT_ZONE ? height >= (segregation_fork_height ? segregation_fork_height : height) - RECENT_OUTPUT_BLOCKS : 0;
|
|
|
|
|
req_t.cumulative = true;
|
|
|
|
|
m_daemon_rpc_mutex.lock();
|
|
|
|
|
bool r = net_utils::invoke_http_json_rpc("/json_rpc", "get_output_distribution", req_t, resp_t, m_http_client, rpc_timeout);
|
|
|
|
|
m_daemon_rpc_mutex.unlock();
|
|
|
|
|
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "transfer_selected");
|
|
|
|
|
THROW_WALLET_EXCEPTION_IF(resp_t.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_distribution");
|
|
|
|
|
THROW_WALLET_EXCEPTION_IF(resp_t.status != CORE_RPC_STATUS_OK, error::get_output_distribution, resp_t.status);
|
|
|
|
|
|
|
|
|
|
// check we got all data
|
|
|
|
|
for(size_t idx: selected_transfers)
|
|
|
|
|
{
|
|
|
|
|
const uint64_t amount = m_transfers[idx].is_rct() ? 0 : m_transfers[idx].amount();
|
|
|
|
|
bool found = false;
|
|
|
|
|
for (const auto &d: resp_t.distributions)
|
|
|
|
|
{
|
|
|
|
|
if (d.amount == amount)
|
|
|
|
|
{
|
|
|
|
|
THROW_WALLET_EXCEPTION_IF(d.start_height > segregation_fork_height, error::get_output_distribution, "Distribution start_height too high");
|
|
|
|
|
THROW_WALLET_EXCEPTION_IF(segregation_fork_height - d.start_height >= d.distribution.size(), error::get_output_distribution, "Distribution size too small");
|
|
|
|
|
THROW_WALLET_EXCEPTION_IF(segregation_fork_height <= RECENT_OUTPUT_BLOCKS, error::wallet_internal_error, "Fork height too low");
|
|
|
|
|
THROW_WALLET_EXCEPTION_IF(segregation_fork_height - RECENT_OUTPUT_BLOCKS < d.start_height, error::get_output_distribution, "Bad start height");
|
|
|
|
|
uint64_t till_fork = d.distribution[segregation_fork_height - d.start_height];
|
|
|
|
|
uint64_t recent = till_fork - d.distribution[segregation_fork_height - RECENT_OUTPUT_BLOCKS - d.start_height];
|
|
|
|
|
segregation_limit[amount] = std::make_pair(till_fork, recent);
|
|
|
|
|
found = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
THROW_WALLET_EXCEPTION_IF(!found, error::get_output_distribution, "Requested amount not found in response");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// we ask for more, to have spares if some outputs are still locked
|
|
|
|
|
size_t base_requested_outputs_count = (size_t)((fake_outputs_count + 1) * 1.5 + 1);
|
|
|
|
|
LOG_PRINT_L2("base_requested_outputs_count: " << base_requested_outputs_count);
|
|
|
|
|
@@ -5692,35 +5768,83 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
|
|
|
|
|
size_t requested_outputs_count = base_requested_outputs_count + (td.is_rct() ? CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW - CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE : 0);
|
|
|
|
|
size_t start = req.outputs.size();
|
|
|
|
|
|
|
|
|
|
// if there are just enough outputs to mix with, use all of them.
|
|
|
|
|
// Eventually this should become impossible.
|
|
|
|
|
const bool output_is_pre_fork = td.m_block_height < segregation_fork_height;
|
|
|
|
|
uint64_t num_outs = 0, num_recent_outs = 0;
|
|
|
|
|
for (const auto &he: resp_t.histogram)
|
|
|
|
|
uint64_t num_post_fork_outs = 0;
|
|
|
|
|
float pre_fork_num_out_ratio = 0.0f;
|
|
|
|
|
float post_fork_num_out_ratio = 0.0f;
|
|
|
|
|
|
|
|
|
|
if (m_segregate_pre_fork_outputs && output_is_pre_fork)
|
|
|
|
|
{
|
|
|
|
|
if (he.amount == amount)
|
|
|
|
|
{
|
|
|
|
|
LOG_PRINT_L2("Found " << print_money(amount) << ": " << he.total_instances << " total, "
|
|
|
|
|
<< he.unlocked_instances << " unlocked, " << he.recent_instances << " recent");
|
|
|
|
|
num_outs = he.unlocked_instances;
|
|
|
|
|
num_recent_outs = he.recent_instances;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
num_outs = segregation_limit[amount].first;
|
|
|
|
|
num_recent_outs = segregation_limit[amount].second;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// if there are just enough outputs to mix with, use all of them.
|
|
|
|
|
// Eventually this should become impossible.
|
|
|
|
|
for (const auto &he: resp_t.histogram)
|
|
|
|
|
{
|
|
|
|
|
if (he.amount == amount)
|
|
|
|
|
{
|
|
|
|
|
LOG_PRINT_L2("Found " << print_money(amount) << ": " << he.total_instances << " total, "
|
|
|
|
|
<< he.unlocked_instances << " unlocked, " << he.recent_instances << " recent");
|
|
|
|
|
num_outs = he.unlocked_instances;
|
|
|
|
|
num_recent_outs = he.recent_instances;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (m_key_reuse_mitigation2)
|
|
|
|
|
{
|
|
|
|
|
if (output_is_pre_fork)
|
|
|
|
|
{
|
|
|
|
|
if (is_shortly_after_segregation_fork)
|
|
|
|
|
{
|
|
|
|
|
pre_fork_num_out_ratio = 33.4/100.0f * (1.0f - RECENT_OUTPUT_RATIO);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
pre_fork_num_out_ratio = 33.4/100.0f * (1.0f - RECENT_OUTPUT_RATIO);
|
|
|
|
|
post_fork_num_out_ratio = 33.4/100.0f * (1.0f - RECENT_OUTPUT_RATIO);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (is_shortly_after_segregation_fork)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
post_fork_num_out_ratio = 67.8/100.0f * (1.0f - RECENT_OUTPUT_RATIO);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
num_post_fork_outs = num_outs - segregation_limit[amount].first;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG_PRINT_L1("" << num_outs << " unlocked outputs of size " << print_money(amount));
|
|
|
|
|
THROW_WALLET_EXCEPTION_IF(num_outs == 0, error::wallet_internal_error,
|
|
|
|
|
"histogram reports no unlocked outputs for " + boost::lexical_cast<std::string>(amount) + ", not even ours");
|
|
|
|
|
THROW_WALLET_EXCEPTION_IF(num_recent_outs > num_outs, error::wallet_internal_error,
|
|
|
|
|
"histogram reports more recent outs than outs for " + boost::lexical_cast<std::string>(amount));
|
|
|
|
|
|
|
|
|
|
// how many fake outs to draw on a pre-fork triangular distribution
|
|
|
|
|
size_t pre_fork_outputs_count = requested_outputs_count * pre_fork_num_out_ratio;
|
|
|
|
|
size_t post_fork_outputs_count = requested_outputs_count * post_fork_num_out_ratio;
|
|
|
|
|
// how many fake outs to draw otherwise
|
|
|
|
|
size_t normal_output_count = requested_outputs_count - pre_fork_outputs_count - post_fork_outputs_count;
|
|
|
|
|
|
|
|
|
|
// X% of those outs are to be taken from recent outputs
|
|
|
|
|
size_t recent_outputs_count = requested_outputs_count * RECENT_OUTPUT_RATIO;
|
|
|
|
|
size_t recent_outputs_count = normal_output_count * RECENT_OUTPUT_RATIO;
|
|
|
|
|
if (recent_outputs_count == 0)
|
|
|
|
|
recent_outputs_count = 1; // ensure we have at least one, if possible
|
|
|
|
|
if (recent_outputs_count > num_recent_outs)
|
|
|
|
|
recent_outputs_count = num_recent_outs;
|
|
|
|
|
if (td.m_global_output_index >= num_outs - num_recent_outs && recent_outputs_count > 0)
|
|
|
|
|
--recent_outputs_count; // if the real out is recent, pick one less recent fake out
|
|
|
|
|
LOG_PRINT_L1("Using " << recent_outputs_count << " recent outputs");
|
|
|
|
|
LOG_PRINT_L1("Fake output makeup: " << requested_outputs_count << " requested: " << recent_outputs_count << " recent, " <<
|
|
|
|
|
pre_fork_outputs_count << " pre-fork, " << post_fork_outputs_count << " post-fork, " <<
|
|
|
|
|
(requested_outputs_count - recent_outputs_count - pre_fork_outputs_count - post_fork_outputs_count) << " full-chain");
|
|
|
|
|
|
|
|
|
|
uint64_t num_found = 0;
|
|
|
|
|
|
|
|
|
|
@@ -5796,6 +5920,7 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
|
|
|
|
|
// list of output indices we've seen.
|
|
|
|
|
|
|
|
|
|
uint64_t i;
|
|
|
|
|
const char *type = "";
|
|
|
|
|
if (num_found - 1 < recent_outputs_count) // -1 to account for the real one we seeded with
|
|
|
|
|
{
|
|
|
|
|
// triangular distribution over [a,b) with a=0, mode c=b=up_index_limit
|
|
|
|
|
@@ -5805,7 +5930,29 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
|
|
|
|
|
// just in case rounding up to 1 occurs after calc
|
|
|
|
|
if (i == num_outs)
|
|
|
|
|
--i;
|
|
|
|
|
LOG_PRINT_L2("picking " << i << " as recent");
|
|
|
|
|
type = "recent";
|
|
|
|
|
}
|
|
|
|
|
else if (num_found -1 < recent_outputs_count + pre_fork_outputs_count)
|
|
|
|
|
{
|
|
|
|
|
// triangular distribution over [a,b) with a=0, mode c=b=up_index_limit
|
|
|
|
|
uint64_t r = crypto::rand<uint64_t>() % ((uint64_t)1 << 53);
|
|
|
|
|
double frac = std::sqrt((double)r / ((uint64_t)1 << 53));
|
|
|
|
|
i = (uint64_t)(frac*segregation_limit[amount].first);
|
|
|
|
|
// just in case rounding up to 1 occurs after calc
|
|
|
|
|
if (i == num_outs)
|
|
|
|
|
--i;
|
|
|
|
|
type = " pre-fork";
|
|
|
|
|
}
|
|
|
|
|
else if (num_found -1 < recent_outputs_count + pre_fork_outputs_count + post_fork_outputs_count)
|
|
|
|
|
{
|
|
|
|
|
// triangular distribution over [a,b) with a=0, mode c=b=up_index_limit
|
|
|
|
|
uint64_t r = crypto::rand<uint64_t>() % ((uint64_t)1 << 53);
|
|
|
|
|
double frac = std::sqrt((double)r / ((uint64_t)1 << 53));
|
|
|
|
|
i = (uint64_t)(frac*num_post_fork_outs) + segregation_limit[amount].first;
|
|
|
|
|
// just in case rounding up to 1 occurs after calc
|
|
|
|
|
if (i == num_post_fork_outs+segregation_limit[amount].first)
|
|
|
|
|
--i;
|
|
|
|
|
type = "post-fork";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
@@ -5816,13 +5963,14 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
|
|
|
|
|
// just in case rounding up to 1 occurs after calc
|
|
|
|
|
if (i == num_outs)
|
|
|
|
|
--i;
|
|
|
|
|
LOG_PRINT_L2("picking " << i << " as triangular");
|
|
|
|
|
type = "triangular";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (seen_indices.count(i))
|
|
|
|
|
continue;
|
|
|
|
|
seen_indices.emplace(i);
|
|
|
|
|
|
|
|
|
|
LOG_PRINT_L2("picking " << i << " as " << type);
|
|
|
|
|
req.outputs.push_back({amount, i});
|
|
|
|
|
++num_found;
|
|
|
|
|
}
|
|
|
|
|
@@ -5860,7 +6008,10 @@ void wallet2::get_outs(std::vector<std::vector<tools::wallet2::get_outs_entry>>
|
|
|
|
|
|
|
|
|
|
uint64_t num_outs = 0;
|
|
|
|
|
const uint64_t amount = td.is_rct() ? 0 : td.amount();
|
|
|
|
|
for (const auto &he: resp_t.histogram)
|
|
|
|
|
const bool output_is_pre_fork = td.m_block_height < segregation_fork_height;
|
|
|
|
|
if (m_segregate_pre_fork_outputs && output_is_pre_fork)
|
|
|
|
|
num_outs = segregation_limit[amount].first;
|
|
|
|
|
else for (const auto &he: resp_t.histogram)
|
|
|
|
|
{
|
|
|
|
|
if (he.amount == amount)
|
|
|
|
|
{
|
|
|
|
|
|