wallet cli: print originating block heights of mixin keys when making transfer

This commit is contained in:
kenshi84 2016-12-23 21:04:54 +09:00
parent c6ec939626
commit f1dde1a429
7 changed files with 175 additions and 18 deletions

View file

@ -1778,7 +1778,7 @@ bool Blockchain::get_outs(const COMMAND_RPC_GET_OUTPUTS_BIN::request& req, COMMA
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));
res.outs.push_back({od.pubkey, od.commitment, unlocked});
res.outs.push_back({od.pubkey, od.commitment, unlocked, od.height, toi.first});
}
return true;
}

View file

@ -300,6 +300,8 @@ namespace cryptonote
outkey.key = epee::string_tools::pod_to_hex(i.key);
outkey.mask = epee::string_tools::pod_to_hex(i.mask);
outkey.unlocked = i.unlocked;
outkey.height = i.height;
outkey.txid = epee::string_tools::pod_to_hex(i.txid);
}
res.status = CORE_RPC_STATUS_OK;

View file

@ -49,7 +49,7 @@ namespace cryptonote
// advance which version they will stop working with
// Don't go over 32767 for any of these
#define CORE_RPC_VERSION_MAJOR 1
#define CORE_RPC_VERSION_MINOR 3
#define CORE_RPC_VERSION_MINOR 4
#define CORE_RPC_VERSION (((CORE_RPC_VERSION_MAJOR)<<16)|(CORE_RPC_VERSION_MINOR))
struct COMMAND_RPC_GET_HEIGHT
@ -329,11 +329,15 @@ namespace cryptonote
crypto::public_key key;
rct::key mask;
bool unlocked;
uint64_t height;
crypto::hash txid;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE_VAL_POD_AS_BLOB(key)
KV_SERIALIZE_VAL_POD_AS_BLOB(mask)
KV_SERIALIZE(unlocked)
KV_SERIALIZE(height)
KV_SERIALIZE_VAL_POD_AS_BLOB(txid)
END_KV_SERIALIZE_MAP()
};
@ -365,11 +369,15 @@ namespace cryptonote
std::string key;
std::string mask;
bool unlocked;
uint64_t height;
std::string txid;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(key)
KV_SERIALIZE(mask)
KV_SERIALIZE(unlocked)
KV_SERIALIZE(height)
KV_SERIALIZE(txid)
END_KV_SERIALIZE_MAP()
};

View file

