wallet: optionally keep track of owned outputs uses

This commit is contained in:
moneromooo-monero 2018-12-03 15:32:14 +00:00
parent e344d93ce7
commit db3f2a91fa
No known key found for this signature in database
GPG key ID: 686F07454D6CEFC3
4 changed files with 109 additions and 19 deletions

View file

@ -146,7 +146,7 @@ namespace
const char* USAGE_START_MINING("start_mining [<number_of_threads>] [bg_mining] [ignore_battery]"); const char* USAGE_START_MINING("start_mining [<number_of_threads>] [bg_mining] [ignore_battery]");
const char* USAGE_SET_DAEMON("set_daemon <host>[:<port>] [trusted|untrusted]"); const char* USAGE_SET_DAEMON("set_daemon <host>[:<port>] [trusted|untrusted]");
const char* USAGE_SHOW_BALANCE("balance [detail]"); const char* USAGE_SHOW_BALANCE("balance [detail]");
const char* USAGE_INCOMING_TRANSFERS("incoming_transfers [available|unavailable] [verbose] [index=<N1>[,<N2>[,...]]]"); const char* USAGE_INCOMING_TRANSFERS("incoming_transfers [available|unavailable] [verbose] [uses] [index=<N1>[,<N2>[,...]]]");
const char* USAGE_PAYMENTS("payments <PID_1> [<PID_2> ... <PID_N>]"); const char* USAGE_PAYMENTS("payments <PID_1> [<PID_2> ... <PID_N>]");
const char* USAGE_PAYMENT_ID("payment_id"); const char* USAGE_PAYMENT_ID("payment_id");
const char* USAGE_TRANSFER("transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] (<URI> | <address> <amount>) [<payment_id>]"); const char* USAGE_TRANSFER("transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] (<URI> | <address> <amount>) [<payment_id>]");
@ -2488,6 +2488,19 @@ bool simple_wallet::set_ignore_fractional_outputs(const std::vector<std::string>
return true; return true;
} }
bool simple_wallet::set_track_uses(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
{
const auto pwd_container = get_and_verify_password();
if (pwd_container)
{
parse_bool_and_use(args[1], [&](bool r) {
m_wallet->track_uses(r);
m_wallet->rewrite(m_wallet_file, pwd_container->password());
});
}
return true;
}
bool simple_wallet::set_device_name(const std::vector<std::string> &args/* = std::vector<std::string>()*/) bool simple_wallet::set_device_name(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
{ {
const auto pwd_container = get_and_verify_password(); const auto pwd_container = get_and_verify_password();
@ -3032,6 +3045,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
success_msg_writer() << "subaddress-lookahead = " << lookahead.first << ":" << lookahead.second; success_msg_writer() << "subaddress-lookahead = " << lookahead.first << ":" << lookahead.second;
success_msg_writer() << "segregation-height = " << m_wallet->segregation_height(); success_msg_writer() << "segregation-height = " << m_wallet->segregation_height();
success_msg_writer() << "ignore-fractional-outputs = " << m_wallet->ignore_fractional_outputs(); success_msg_writer() << "ignore-fractional-outputs = " << m_wallet->ignore_fractional_outputs();
success_msg_writer() << "track-uses = " << m_wallet->track_uses();
success_msg_writer() << "device_name = " << m_wallet->device_name(); success_msg_writer() << "device_name = " << m_wallet->device_name();
return true; return true;
} }
@ -3088,6 +3102,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
CHECK_SIMPLE_VARIABLE("subaddress-lookahead", set_subaddress_lookahead, tr("<major>:<minor>")); CHECK_SIMPLE_VARIABLE("subaddress-lookahead", set_subaddress_lookahead, tr("<major>:<minor>"));
CHECK_SIMPLE_VARIABLE("segregation-height", set_segregation_height, tr("unsigned integer")); CHECK_SIMPLE_VARIABLE("segregation-height", set_segregation_height, tr("unsigned integer"));
CHECK_SIMPLE_VARIABLE("ignore-fractional-outputs", set_ignore_fractional_outputs, tr("0 or 1")); CHECK_SIMPLE_VARIABLE("ignore-fractional-outputs", set_ignore_fractional_outputs, tr("0 or 1"));
CHECK_SIMPLE_VARIABLE("track-uses", set_track_uses, tr("0 or 1"));
CHECK_SIMPLE_VARIABLE("device-name", set_device_name, tr("<device_name[:device_spec]>")); CHECK_SIMPLE_VARIABLE("device-name", set_device_name, tr("<device_name[:device_spec]>"));
} }
fail_msg_writer() << tr("set: unrecognized argument(s)"); fail_msg_writer() << tr("set: unrecognized argument(s)");
@ -4813,6 +4828,7 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args
bool filter = false; bool filter = false;
bool available = false; bool available = false;
bool verbose = false; bool verbose = false;
bool uses = false;
if (local_args.size() > 0) if (local_args.size() > 0)
{ {
if (local_args[0] == "available") if (local_args[0] == "available")
@ -4828,12 +4844,22 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args
local_args.erase(local_args.begin()); local_args.erase(local_args.begin());
} }
} }
if (local_args.size() > 0 && local_args[0] == "verbose") while (local_args.size() > 0)
{ {
verbose = true; if (local_args[0] == "verbose")
verbose = true;
else if (local_args[0] == "uses")
uses = true;
else
{
fail_msg_writer() << tr("Invalid keyword: ") << local_args.front();
break;
}
local_args.erase(local_args.begin()); local_args.erase(local_args.begin());
} }
const uint64_t blockchain_height = m_wallet->get_blockchain_current_height();
PAUSE_READLINE(); PAUSE_READLINE();
std::set<uint32_t> subaddr_indices; std::set<uint32_t> subaddr_indices;
@ -4867,9 +4893,16 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args
verbose_string = (boost::format("%68s%68s") % tr("pubkey") % tr("key image")).str(); verbose_string = (boost::format("%68s%68s") % tr("pubkey") % tr("key image")).str();
message_writer() << boost::format("%21s%8s%12s%8s%16s%68s%16s%s") % tr("amount") % tr("spent") % tr("unlocked") % tr("ringct") % tr("global index") % tr("tx id") % tr("addr index") % verbose_string; message_writer() << boost::format("%21s%8s%12s%8s%16s%68s%16s%s") % tr("amount") % tr("spent") % tr("unlocked") % tr("ringct") % tr("global index") % tr("tx id") % tr("addr index") % verbose_string;
} }
std::string verbose_string; std::string extra_string;
if (verbose) if (verbose)
verbose_string = (boost::format("%68s%68s") % td.get_public_key() % (td.m_key_image_known ? epee::string_tools::pod_to_hex(td.m_key_image) : td.m_key_image_partial ? (epee::string_tools::pod_to_hex(td.m_key_image) + "/p") : std::string(64, '?'))).str(); extra_string += (boost::format("%68s%68s") % td.get_public_key() % (td.m_key_image_known ? epee::string_tools::pod_to_hex(td.m_key_image) : td.m_key_image_partial ? (epee::string_tools::pod_to_hex(td.m_key_image) + "/p") : std::string(64, '?'))).str();
if (uses)
{
std::vector<uint64_t> heights;
for (const auto &e: td.m_uses) heights.push_back(e.first);
const std::pair<std::string, std::string> line = show_outputs_line(heights, blockchain_height, td.m_spent_height);
extra_string += tr("Heights: ") + line.first + "\n" + line.second;
}
message_writer(td.m_spent ? console_color_magenta : console_color_green, false) << message_writer(td.m_spent ? console_color_magenta : console_color_green, false) <<
boost::format("%21s%8s%12s%8s%16u%68s%16u%s") % boost::format("%21s%8s%12s%8s%16u%68s%16u%s") %
print_money(td.amount()) % print_money(td.amount()) %
@ -4879,7 +4912,7 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args
td.m_global_output_index % td.m_global_output_index %
td.m_txid % td.m_txid %
td.m_subaddr_index.minor % td.m_subaddr_index.minor %
verbose_string; extra_string;
++transfers_found; ++transfers_found;
} }
} }
@ -5031,6 +5064,33 @@ bool simple_wallet::rescan_spent(const std::vector<std::string> &args)
return true; return true;
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
std::pair<std::string, std::string> simple_wallet::show_outputs_line(const std::vector<uint64_t> &heights, uint64_t blockchain_height, uint64_t highlight_height) const
{
std::stringstream ostr;
for (uint64_t h: heights)
blockchain_height = std::max(blockchain_height, h);
for (size_t j = 0; j < heights.size(); ++j)
ostr << (heights[j] == highlight_height ? " *" : " ") << heights[j];
// visualize the distribution, using the code by moneroexamples onion-monero-viewer
const uint64_t resolution = 79;
std::string ring_str(resolution, '_');
for (size_t j = 0; j < heights.size(); ++j)
{
uint64_t pos = (heights[j] * resolution) / blockchain_height;
ring_str[pos] = 'o';
}
if (highlight_height < blockchain_height)
{
uint64_t pos = (highlight_height * resolution) / blockchain_height;
ring_str[pos] = '*';
}
return std::make_pair(ostr.str(), ring_str);
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::print_ring_members(const std::vector<tools::wallet2::pending_tx>& ptx_vector, std::ostream& ostr) bool simple_wallet::print_ring_members(const std::vector<tools::wallet2::pending_tx>& ptx_vector, std::ostream& ostr)
{ {
uint32_t version; uint32_t version;
@ -5101,21 +5161,18 @@ bool simple_wallet::print_ring_members(const std::vector<tools::wallet2::pending
} }
} }
ostr << tr("\nOriginating block heights: "); ostr << tr("\nOriginating block heights: ");
for (size_t j = 0; j < absolute_offsets.size(); ++j)
ostr << tr(j == source.real_output ? " *" : " ") << res.outs[j].height;
spent_key_height[i] = res.outs[source.real_output].height; spent_key_height[i] = res.outs[source.real_output].height;
spent_key_txid [i] = res.outs[source.real_output].txid; spent_key_txid [i] = res.outs[source.real_output].txid;
// visualize the distribution, using the code by moneroexamples onion-monero-viewer std::vector<uint64_t> heights(absolute_offsets.size(), 0);
const uint64_t resolution = 79; uint64_t highlight_height = std::numeric_limits<uint64_t>::max();
std::string ring_str(resolution, '_');
for (size_t j = 0; j < absolute_offsets.size(); ++j) for (size_t j = 0; j < absolute_offsets.size(); ++j)
{ {
uint64_t pos = (res.outs[j].height * resolution) / blockchain_height; heights[j] = res.outs[j].height;
ring_str[pos] = 'o'; if (j == source.real_output)
highlight_height = heights[j];
} }
uint64_t pos = (res.outs[source.real_output].height * resolution) / blockchain_height; std::pair<std::string, std::string> ring_str = show_outputs_line(heights, highlight_height);
ring_str[pos] = '*'; ostr << ring_str.first << tr("\n|") << ring_str.second << tr("|\n");
ostr << tr("\n|") << ring_str << tr("|\n");
} }
// warn if rings contain keys originating from the same tx or temporally very close block heights // warn if rings contain keys originating from the same tx or temporally very close block heights
bool are_keys_from_same_tx = false; bool are_keys_from_same_tx = false;

