wallet: transfer_selected_rct now also selects fake outs

This commit is contained in:
moneromooo-monero 2016-08-21 09:56:32 +01:00
parent 4f887dea6e
commit b951bc8780
No known key found for this signature in database
GPG Key ID: 686F07454D6CEFC3
4 changed files with 213 additions and 292 deletions

View File

@ -1753,11 +1753,11 @@ bool Blockchain::get_outs(const COMMAND_RPC_GET_OUTPUTS::request& req, COMMAND_R
for (const auto &i: req.outputs) for (const auto &i: req.outputs)
{ {
// get tx_hash, tx_out_index from DB // get tx_hash, tx_out_index from DB
crypto::public_key key = m_db->get_output_key(i.amount, i.index).pubkey; const output_data_t &od = m_db->get_output_key(i.amount, i.index);
tx_out_index toi = m_db->get_output_tx_and_index(i.amount, i.index); tx_out_index toi = m_db->get_output_tx_and_index(i.amount, i.index);
bool unlocked = is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first)); bool unlocked = is_tx_spendtime_unlocked(m_db->get_tx_unlock_time(toi.first));
res.outs.push_back({key, unlocked}); res.outs.push_back({od.pubkey, od.commitment, unlocked});
} }
return true; return true;
} }

View File

@ -41,7 +41,7 @@ namespace cryptonote
#define CORE_RPC_STATUS_BUSY "BUSY" #define CORE_RPC_STATUS_BUSY "BUSY"
#define CORE_RPC_STATUS_NOT_MINING "NOT MINING" #define CORE_RPC_STATUS_NOT_MINING "NOT MINING"
#define CORE_RPC_VERSION 2 #define CORE_RPC_VERSION 3
struct COMMAND_RPC_GET_HEIGHT struct COMMAND_RPC_GET_HEIGHT
{ {
@ -316,10 +316,12 @@ namespace cryptonote
struct outkey struct outkey
{ {
crypto::public_key key; crypto::public_key key;
rct::key mask;
bool unlocked; bool unlocked;
BEGIN_KV_SERIALIZE_MAP() BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE_VAL_POD_AS_BLOB(key) KV_SERIALIZE_VAL_POD_AS_BLOB(key)
KV_SERIALIZE_VAL_POD_AS_BLOB(mask)
KV_SERIALIZE(unlocked) KV_SERIALIZE(unlocked)
END_KV_SERIALIZE_MAP() END_KV_SERIALIZE_MAP()
}; };

View File

@ -29,6 +29,7 @@
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers // Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
#include <random> #include <random>
#include <tuple>
#include <boost/archive/binary_oarchive.hpp> #include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp> #include <boost/archive/binary_iarchive.hpp>
@ -2655,6 +2656,185 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions(std::vector<crypto
} }
} }
template<typename entry>
void wallet2::get_outs(std::vector<std::vector<entry>> &outs, const std::list<transfer_container::iterator> &selected_transfers, size_t fake_outputs_count)
{
LOG_PRINT_L2("fake_outputs_count: " << fake_outputs_count);
outs.clear();
if (fake_outputs_count > 0)
{
// get histogram for the amounts we need
epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request> req_t = AUTO_VAL_INIT(req_t);
epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response, std::string> resp_t = AUTO_VAL_INIT(resp_t);
m_daemon_rpc_mutex.lock();
req_t.jsonrpc = "2.0";
req_t.id = epee::serialization::storage_entry(0);
req_t.method = "get_output_histogram";
for(auto it: selected_transfers)
req_t.params.amounts.push_back(it->is_rct() ? 0 : it->amount());
req_t.params.unlocked = true;
bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/json_rpc", req_t, resp_t, m_http_client);
m_daemon_rpc_mutex.unlock();
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "transfer_selected");
THROW_WALLET_EXCEPTION_IF(resp_t.result.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram");
THROW_WALLET_EXCEPTION_IF(resp_t.result.status != CORE_RPC_STATUS_OK, error::get_histogram_error, resp_t.result.status);
// we ask for more, to have spares if some outputs are still locked
size_t requested_outputs_count = (size_t)((fake_outputs_count + 1) * 1.5 + 1);
LOG_PRINT_L2("requested_outputs_count: " << requested_outputs_count);
// generate output indices to request
COMMAND_RPC_GET_OUTPUTS::request req = AUTO_VAL_INIT(req);
COMMAND_RPC_GET_OUTPUTS::response daemon_resp = AUTO_VAL_INIT(daemon_resp);
for(transfer_container::iterator it: selected_transfers)
{
const uint64_t amount = it->is_rct() ? 0 : it->amount();
std::unordered_set<uint64_t> seen_indices;
size_t start = req.outputs.size();
// if there are just enough outputs to mix with, use all of them.
// Eventually this should become impossible.
uint64_t num_outs = 0;
for (auto he: resp_t.result.histogram)
{
if (he.amount == amount)
{
num_outs = he.instances;
break;
}
}
LOG_PRINT_L1("" << num_outs << " outputs of size " << print_money(amount));
if (num_outs <= requested_outputs_count)
{
for (uint64_t i = 0; i < num_outs; i++)
req.outputs.push_back({amount, i});
// duplicate to make up shortfall: this will be caught after the RPC call,
// so we can also output the amounts for which we can't reach the required
// mixin after checking the actual unlockedness
for (uint64_t i = num_outs; i < requested_outputs_count; ++i)
req.outputs.push_back({amount, num_outs - 1});
}
else
{
// start with real one
uint64_t num_found = 1;
seen_indices.emplace(it->m_global_output_index);
req.outputs.push_back({amount, it->m_global_output_index});
// while we still need more mixins
while (num_found < requested_outputs_count)
{
// if we've gone through every possible output, we've gotten all we can
if (seen_indices.size() == num_outs)
break;
// get a random output index from the DB. If we've already seen it,
// return to the top of the loop and try again, otherwise add it to the
// list of output indices we've seen.
// 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));
uint64_t i = (uint64_t)(frac*num_outs);
// just in case rounding up to 1 occurs after sqrt
if (i == num_outs)
--i;
if (seen_indices.count(i))
continue;
seen_indices.emplace(i);
req.outputs.push_back({amount, i});
++num_found;
}
}
// sort the subsection, to ensure the daemon doesn't know wich output is ours
std::sort(req.outputs.begin() + start, req.outputs.end(),
[](const COMMAND_RPC_GET_OUTPUTS::out &a, const COMMAND_RPC_GET_OUTPUTS::out &b) { return a.index < b.index; });
}
for (auto i: req.outputs)
LOG_PRINT_L1("asking for output " << i.index << " for " << print_money(i.amount));
// get the keys for those
m_daemon_rpc_mutex.lock();
r = epee::net_utils::invoke_http_bin_remote_command2(m_daemon_address + "/get_outs.bin", req, daemon_resp, m_http_client, 200000);
m_daemon_rpc_mutex.unlock();
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_outs.bin");
THROW_WALLET_EXCEPTION_IF(daemon_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_outs.bin");
THROW_WALLET_EXCEPTION_IF(daemon_resp.status != CORE_RPC_STATUS_OK, error::get_random_outs_error, daemon_resp.status);
THROW_WALLET_EXCEPTION_IF(daemon_resp.outs.size() != req.outputs.size(), error::wallet_internal_error,
"daemon returned wrong response for get_outs.bin, wrong amounts count = " +
std::to_string(daemon_resp.outs.size()) + ", expected " + std::to_string(req.outputs.size()));
std::unordered_map<uint64_t, uint64_t> scanty_outs;
size_t base = 0;
outs.reserve(selected_transfers.size());
for(transfer_container::iterator it: selected_transfers)
{
outs.push_back(std::vector<entry>());
outs.back().reserve(fake_outputs_count + 1);
const rct::key mask = it->is_rct() ? rct::commit(it->amount(), it->m_mask) : rct::zeroCommit(it->amount());
// pick real out first (it will be sorted when done)
outs.back().push_back(std::make_tuple(it->m_global_output_index, boost::get<txout_to_key>(it->m_tx.vout[it->m_internal_output_index].target).key, mask));
// then pick others in random order till we reach the required number
// since we use an equiprobable pick here, we don't upset the triangular distribution
std::vector<size_t> order;
order.resize(requested_outputs_count);
for (size_t n = 0; n < order.size(); ++n)
order[n] = n;
std::shuffle(order.begin(), order.end(), std::default_random_engine(crypto::rand<unsigned>()));
for (size_t o = 0; o < requested_outputs_count && outs.back().size() < fake_outputs_count + 1; ++o)
{
size_t i = base + order[o];
if (req.outputs[i].index == it->m_global_output_index) // don't re-add real one
continue;
if (!daemon_resp.outs[i].unlocked) // don't add locked outs
continue;
if (o > 0 && daemon_resp.outs[i].key == daemon_resp.outs[i - 1].key) // don't add duplicates
continue;
outs.back().push_back(std::make_tuple(req.outputs[i].index, daemon_resp.outs[i].key, daemon_resp.outs[i].mask));
}
if (outs.back().size() < fake_outputs_count + 1)
{
scanty_outs[it->amount()] = outs.back().size();
}
else
{
// sort the subsection, so any spares are reset in order
std::sort(outs.back().begin(), outs.back().end(), [](const entry &a, const entry &b) { return std::get<0>(a) < std::get<0>(b); });
// sanity check
for (size_t n = 1; n < outs.back().size(); ++n)
{
THROW_WALLET_EXCEPTION_IF(std::get<0>(outs.back()[n]) == std::get<0>(outs.back()[n-1]), error::wallet_internal_error,
"Duplicate indices though we did not ask for any");
THROW_WALLET_EXCEPTION_IF(std::get<1>(outs.back()[n]) == std::get<1>(outs.back()[n-1]), error::wallet_internal_error,
"Duplicate keys after we have weeded them out");
}
}
base += requested_outputs_count;
}
THROW_WALLET_EXCEPTION_IF(!scanty_outs.empty(), error::not_enough_outs_to_mix, scanty_outs, fake_outputs_count);
}
else
{
for (transfer_container::iterator it: selected_transfers)
{
std::vector<entry> v;
const rct::key mask = it->is_rct() ? rct::commit(it->amount(), it->m_mask) : rct::zeroCommit(it->amount());
v.push_back(std::make_tuple(it->m_global_output_index, boost::get<txout_to_key>(it->m_tx.vout[it->m_internal_output_index].target).key, mask));
outs.push_back(v);
}
}
}
template<typename T> template<typename T>
void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<transfer_container::iterator> selected_transfers, size_t fake_outputs_count, void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_entry>& dsts, const std::list<transfer_container::iterator> selected_transfers, size_t fake_outputs_count,
uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx) uint64_t unlock_time, uint64_t fee, const std::vector<uint8_t>& extra, T destination_split_strategy, const tx_dust_policy& dust_policy, cryptonote::transaction& tx, pending_tx &ptx)
@ -2686,170 +2866,9 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
LOG_PRINT_L2("wanted " << print_money(needed_money) << ", found " << print_money(found_money) << ", fee " << print_money(fee)); LOG_PRINT_L2("wanted " << print_money(needed_money) << ", found " << print_money(found_money) << ", fee " << print_money(fee));
THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_money, found_money, needed_money - fee, fee); THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_money, found_money, needed_money - fee, fee);
typedef std::pair<uint64_t, crypto::public_key> entry; typedef std::tuple<uint64_t, crypto::public_key, rct::key> entry;
std::vector<std::vector<entry>> outs; std::vector<std::vector<entry>> outs;
if (fake_outputs_count) get_outs(outs, selected_transfers, fake_outputs_count); // may throw
{
// get histogram for the amounts we need
epee::json_rpc::request<cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::request> req_t = AUTO_VAL_INIT(req_t);
epee::json_rpc::response<cryptonote::COMMAND_RPC_GET_OUTPUT_HISTOGRAM::response, std::string> resp_t = AUTO_VAL_INIT(resp_t);
m_daemon_rpc_mutex.lock();
req_t.jsonrpc = "2.0";
req_t.id = epee::serialization::storage_entry(0);
req_t.method = "get_output_histogram";
for(auto it: selected_transfers)
req_t.params.amounts.push_back(it->amount());
req_t.params.unlocked = true;
bool r = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/json_rpc", req_t, resp_t, m_http_client);
m_daemon_rpc_mutex.unlock();
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "transfer_selected");
THROW_WALLET_EXCEPTION_IF(resp_t.result.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_output_histogram");
THROW_WALLET_EXCEPTION_IF(resp_t.result.status != CORE_RPC_STATUS_OK, error::get_histogram_error, resp_t.result.status);
// we ask for more, to have spares if some outputs are still locked
size_t requested_outputs_count = (size_t)((fake_outputs_count + 1) * 1.5 + 1);
// generate output indices to request
COMMAND_RPC_GET_OUTPUTS::request req = AUTO_VAL_INIT(req);
COMMAND_RPC_GET_OUTPUTS::response daemon_resp = AUTO_VAL_INIT(daemon_resp);
for(transfer_container::iterator it: selected_transfers)
{
std::unordered_set<uint64_t> seen_indices;
size_t start = req.outputs.size();
// if there are just enough outputs to mix with, use all of them.
// Eventually this should become impossible.
uint64_t num_outs = 0;
for (auto he: resp_t.result.histogram)
{
if (he.amount == it->amount())
{
num_outs = he.instances;
break;
}
}
if (num_outs <= requested_outputs_count)
{
for (uint64_t i = 0; i < num_outs; i++)
req.outputs.push_back({it->amount(), i});
// duplicate to make up shortfall: this will be caught after the RPC call,
// so we can also output the amounts for which we can't reach the required
// mixin after checking the actual unlockedness
for (uint64_t i = num_outs; i < requested_outputs_count; ++i)
req.outputs.push_back({it->amount(), num_outs - 1});
}
else
{
// start with real one
uint64_t num_found = 1;
seen_indices.emplace(it->m_global_output_index);
req.outputs.push_back({it->amount(), it->m_global_output_index});
// while we still need more mixins
while (num_found < requested_outputs_count)
{
// if we've gone through every possible output, we've gotten all we can
if (seen_indices.size() == num_outs)
break;
// get a random output index from the DB. If we've already seen it,
// return to the top of the loop and try again, otherwise add it to the
// list of output indices we've seen.
// 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));
uint64_t i = (uint64_t)(frac*num_outs);
// just in case rounding up to 1 occurs after sqrt
if (i == num_outs)
--i;
if (seen_indices.count(i))
continue;
seen_indices.emplace(i);
req.outputs.push_back({it->amount(), i});
++num_found;
}
}
// sort the subsection, to ensure the daemon doesn't know wich output is ours
std::sort(req.outputs.begin() + start, req.outputs.end(),
[](const COMMAND_RPC_GET_OUTPUTS::out &a, const COMMAND_RPC_GET_OUTPUTS::out &b) { return a.index < b.index; });
}
// get the keys for those
m_daemon_rpc_mutex.lock();
r = epee::net_utils::invoke_http_bin_remote_command2(m_daemon_address + "/get_outs.bin", req, daemon_resp, m_http_client, 200000);
m_daemon_rpc_mutex.unlock();
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "get_outs.bin");
THROW_WALLET_EXCEPTION_IF(daemon_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "get_outs.bin");
THROW_WALLET_EXCEPTION_IF(daemon_resp.status != CORE_RPC_STATUS_OK, error::get_random_outs_error, daemon_resp.status);
THROW_WALLET_EXCEPTION_IF(daemon_resp.outs.size() != req.outputs.size(), error::wallet_internal_error,
"daemon returned wrong response for get_outs.bin, wrong amounts count = " +
std::to_string(daemon_resp.outs.size()) + ", expected " + std::to_string(req.outputs.size()));
std::unordered_map<uint64_t, uint64_t> scanty_outs;
size_t base = 0;
outs.reserve(selected_transfers.size());
for(transfer_container::iterator it: selected_transfers)
{
outs.push_back(std::vector<entry>());
outs.back().reserve(fake_outputs_count + 1);
// pick real out first (it will be sorted when done)
outs.back().push_back({it->m_global_output_index, boost::get<txout_to_key>(it->m_tx.vout[it->m_internal_output_index].target).key});
// then pick others in random order till we reach the required number
// since we use an equiprobable pick here, we don't upset the triangular distribution
std::vector<size_t> order;
order.resize(requested_outputs_count);
for (size_t n = 0; n < order.size(); ++n)
order[n] = n;
std::shuffle(order.begin(), order.end(), std::default_random_engine(crypto::rand<unsigned>()));
for (size_t o = 0; o < requested_outputs_count && outs.back().size() < fake_outputs_count + 1; ++o)
{
size_t i = base + order[o];
if (req.outputs[i].index == it->m_global_output_index) // don't re-add real one
continue;
if (!daemon_resp.outs[i].unlocked) // don't add locked outs
continue;
if (o > 0 && daemon_resp.outs[i].key == daemon_resp.outs[i - 1].key) // don't add duplicates
continue;
outs.back().push_back({req.outputs[i].index, daemon_resp.outs[i].key});
}
if (outs.back().size() < fake_outputs_count + 1)
{
scanty_outs[it->amount()] = outs.back().size();
}
else
{
// sort the subsection, so any spares are reset in order
std::sort(outs.back().begin(), outs.back().end(), [](const entry &a, const entry &b) { return a.first < b.first; });
// sanity check
for (size_t n = 1; n < outs.back().size(); ++n)
{
THROW_WALLET_EXCEPTION_IF(outs.back()[n].first == outs.back()[n-1].first, error::wallet_internal_error,
"Duplicate indices though we did not ask for any");
THROW_WALLET_EXCEPTION_IF(outs.back()[n].second == outs.back()[n-1].second, error::wallet_internal_error,
"Duplicate keys after we have weeded them out");
}
}
base += requested_outputs_count;
}
THROW_WALLET_EXCEPTION_IF(!scanty_outs.empty(), error::not_enough_outs_to_mix, scanty_outs, fake_outputs_count);
}
else
{
for (transfer_container::iterator it: selected_transfers)
{
std::vector<entry> v;
v.push_back({it->m_global_output_index, boost::get<txout_to_key>(it->m_tx.vout[it->m_internal_output_index].target).key});
outs.push_back(v);
}
}
//prepare inputs //prepare inputs
typedef cryptonote::tx_source_entry::output_entry tx_output_entry; typedef cryptonote::tx_source_entry::output_entry tx_output_entry;
@ -2867,9 +2886,9 @@ void wallet2::transfer_selected(const std::vector<cryptonote::tx_destination_ent
for (size_t n = 0; n < fake_outputs_count + 1; ++n) for (size_t n = 0; n < fake_outputs_count + 1; ++n)
{ {
tx_output_entry oe; tx_output_entry oe;
oe.first = outs[out_index][n].first; oe.first = std::get<0>(outs[out_index][n]);
oe.second.dest = rct::pk2rct(outs[out_index][n].second); oe.second.dest = rct::pk2rct(std::get<1>(outs[out_index][n]));
oe.second.mask = rct::zeroCommit(td.amount()); oe.second.mask = std::get<2>(outs[out_index][n]);
src.outputs.push_back(oe); src.outputs.push_back(oe);
++i; ++i;
@ -2976,76 +2995,13 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
LOG_PRINT_L2("wanted " << print_money(needed_money) << ", found " << print_money(found_money) << ", fee " << print_money(fee)); LOG_PRINT_L2("wanted " << print_money(needed_money) << ", found " << print_money(found_money) << ", fee " << print_money(fee));
THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_money, found_money, needed_money - fee, fee); THROW_WALLET_EXCEPTION_IF(found_money < needed_money, error::not_enough_money, found_money, needed_money - fee, fee);
typedef cryptonote::tx_source_entry::output_entry tx_output_entry; typedef std::tuple<uint64_t, crypto::public_key, rct::key> entry;
std::vector<std::vector<entry>> outs;
std::vector<size_t> selected_transfer_to_daemon_resp; get_outs(outs, selected_transfers, fake_outputs_count); // may throw
COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::request rct_req = AUTO_VAL_INIT(rct_req);
COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::response rct_daemon_resp = AUTO_VAL_INIT(rct_daemon_resp);
COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request daemon_req = AUTO_VAL_INIT(daemon_req);
COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response daemon_resp = AUTO_VAL_INIT(daemon_resp);
if(fake_outputs_count)
{
rct_req.outs_count = 0;
size_t n_rct_inputs = 0;
BOOST_FOREACH(transfer_container::iterator it, selected_transfers)
{
THROW_WALLET_EXCEPTION_IF(it->m_tx.vout.size() <= it->m_internal_output_index, error::wallet_internal_error,
"m_internal_output_index = " + std::to_string(it->m_internal_output_index) +
" is greater or equal to outputs count = " + std::to_string(it->m_tx.vout.size()));
if (it->is_rct())
++n_rct_inputs;
else
daemon_req.amounts.push_back(it->amount());
selected_transfer_to_daemon_resp.push_back(daemon_req.amounts.size() - 1);
}
size_t n_amounts_requested = daemon_req.amounts.size();
rct_req.outs_count = n_rct_inputs * (fake_outputs_count + 1);
if (rct_req.outs_count > 0)
{
LOG_PRINT_L2("We need RCT fake outs for " << n_rct_inputs << " inputs");
rct_req.outs_count = n_rct_inputs * (fake_outputs_count + 1); // add one to make possible (if need) to skip real output key
m_daemon_rpc_mutex.lock();
bool r = epee::net_utils::invoke_http_bin_remote_command2(m_daemon_address + "/getrandom_rctouts.bin", rct_req, rct_daemon_resp, m_http_client, 200000);
m_daemon_rpc_mutex.unlock();
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "getrandom_rctouts.bin");
THROW_WALLET_EXCEPTION_IF(rct_daemon_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "getrandom_rctouts.bin");
THROW_WALLET_EXCEPTION_IF(rct_daemon_resp.status != CORE_RPC_STATUS_OK, error::get_random_outs_error, rct_daemon_resp.status);
THROW_WALLET_EXCEPTION_IF(rct_daemon_resp.outs.size() != n_rct_inputs * (fake_outputs_count + 1), error::wallet_internal_error,
"daemon returned wrong response for getrandom_rctouts.bin, wrong amounts count = " +
std::to_string(daemon_resp.outs.size()) + ", expected " + std::to_string(n_rct_inputs * (fake_outputs_count + 1)));
}
if (!daemon_req.amounts.empty())
{
LOG_PRINT_L2("We need non-RCT fake outs for " << daemon_req.amounts.size() << " inputs");
daemon_req.outs_count = fake_outputs_count + 1; // add one to make possible (if need) to skip real output key
m_daemon_rpc_mutex.lock();
bool r = epee::net_utils::invoke_http_bin_remote_command2(m_daemon_address + "/getrandom_outs.bin", daemon_req, daemon_resp, m_http_client, 200000);
m_daemon_rpc_mutex.unlock();
THROW_WALLET_EXCEPTION_IF(!r, error::no_connection_to_daemon, "getrandom_outs.bin");
THROW_WALLET_EXCEPTION_IF(daemon_resp.status == CORE_RPC_STATUS_BUSY, error::daemon_busy, "getrandom_outs.bin");
THROW_WALLET_EXCEPTION_IF(daemon_resp.status != CORE_RPC_STATUS_OK, error::get_random_outs_error, daemon_resp.status);
THROW_WALLET_EXCEPTION_IF(daemon_resp.outs.size() != n_amounts_requested, error::wallet_internal_error,
"daemon returned wrong response for getrandom_outs.bin, wrong amounts count = " +
std::to_string(daemon_resp.outs.size()) + ", expected " + std::to_string(n_amounts_requested));
std::unordered_map<uint64_t, uint64_t> scanty_outs;
BOOST_FOREACH(COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount& amount_outs, daemon_resp.outs)
{
if (amount_outs.outs.size() < fake_outputs_count)
{
scanty_outs[amount_outs.amount] = amount_outs.outs.size();
}
}
THROW_WALLET_EXCEPTION_IF(!scanty_outs.empty(), error::not_enough_outs_to_mix, scanty_outs, fake_outputs_count);
}
}
//prepare inputs //prepare inputs
size_t i = 0; size_t i = 0, out_index = 0;
std::vector<cryptonote::tx_source_entry> sources; std::vector<cryptonote::tx_source_entry> sources;
rct_daemon_resp.outs.sort([](const COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::out_entry& a, const COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::out_entry& b){return a.global_amount_index < b.global_amount_index;});
std::list<COMMAND_RPC_GET_RANDOM_RCT_OUTPUTS::out_entry>::const_iterator rctit = rct_daemon_resp.outs.begin();
BOOST_FOREACH(transfer_container::iterator it, selected_transfers) BOOST_FOREACH(transfer_container::iterator it, selected_transfers)
{ {
sources.resize(sources.size()+1); sources.resize(sources.size()+1);
@ -3054,76 +3010,37 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
src.amount = td.amount(); src.amount = td.amount();
src.rct = td.is_rct(); src.rct = td.is_rct();
//paste mixin transaction //paste mixin transaction
if(it->is_rct())
{
while (src.outputs.size() < fake_outputs_count)
{
THROW_WALLET_EXCEPTION_IF(rctit == rct_daemon_resp.outs.end(), error::wallet_internal_error, "Out of rct inputs");
// check if we have the daemon supplied output in our real ones
bool found = false;
BOOST_FOREACH(transfer_container::iterator checkit, selected_transfers)
{
if (checkit->m_global_output_index == rctit->global_amount_index && checkit->m_tx.vout[checkit->m_internal_output_index].amount == rctit->amount)
{
found = true;
break;
}
}
if(found)
{
++rctit;
continue;
}
tx_output_entry oe;
oe.first = rctit->global_amount_index;
oe.second.dest = rct::pk2rct(rctit->out_key);
oe.second.mask = rctit->commitment;
++rctit;
src.outputs.push_back(oe);
}
}
else
{
typedef COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry out_entry;
typedef cryptonote::tx_source_entry::output_entry tx_output_entry;
size_t idx = selected_transfer_to_daemon_resp[i]; typedef cryptonote::tx_source_entry::output_entry tx_output_entry;
THROW_WALLET_EXCEPTION_IF(daemon_req.amounts.size() != daemon_resp.outs.size(), error::wallet_internal_error, "Bad amounts/out size"); for (size_t n = 0; n < fake_outputs_count + 1; ++n)
THROW_WALLET_EXCEPTION_IF(idx >= daemon_resp.outs.size(), error::wallet_internal_error, "Bad mapping to daemon_resp.outs");
THROW_WALLET_EXCEPTION_IF(td.amount() != daemon_req.amounts[idx], error::wallet_internal_error, "Bad mapping to daemon_resp.outs/amounts");
daemon_resp.outs[idx].outs.sort([](const out_entry& a, const out_entry& b){return a.global_amount_index < b.global_amount_index;});
BOOST_FOREACH(out_entry& daemon_oe, daemon_resp.outs[idx].outs)
{ {
if(td.m_global_output_index == daemon_oe.global_amount_index)
continue;
tx_output_entry oe; tx_output_entry oe;
oe.first = daemon_oe.global_amount_index; oe.first = std::get<0>(outs[out_index][n]);
oe.second.dest = rct::pk2rct(daemon_oe.out_key); oe.second.dest = rct::pk2rct(std::get<1>(outs[out_index][n]));
oe.second.mask = rct::zeroCommit(td.amount()); oe.second.mask = std::get<2>(outs[out_index][n]);
src.outputs.push_back(oe); src.outputs.push_back(oe);
if(src.outputs.size() >= fake_outputs_count)
break;
}
} }
++i;
//paste real transaction to the random index //paste real transaction to the random index
auto it_to_insert = std::find_if(src.outputs.begin(), src.outputs.end(), [&](const tx_output_entry& a) auto it_to_replace = std::find_if(src.outputs.begin(), src.outputs.end(), [&](const tx_output_entry& a)
{ {
return a.first >= td.m_global_output_index; return a.first == td.m_global_output_index;
}); });
//size_t real_index = src.outputs.size() ? (rand() % src.outputs.size() ):0; THROW_WALLET_EXCEPTION_IF(it_to_replace == src.outputs.end(), error::wallet_internal_error,
// TODO: same index (for now) "real output not found");
tx_output_entry real_oe; tx_output_entry real_oe;
real_oe.first = td.m_global_output_index; real_oe.first = td.m_global_output_index;
real_oe.second.dest = rct::pk2rct(boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key); real_oe.second.dest = rct::pk2rct(boost::get<txout_to_key>(td.m_tx.vout[td.m_internal_output_index].target).key);
real_oe.second.mask = rct::commit(td.amount(), td.m_mask); real_oe.second.mask = rct::commit(td.amount(), td.m_mask);
auto interted_it = src.outputs.insert(it_to_insert, real_oe); *it_to_replace = real_oe;
src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx); src.real_out_tx_key = get_tx_pub_key_from_extra(td.m_tx);
src.real_output = interted_it - src.outputs.begin(); src.real_output = it_to_replace - src.outputs.begin();
src.real_output_in_tx_index = td.m_internal_output_index; src.real_output_in_tx_index = td.m_internal_output_index;
src.mask = td.m_mask; src.mask = td.m_mask;
detail::print_source_entry(src); detail::print_source_entry(src);
++i; ++out_index;
} }
cryptonote::tx_destination_entry change_dts = AUTO_VAL_INIT(change_dts); cryptonote::tx_destination_entry change_dts = AUTO_VAL_INIT(change_dts);

View File

@ -463,6 +463,8 @@ namespace tools
std::vector<size_t> pick_prefered_rct_inputs(uint64_t needed_money) const; std::vector<size_t> pick_prefered_rct_inputs(uint64_t needed_money) const;
void set_spent(transfer_details &td, uint64_t height); void set_spent(transfer_details &td, uint64_t height);
void set_unspent(transfer_details &td); void set_unspent(transfer_details &td);
template<typename entry>
void get_outs(std::vector<std::vector<entry>> &outs, const std::list<transfer_container::iterator> &selected_transfers, size_t fake_outputs_count);
cryptonote::account_base m_account; cryptonote::account_base m_account;
std::string m_daemon_address; std::string m_daemon_address;