@ -371,6 +371,17 @@ bool simple_wallet::set_always_confirm_transfers(const std::vector<std::string>
return true;
}
bool simple_wallet::set_print_ring_members(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
{
const auto pwd_container = get_and_verify_password();
if (pwd_container)
{
m_wallet->print_ring_members(is_it_true(args[1]));
m_wallet->rewrite(m_wallet_file, pwd_container->password());
}
return true;
}
bool simple_wallet::set_store_tx_info(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
{
if (m_wallet->watch_only())
@ -570,7 +581,7 @@ simple_wallet::simple_wallet()
m_cmd_binder.set_handler("viewkey", boost::bind(&simple_wallet::viewkey, this, _1), tr("Display private view key"));
m_cmd_binder.set_handler("spendkey", boost::bind(&simple_wallet::spendkey, this, _1), tr("Display private spend key"));
m_cmd_binder.set_handler("seed", boost::bind(&simple_wallet::seed, this, _1), tr("Display Electrum-style mnemonic seed"));
m_cmd_binder.set_handler("set", boost::bind(&simple_wallet::set_variable, this, _1), tr("Available options: seed language - set wallet seed language; always-confirm-transfers <1|0> - whether to confirm unsplit txes; store-tx-info <1|0> - whether to store outgoing tx info (destination address, payment ID, tx secret key) for future reference; default-mixin <n> - set default mixin (default is 4); auto-refresh <1|0> - whether to automatically sync new blocks from the daemon; refresh-type <full|optimize-coinbase|no-coinbase|default> - set wallet refresh behaviour; priority [1|2|3] - normal/elevated/priority fee; confirm-missing-payment-id <1|0>"));
m_cmd_binder.set_handler("set", boost::bind(&simple_wallet::set_variable, this, _1), tr("Available options: seed language - set wallet seed language; always-confirm-transfers <1|0> - whether to confirm unsplit txes; print-ring-members <1|0> - whether to print detailed information about ring members during confirmation; store-tx-info <1|0> - whether to store outgoing tx info (destination address, payment ID, tx secret key) for future reference; default-mixin <n> - set default mixin (default is 4); auto-refresh <1|0> - whether to automatically sync new blocks from the daemon; refresh-type <full|optimize-coinbase|no-coinbase|default> - set wallet refresh behaviour; priority [1|2|3] - normal/elevated/priority fee; confirm-missing-payment-id <1|0>"));
m_cmd_binder.set_handler("rescan_spent", boost::bind(&simple_wallet::rescan_spent, this, _1), tr("Rescan blockchain for spent outputs"));
m_cmd_binder.set_handler("get_tx_key", boost::bind(&simple_wallet::get_tx_key, this, _1), tr("Get transaction key (r) for a given <txid>"));
m_cmd_binder.set_handler("check_tx_key", boost::bind(&simple_wallet::check_tx_key, this, _1), tr("Check amount going to <address> in <txid>"));
@ -596,6 +607,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
{
success_msg_writer() << "seed = " << m_wallet->get_seed_language();
success_msg_writer() << "always-confirm-transfers = " << m_wallet->always_confirm_transfers();
success_msg_writer() << "print-ring-members = " << m_wallet->print_ring_members();
success_msg_writer() << "store-tx-info = " << m_wallet->store_tx_info();
success_msg_writer() << "default-mixin = " << m_wallet->default_mixin();
success_msg_writer() << "auto-refresh = " << m_wallet->auto_refresh();
@ -632,6 +644,19 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
return true;
}
}
else if (args[0] == "print-ring-members")
{
if (args.size() <= 1)
{
fail_msg_writer() << tr("set print-ring-members: needs an argument (0 or 1)");
return true;
}
else
{
set_print_ring_members(args);
return true;
}
}
else if (args[0] == "store-tx-info")
{
if (args.size() <= 1)
@ -1119,10 +1144,12 @@ bool simple_wallet::handle_command_line(const boost::program_options::variables_
return true;
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::try_connect_to_daemon(bool silent)
bool simple_wallet::try_connect_to_daemon(bool silent, uint32_t* version)
{
uint32_t version = 0;
if (!m_wallet->check_connection(&version))
uint32_t version_ = 0;
if (!version)
version = &version_;
if (!m_wallet->check_connection(version))
{
if (!silent)
fail_msg_writer() << tr("wallet failed to connect to daemon: ") << m_wallet->get_daemon_address() << ". " <<
@ -1130,10 +1157,10 @@ bool simple_wallet::try_connect_to_daemon(bool silent)
"Please make sure daemon is running or restart the wallet with the correct daemon address.");
return false;
}
if (!m_allow_mismatched_daemon_version && ((version >> 16) != CORE_RPC_VERSION_MAJOR))
if (!m_allow_mismatched_daemon_version && ((*version >> 16) != CORE_RPC_VERSION_MAJOR))
{
if (!silent)
fail_msg_writer() << boost::format(tr("Daemon uses a different RPC major version (%u) than the wallet (%u): %s. Either update one of them, or use --allow-mismatched-daemon-version.")) % (version>>16) % CORE_RPC_VERSION_MAJOR % m_wallet->get_daemon_address();
fail_msg_writer() << boost::format(tr("Daemon uses a different RPC major version (%u) than the wallet (%u): %s. Either update one of them, or use --allow-mismatched-daemon-version.")) % (*version>>16) % CORE_RPC_VERSION_MAJOR % m_wallet->get_daemon_address();
return false;
}
return true;
@ -1865,6 +1892,108 @@ bool simple_wallet::rescan_spent(const std::vector<std::string> &args)
return true;
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::print_ring_members(const std::vector<tools::wallet2::pending_tx>& ptx_vector, std::ostream& ostr)
{
uint32_t version;
if (!try_connect_to_daemon(false, &version))
{
fail_msg_writer() << tr("failed to connect to the daemon");
return false;
}
// available for RPC version 1.4 or higher
if (version < 0x10004)
return true;
std::string err;
uint64_t blockchain_height = get_daemon_blockchain_height(err);
if (!err.empty())
{
fail_msg_writer() << tr("failed to get blockchain height: ") << err;
return false;
}
// for each transaction
for (size_t n = 0; n < ptx_vector.size(); ++n)
{
const cryptonote::transaction& tx = ptx_vector[n].tx;
const tools::wallet2::tx_construction_data& construction_data = ptx_vector[n].construction_data;
ostr << boost::format(tr("\nTransaction %llu/%llu: txid=%s")) % (n + 1) % ptx_vector.size() % cryptonote::get_transaction_hash(tx);
// for each input
std::vector<int> spent_key_height(tx.vin.size());
std::vector<crypto::hash> spent_key_txid (tx.vin.size());
for (size_t i = 0; i < tx.vin.size(); ++i)
{
if (tx.vin[i].type() != typeid(cryptonote::txin_to_key))
continue;
const cryptonote::txin_to_key& in_key = boost::get<cryptonote::txin_to_key>(tx.vin[i]);
const cryptonote::tx_source_entry& source = construction_data.sources[i];
ostr << boost::format(tr("\nInput %llu/%llu: amount=%s")) % (i + 1) % tx.vin.size() % print_money(source.amount);
// convert relative offsets of ring member keys into absolute offsets (indices) associated with the amount
std::vector<uint64_t> absolute_offsets = cryptonote::relative_output_offsets_to_absolute(in_key.key_offsets);
// get block heights from which those ring member keys originated
COMMAND_RPC_GET_OUTPUTS_BIN::request req = AUTO_VAL_INIT(req);
req.outputs.resize(absolute_offsets.size());
for (size_t j = 0; j < absolute_offsets.size(); ++j)
{
req.outputs[j].amount = in_key.amount;
req.outputs[j].index = absolute_offsets[j];
}
COMMAND_RPC_GET_OUTPUTS_BIN::response res = AUTO_VAL_INIT(res);
bool r = net_utils::invoke_http_bin_remote_command2(m_wallet->get_daemon_address() + "/get_outs.bin", req, res, m_http_client);
err = interpret_rpc_response(r, res.status);
if (!err.empty())
{
fail_msg_writer() << tr("failed to get output: ") << err;
return false;
}
// make sure that returned block heights are less than blockchain height
for (auto& res_out : res.outs)
{
if (res_out.height >= blockchain_height)
{
fail_msg_writer() << tr("output key's originating block height shouldn't be higher than the blockchain height");
return false;
}
}
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_txid [i] = res.outs[source.real_output].txid;
// 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 < absolute_offsets.size(); ++j)
{
uint64_t pos = (res.outs[j].height * resolution) / blockchain_height;
ring_str[pos] = 'o';
}
uint64_t pos = (res.outs[source.real_output].height * resolution) / blockchain_height;
ring_str[pos] = '*';
ostr << tr("\n|") << ring_str << tr("|\n");
}
// 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_close_height = false;
for (size_t i = 0; i < tx.vin.size(); ++i) {
for (size_t j = i + 1; j < tx.vin.size(); ++j)
{
if (spent_key_txid[i] == spent_key_txid[j])
are_keys_from_same_tx = true;
if (std::abs(spent_key_height[i] - spent_key_height[j]) < 5)
are_keys_from_close_height = true;
}
}
if (are_keys_from_same_tx || are_keys_from_close_height)
{
ostr
<< tr("\nWarning: Some input keys being spent are from ")
<< tr(are_keys_from_same_tx ? "the same transaction" : "blocks that are temporally very close")
<< tr(", which can break the anonymity of ring signature. Make sure this is intentional!");
}
ostr << ENDL;
}
return true;
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::string> &args_)
{
if (!try_connect_to_daemon())
@ -2075,7 +2204,12 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
float days = locked_blocks / 720.0f;
prompt << boost::format(tr(".\nThis transaction will unlock on block %llu, in approximately %s days (assuming 2 minutes per block)")) % ((unsigned long long)unlock_block) % days;
}
prompt << tr(".") << ENDL << tr("Is this okay? (Y/Yes/N/No): ");
if (m_wallet->print_ring_members())
{
if (!print_ring_members(ptx_vector, prompt))
return true;
}
prompt << ENDL << tr("Is this okay? (Y/Yes/N/No): ");
std::string accepted = command_line::input_line(prompt.str());
if (std::cin.eof())
@ -2503,19 +2637,21 @@ bool simple_wallet::sweep_all(const std::vector<std::string> &args_)
total_sent += m_wallet->get_transfer_details(i).amount();
}
std::string prompt_str;
std::ostringstream prompt;
if (!print_ring_members(ptx_vector, prompt))
return true;
if (ptx_vector.size() > 1) {
prompt_str = (boost::format(tr("Sweeping %s in %llu transactions for a total fee of %s. Is this okay? (Y/Yes/N/No): ")) %
prompt << boost::format(tr("Sweeping %s in %llu transactions for a total fee of %s. Is this okay? (Y/Yes/N/No): ")) %
print_money(total_sent) %
((unsigned long long)ptx_vector.size()) %
print_money(total_fee)).str();
print_money(total_fee);
}
else {
prompt_str = (boost::format(tr("Sweeping %s for a total fee of %s. Is this okay? (Y/Yes/N/No)")) %
prompt << boost::format(tr("Sweeping %s for a total fee of %s. Is this okay? (Y/Yes/N/No)")) %
print_money(total_sent) %
print_money(total_fee)).str();
print_money(total_fee);
}
std::string accepted = command_line::input_line(prompt_str);
std::string accepted = command_line::input_line(prompt.str());
if (std::cin.eof())
return true;
if (!command_line::is_yes(accepted))

View file

@ -106,6 +106,7 @@ namespace cryptonote
*/
bool seed_set_language(const std::vector<std::string> &args = std::vector<std::string>());
bool set_always_confirm_transfers(const std::vector<std::string> &args = std::vector<std::string>());
bool set_print_ring_members(const std::vector<std::string> &args = std::vector<std::string>());
bool set_store_tx_info(const std::vector<std::string> &args = std::vector<std::string>());
bool set_default_mixin(const std::vector<std::string> &args = std::vector<std::string>());
bool set_auto_refresh(const std::vector<std::string> &args = std::vector<std::string>());
@ -160,11 +161,12 @@ namespace cryptonote
bool show_transfer(const std::vector<std::string> &args);
uint64_t get_daemon_blockchain_height(std::string& err);
bool try_connect_to_daemon(bool silent = false);
bool try_connect_to_daemon(bool silent = false, uint32_t* version = nullptr);
bool ask_wallet_create_if_needed();
bool accept_loaded_tx(const std::function<size_t()> get_num_txes, const std::function<const tools::wallet2::tx_construction_data&(size_t)> &get_tx, const std::string &extra_message = std::string());
bool accept_loaded_tx(const tools::wallet2::unsigned_tx_set &txs);
bool accept_loaded_tx(const tools::wallet2::signed_tx_set &txs);
bool print_ring_members(const std::vector<tools::wallet2::pending_tx>& ptx_vector, std::ostream& ostr);
/*!
* \brief Prints the seed with a nice message

View file

@ -1808,6 +1808,9 @@ bool wallet2::store_keys(const std::string& keys_file_name, const std::string& p
value2.SetInt(m_always_confirm_transfers ? 1 :0);
json.AddMember("always_confirm_transfers", value2, json.GetAllocator());
value2.SetInt(m_print_ring_members ? 1 :0);
json.AddMember("print_ring_members", value2, json.GetAllocator());
value2.SetInt(m_store_tx_info ? 1 :0);
json.AddMember("store_tx_info", value2, json.GetAllocator());
@ -1890,6 +1893,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const std::string& pa
is_old_file_format = true;
m_watch_only = false;
m_always_confirm_transfers = false;
m_print_ring_members = false;
m_default_mixin = 0;
m_default_priority = 0;
m_auto_refresh = true;
@ -1920,6 +1924,8 @@ bool wallet2::load_keys(const std::string& keys_file_name, const std::string& pa
m_watch_only = field_watch_only;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, always_confirm_transfers, int, Int, false, true);
m_always_confirm_transfers = field_always_confirm_transfers;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, print_ring_members, int, Int, false, true);
m_print_ring_members = field_print_ring_members;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, store_tx_keys, int, Int, false, true);
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, store_tx_info, int, Int, false, true);
m_store_tx_info = ((field_store_tx_keys != 0) || (field_store_tx_info != 0));

View file

@ -98,7 +98,7 @@ namespace tools
};
private:
wallet2(const wallet2&) : m_run(true), m_callback(0), m_testnet(false), m_always_confirm_transfers(true), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true) {}
wallet2(const wallet2&) : m_run(true), m_callback(0), m_testnet(false), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true) {}
public:
static const char* tr(const char* str);// { return i18n_translate(str, "cryptonote::simple_wallet"); }
@ -119,7 +119,7 @@ namespace tools
//! Uses stdin and stdout. Returns a wallet2 and password for wallet with no file if no errors.
static std::pair<std::unique_ptr<wallet2>, password_container> make_new(const boost::program_options::variables_map& vm);
wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_always_confirm_transfers(true), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_restricted(restricted), is_old_file_format(false) {}
wallet2(bool testnet = false, bool restricted = false) : m_run(true), m_callback(0), m_testnet(testnet), m_always_confirm_transfers(true), m_print_ring_members(false), m_store_tx_info(true), m_default_mixin(0), m_default_priority(0), m_refresh_type(RefreshOptimizeCoinbase), m_auto_refresh(true), m_refresh_from_block_height(0), m_confirm_missing_payment_id(true), m_restricted(restricted), is_old_file_format(false) {}
struct transfer_details
{
uint64_t m_block_height;
@ -476,6 +476,8 @@ namespace tools
bool always_confirm_transfers() const { return m_always_confirm_transfers; }
void always_confirm_transfers(bool always) { m_always_confirm_transfers = always; }
bool print_ring_members() const { return m_print_ring_members; }
void print_ring_members(bool value) { m_print_ring_members = value; }
bool store_tx_info() const { return m_store_tx_info; }
void store_tx_info(bool store) { m_store_tx_info = store; }
uint32_t default_mixin() const { return m_default_mixin; }
@ -624,6 +626,7 @@ namespace tools
bool is_old_file_format; /*!< Whether the wallet file is of an old file format */
bool m_watch_only; /*!< no spend key */
bool m_always_confirm_transfers;
bool m_print_ring_members;
bool m_store_tx_info; /*!< request txkey to be returned in RPC, and store in the wallet cache file */
uint32_t m_default_mixin;
uint32_t m_default_priority;