View file

@ -142,6 +142,7 @@ namespace cryptonote
bool set_subaddress_lookahead(const std::vector<std::string> &args = std::vector<std::string>()); bool set_subaddress_lookahead(const std::vector<std::string> &args = std::vector<std::string>());
bool set_segregation_height(const std::vector<std::string> &args = std::vector<std::string>()); bool set_segregation_height(const std::vector<std::string> &args = std::vector<std::string>());
bool set_ignore_fractional_outputs(const std::vector<std::string> &args = std::vector<std::string>()); bool set_ignore_fractional_outputs(const std::vector<std::string> &args = std::vector<std::string>());
bool set_track_uses(const std::vector<std::string> &args = std::vector<std::string>());
bool set_device_name(const std::vector<std::string> &args = std::vector<std::string>()); bool set_device_name(const std::vector<std::string> &args = std::vector<std::string>());
bool help(const std::vector<std::string> &args = std::vector<std::string>()); bool help(const std::vector<std::string> &args = std::vector<std::string>());
bool start_mining(const std::vector<std::string> &args); bool start_mining(const std::vector<std::string> &args);
@ -251,6 +252,7 @@ namespace cryptonote
bool print_seed(bool encrypted); bool print_seed(bool encrypted);
void key_images_sync_intern(); void key_images_sync_intern();
void on_refresh_finished(uint64_t start_height, uint64_t fetched_blocks, bool is_init, bool received_money); void on_refresh_finished(uint64_t start_height, uint64_t fetched_blocks, bool is_init, bool received_money);
std::pair<std::string, std::string> show_outputs_line(const std::vector<uint64_t> &heights, uint64_t blockchain_height, uint64_t highlight_height = std::numeric_limits<uint64_t>::max()) const;
struct transfer_view struct transfer_view
{ {

View file

@ -894,6 +894,7 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended):
m_key_reuse_mitigation2(true), m_key_reuse_mitigation2(true),
m_segregation_height(0), m_segregation_height(0),
m_ignore_fractional_outputs(true), m_ignore_fractional_outputs(true),
m_track_uses(false),
m_is_initialized(false), m_is_initialized(false),
m_kdf_rounds(kdf_rounds), m_kdf_rounds(kdf_rounds),
is_old_file_format(false), is_old_file_format(false),
@ -1448,6 +1449,7 @@ void wallet2::cache_tx_data(const cryptonote::transaction& tx, const crypto::has
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen, const tx_cache_data &tx_cache_data) void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen, const tx_cache_data &tx_cache_data)
{ {
PERF_TIMER(process_new_transaction);
// In this function, tx (probably) only contains the base information // In this function, tx (probably) only contains the base information
// (that is, the prunable stuff may or may not be included) // (that is, the prunable stuff may or may not be included)
if (!miner_tx && !pool) if (!miner_tx && !pool)
@ -1780,11 +1782,12 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
{ {
if(in.type() != typeid(cryptonote::txin_to_key)) if(in.type() != typeid(cryptonote::txin_to_key))
continue; continue;
auto it = m_key_images.find(boost::get<cryptonote::txin_to_key>(in).k_image); const cryptonote::txin_to_key &in_to_key = boost::get<cryptonote::txin_to_key>(in);
auto it = m_key_images.find(in_to_key.k_image);
if(it != m_key_images.end()) if(it != m_key_images.end())
{ {
transfer_details& td = m_transfers[it->second]; transfer_details& td = m_transfers[it->second];
uint64_t amount = boost::get<cryptonote::txin_to_key>(in).amount; uint64_t amount = in_to_key.amount;
if (amount > 0) if (amount > 0)
{ {
if(amount != td.amount()) if(amount != td.amount())
@ -1815,6 +1818,20 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
m_callback->on_money_spent(height, txid, tx, amount, tx, td.m_subaddr_index); m_callback->on_money_spent(height, txid, tx, amount, tx, td.m_subaddr_index);
} }
} }
if (!pool && m_track_uses)
{
PERF_TIMER(track_uses);
std::vector<uint64_t> offsets = cryptonote::relative_output_offsets_to_absolute(in_to_key.key_offsets);
for (transfer_details &td: m_transfers)
{
if ((td.is_rct() ? 0 : td.amount()) != in_to_key.amount)
continue;
for (uint64_t offset: offsets)
if (offset == td.m_global_output_index)
td.m_uses.push_back(std::make_pair(height, txid));
}
}
} }
uint64_t fee = miner_tx ? 0 : tx.version == 1 ? tx_money_spent_in_ins - get_outs_money_amount(tx) : tx.rct_signatures.txnFee; uint64_t fee = miner_tx ? 0 : tx.version == 1 ? tx_money_spent_in_ins - get_outs_money_amount(tx) : tx.rct_signatures.txnFee;
@ -3183,6 +3200,9 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable
value2.SetInt(m_ignore_fractional_outputs ? 1 : 0); value2.SetInt(m_ignore_fractional_outputs ? 1 : 0);
json.AddMember("ignore_fractional_outputs", value2, json.GetAllocator()); json.AddMember("ignore_fractional_outputs", value2, json.GetAllocator());
value2.SetInt(m_track_uses ? 1 : 0);
json.AddMember("track_uses", value2, json.GetAllocator());
value2.SetUint(m_subaddress_lookahead_major); value2.SetUint(m_subaddress_lookahead_major);
json.AddMember("subaddress_lookahead_major", value2, json.GetAllocator()); json.AddMember("subaddress_lookahead_major", value2, json.GetAllocator());
@ -3329,6 +3349,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
m_key_reuse_mitigation2 = true; m_key_reuse_mitigation2 = true;
m_segregation_height = 0; m_segregation_height = 0;
m_ignore_fractional_outputs = true; m_ignore_fractional_outputs = true;
m_track_uses = false;
m_subaddress_lookahead_major = SUBADDRESS_LOOKAHEAD_MAJOR; m_subaddress_lookahead_major = SUBADDRESS_LOOKAHEAD_MAJOR;
m_subaddress_lookahead_minor = SUBADDRESS_LOOKAHEAD_MINOR; m_subaddress_lookahead_minor = SUBADDRESS_LOOKAHEAD_MINOR;
m_original_keys_available = false; m_original_keys_available = false;
@ -3481,6 +3502,8 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
m_segregation_height = field_segregation_height; m_segregation_height = field_segregation_height;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, ignore_fractional_outputs, int, Int, false, true); GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, ignore_fractional_outputs, int, Int, false, true);
m_ignore_fractional_outputs = field_ignore_fractional_outputs; m_ignore_fractional_outputs = field_ignore_fractional_outputs;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, track_uses, int, Int, false, false);
m_track_uses = field_track_uses;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, subaddress_lookahead_major, uint32_t, Uint, false, SUBADDRESS_LOOKAHEAD_MAJOR); GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, subaddress_lookahead_major, uint32_t, Uint, false, SUBADDRESS_LOOKAHEAD_MAJOR);
m_subaddress_lookahead_major = field_subaddress_lookahead_major; m_subaddress_lookahead_major = field_subaddress_lookahead_major;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, subaddress_lookahead_minor, uint32_t, Uint, false, SUBADDRESS_LOOKAHEAD_MINOR); GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, subaddress_lookahead_minor, uint32_t, Uint, false, SUBADDRESS_LOOKAHEAD_MINOR);

View file

@ -273,6 +273,7 @@ namespace tools
bool m_key_image_partial; bool m_key_image_partial;
std::vector<rct::key> m_multisig_k; std::vector<rct::key> m_multisig_k;
std::vector<multisig_info> m_multisig_info; // one per other participant std::vector<multisig_info> m_multisig_info; // one per other participant
std::vector<std::pair<uint64_t, crypto::hash>> m_uses;
bool is_rct() const { return m_rct; } bool is_rct() const { return m_rct; }
uint64_t amount() const { return m_amount; } uint64_t amount() const { return m_amount; }
@ -297,6 +298,7 @@ namespace tools
FIELD(m_key_image_partial) FIELD(m_key_image_partial)
FIELD(m_multisig_k) FIELD(m_multisig_k)
FIELD(m_multisig_info) FIELD(m_multisig_info)
FIELD(m_uses)
END_SERIALIZE() END_SERIALIZE()
}; };
@ -984,6 +986,8 @@ namespace tools
void ignore_fractional_outputs(bool value) { m_ignore_fractional_outputs = value; } void ignore_fractional_outputs(bool value) { m_ignore_fractional_outputs = value; }
bool confirm_non_default_ring_size() const { return m_confirm_non_default_ring_size; } bool confirm_non_default_ring_size() const { return m_confirm_non_default_ring_size; }
void confirm_non_default_ring_size(bool always) { m_confirm_non_default_ring_size = always; } void confirm_non_default_ring_size(bool always) { m_confirm_non_default_ring_size = always; }
bool track_uses() const { return m_track_uses; }
void track_uses(bool value) { m_track_uses = value; }
const std::string & device_name() const { return m_device_name; } const std::string & device_name() const { return m_device_name; }
void device_name(const std::string & device_name) { m_device_name = device_name; } void device_name(const std::string & device_name) { m_device_name = device_name; }
const std::string & device_derivation_path() const { return m_device_derivation_path; } const std::string & device_derivation_path() const { return m_device_derivation_path; }
@ -1395,6 +1399,7 @@ namespace tools
bool m_key_reuse_mitigation2; bool m_key_reuse_mitigation2;
uint64_t m_segregation_height; uint64_t m_segregation_height;
bool m_ignore_fractional_outputs; bool m_ignore_fractional_outputs;
bool m_track_uses;
bool m_is_initialized; bool m_is_initialized;
NodeRPCProxy m_node_rpc_proxy; NodeRPCProxy m_node_rpc_proxy;
std::unordered_set<crypto::hash> m_scanned_pool_txs[2]; std::unordered_set<crypto::hash> m_scanned_pool_txs[2];
@ -1444,7 +1449,7 @@ namespace tools
}; };
} }
BOOST_CLASS_VERSION(tools::wallet2, 27) BOOST_CLASS_VERSION(tools::wallet2, 27)
BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 10) BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 11)
BOOST_CLASS_VERSION(tools::wallet2::multisig_info, 1) BOOST_CLASS_VERSION(tools::wallet2::multisig_info, 1)
BOOST_CLASS_VERSION(tools::wallet2::multisig_info::LR, 0) BOOST_CLASS_VERSION(tools::wallet2::multisig_info::LR, 0)
BOOST_CLASS_VERSION(tools::wallet2::multisig_tx_set, 1) BOOST_CLASS_VERSION(tools::wallet2::multisig_tx_set, 1)
@ -1593,6 +1598,9 @@ namespace boost
return; return;
} }
a & x.m_key_image_requested; a & x.m_key_image_requested;
if (ver < 11)
return;
a & x.m_uses;
} }
template <class Archive> template <class Archive>