mirror of
https://git.wownero.com/wownero/wownero.git
synced 2024-08-15 01:03:23 +00:00
wallet: adds rescan_bc option with preserving key images
- enables to perform rescan_spent / ki sync with untrusted daemon. Spent check status involves RPC calls which require trusted daemon status as it leaks information. The new call performs soft reset while preserving key images thus a sequence: refresh, ki sync / import, rescan_bc keep_ki will correctly perform spent checking without need for trusted daemon. - useful to detect spent outputs with untrusted daemon on watch_only / multisig / hw-cold wallets after expensive key image sync. - cli: rescan_bc keep_ki
This commit is contained in:
parent
c88e992104
commit
f42263ebb6
4 changed files with 160 additions and 26 deletions
|
@ -190,7 +190,7 @@ namespace
|
||||||
const char* USAGE_CHECK_RESERVE_PROOF("check_reserve_proof <address> <signature_file> [<message>]");
|
const char* USAGE_CHECK_RESERVE_PROOF("check_reserve_proof <address> <signature_file> [<message>]");
|
||||||
const char* USAGE_SHOW_TRANSFERS("show_transfers [in|out|pending|failed|pool|coinbase] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]]");
|
const char* USAGE_SHOW_TRANSFERS("show_transfers [in|out|pending|failed|pool|coinbase] [index=<N1>[,<N2>,...]] [<min_height> [<max_height>]]");
|
||||||
const char* USAGE_UNSPENT_OUTPUTS("unspent_outputs [index=<N1>[,<N2>,...]] [<min_amount> [<max_amount>]]");
|
const char* USAGE_UNSPENT_OUTPUTS("unspent_outputs [index=<N1>[,<N2>,...]] [<min_amount> [<max_amount>]]");
|
||||||
const char* USAGE_RESCAN_BC("rescan_bc [hard]");
|
const char* USAGE_RESCAN_BC("rescan_bc [hard|soft|keep_ki] [start_height=0]");
|
||||||
const char* USAGE_SET_TX_NOTE("set_tx_note <txid> [free text note]");
|
const char* USAGE_SET_TX_NOTE("set_tx_note <txid> [free text note]");
|
||||||
const char* USAGE_GET_TX_NOTE("get_tx_note <txid>");
|
const char* USAGE_GET_TX_NOTE("get_tx_note <txid>");
|
||||||
const char* USAGE_GET_DESCRIPTION("get_description");
|
const char* USAGE_GET_DESCRIPTION("get_description");
|
||||||
|
@ -4748,8 +4748,16 @@ bool simple_wallet::refresh_main(uint64_t start_height, enum ResetType reset, bo
|
||||||
|
|
||||||
LOCK_IDLE_SCOPE();
|
LOCK_IDLE_SCOPE();
|
||||||
|
|
||||||
|
crypto::hash transfer_hash_pre{};
|
||||||
|
uint64_t height_pre, height_post;
|
||||||
|
|
||||||
if (reset != ResetNone)
|
if (reset != ResetNone)
|
||||||
m_wallet->rescan_blockchain(reset == ResetHard, false);
|
{
|
||||||
|
if (reset == ResetSoftKeepKI)
|
||||||
|
height_pre = m_wallet->hash_m_transfers(-1, transfer_hash_pre);
|
||||||
|
|
||||||
|
m_wallet->rescan_blockchain(reset == ResetHard, false, reset == ResetSoftKeepKI);
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef HAVE_READLINE
|
#ifdef HAVE_READLINE
|
||||||
rdln::suspend_readline pause_readline;
|
rdln::suspend_readline pause_readline;
|
||||||
|
@ -4766,6 +4774,18 @@ bool simple_wallet::refresh_main(uint64_t start_height, enum ResetType reset, bo
|
||||||
m_in_manual_refresh.store(true, std::memory_order_relaxed);
|
m_in_manual_refresh.store(true, std::memory_order_relaxed);
|
||||||
epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){m_in_manual_refresh.store(false, std::memory_order_relaxed);});
|
epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){m_in_manual_refresh.store(false, std::memory_order_relaxed);});
|
||||||
m_wallet->refresh(m_wallet->is_trusted_daemon(), start_height, fetched_blocks, received_money);
|
m_wallet->refresh(m_wallet->is_trusted_daemon(), start_height, fetched_blocks, received_money);
|
||||||
|
|
||||||
|
if (reset == ResetSoftKeepKI)
|
||||||
|
{
|
||||||
|
m_wallet->finish_rescan_bc_keep_key_images(height_pre, transfer_hash_pre);
|
||||||
|
|
||||||
|
height_post = m_wallet->get_num_transfer_details();
|
||||||
|
if (height_pre != height_post)
|
||||||
|
{
|
||||||
|
message_writer() << tr("New transfer received since rescan was started. Key images are incomplete.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ok = true;
|
ok = true;
|
||||||
// Clear line "Height xxx of xxx"
|
// Clear line "Height xxx of xxx"
|
||||||
std::cout << "\r \r";
|
std::cout << "\r \r";
|
||||||
|
@ -7773,18 +7793,43 @@ bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_)
|
||||||
//----------------------------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------------------------
|
||||||
bool simple_wallet::rescan_blockchain(const std::vector<std::string> &args_)
|
bool simple_wallet::rescan_blockchain(const std::vector<std::string> &args_)
|
||||||
{
|
{
|
||||||
bool hard = false;
|
uint64_t start_height = 0;
|
||||||
|
ResetType reset_type = ResetSoft;
|
||||||
|
|
||||||
if (!args_.empty())
|
if (!args_.empty())
|
||||||
{
|
{
|
||||||
if (args_[0] != "hard")
|
if (args_[0] == "hard")
|
||||||
|
{
|
||||||
|
reset_type = ResetHard;
|
||||||
|
}
|
||||||
|
else if (args_[0] == "soft")
|
||||||
|
{
|
||||||
|
reset_type = ResetSoft;
|
||||||
|
}
|
||||||
|
else if (args_[0] == "keep_ki")
|
||||||
|
{
|
||||||
|
reset_type = ResetSoftKeepKI;
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
PRINT_USAGE(USAGE_RESCAN_BC);
|
PRINT_USAGE(USAGE_RESCAN_BC);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
hard = true;
|
|
||||||
|
if (args_.size() > 1)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
start_height = boost::lexical_cast<uint64_t>( args_[1] );
|
||||||
|
}
|
||||||
|
catch(const boost::bad_lexical_cast &)
|
||||||
|
{
|
||||||
|
start_height = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hard)
|
if (reset_type == ResetHard)
|
||||||
{
|
{
|
||||||
message_writer() << tr("Warning: this will lose any information which can not be recovered from the blockchain.");
|
message_writer() << tr("Warning: this will lose any information which can not be recovered from the blockchain.");
|
||||||
message_writer() << tr("This includes destination addresses, tx secret keys, tx notes, etc");
|
message_writer() << tr("This includes destination addresses, tx secret keys, tx notes, etc");
|
||||||
|
@ -7795,7 +7840,20 @@ bool simple_wallet::rescan_blockchain(const std::vector<std::string> &args_)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return refresh_main(0, hard ? ResetHard : ResetSoft, true);
|
|
||||||
|
const uint64_t wallet_from_height = m_wallet->get_refresh_from_block_height();
|
||||||
|
if (start_height > wallet_from_height)
|
||||||
|
{
|
||||||
|
message_writer() << tr("Warning: your restore height is higher than wallet restore height: ") << wallet_from_height;
|
||||||
|
std::string confirm = input_line(tr("Rescan anyway ? (Y/Yes/N/No): "));
|
||||||
|
if(!std::cin.eof())
|
||||||
|
{
|
||||||
|
if (!command_line::is_yes(confirm))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return refresh_main(start_height, reset_type, true);
|
||||||
}
|
}
|
||||||
//----------------------------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------------------------
|
||||||
void simple_wallet::check_for_messages()
|
void simple_wallet::check_for_messages()
|
||||||
|
|
|
@ -84,7 +84,7 @@ namespace cryptonote
|
||||||
std::string get_command_usage(const std::vector<std::string> &args);
|
std::string get_command_usage(const std::vector<std::string> &args);
|
||||||
private:
|
private:
|
||||||
|
|
||||||
enum ResetType { ResetNone, ResetSoft, ResetHard };
|
enum ResetType { ResetNone, ResetSoft, ResetHard, ResetSoftKeepKI };
|
||||||
|
|
||||||
bool handle_command_line(const boost::program_options::variables_map& vm);
|
bool handle_command_line(const boost::program_options::variables_map& vm);
|
||||||
|
|
||||||
|
|
|
@ -3223,6 +3223,26 @@ bool wallet2::clear()
|
||||||
m_device_last_key_image_sync = 0;
|
m_device_last_key_image_sync = 0;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
//----------------------------------------------------------------------------------------------------
|
||||||
|
void wallet2::clear_soft(bool keep_key_images)
|
||||||
|
{
|
||||||
|
m_blockchain.clear();
|
||||||
|
m_transfers.clear();
|
||||||
|
if (!keep_key_images)
|
||||||
|
m_key_images.clear();
|
||||||
|
m_pub_keys.clear();
|
||||||
|
m_unconfirmed_txs.clear();
|
||||||
|
m_payments.clear();
|
||||||
|
m_confirmed_txs.clear();
|
||||||
|
m_unconfirmed_payments.clear();
|
||||||
|
m_scanned_pool_txs[0].clear();
|
||||||
|
m_scanned_pool_txs[1].clear();
|
||||||
|
|
||||||
|
cryptonote::block b;
|
||||||
|
generate_genesis(b);
|
||||||
|
m_blockchain.push_back(get_block_hash(b));
|
||||||
|
m_last_block_reward = cryptonote::get_outs_money_amount(b.miner_tx);
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Stores wallet information to wallet file.
|
* \brief Stores wallet information to wallet file.
|
||||||
|
@ -5469,8 +5489,12 @@ void wallet2::rescan_spent()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//----------------------------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------------------------
|
||||||
void wallet2::rescan_blockchain(bool hard, bool refresh)
|
void wallet2::rescan_blockchain(bool hard, bool refresh, bool keep_key_images)
|
||||||
{
|
{
|
||||||
|
CHECK_AND_ASSERT_THROW_MES(!hard || !keep_key_images, "Cannot preserve key images on hard rescan");
|
||||||
|
const size_t transfers_cnt = m_transfers.size();
|
||||||
|
crypto::hash transfers_hash{};
|
||||||
|
|
||||||
if(hard)
|
if(hard)
|
||||||
{
|
{
|
||||||
clear();
|
clear();
|
||||||
|
@ -5478,25 +5502,16 @@ void wallet2::rescan_blockchain(bool hard, bool refresh)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_blockchain.clear();
|
if (keep_key_images && refresh)
|
||||||
m_transfers.clear();
|
hash_m_transfers((int64_t) transfers_cnt, transfers_hash);
|
||||||
m_key_images.clear();
|
clear_soft(keep_key_images);
|
||||||
m_pub_keys.clear();
|
|
||||||
m_unconfirmed_txs.clear();
|
|
||||||
m_payments.clear();
|
|
||||||
m_confirmed_txs.clear();
|
|
||||||
m_unconfirmed_payments.clear();
|
|
||||||
m_scanned_pool_txs[0].clear();
|
|
||||||
m_scanned_pool_txs[1].clear();
|
|
||||||
|
|
||||||
cryptonote::block b;
|
|
||||||
generate_genesis(b);
|
|
||||||
m_blockchain.push_back(get_block_hash(b));
|
|
||||||
m_last_block_reward = cryptonote::get_outs_money_amount(b.miner_tx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (refresh)
|
if (refresh)
|
||||||
this->refresh(false);
|
this->refresh(false);
|
||||||
|
|
||||||
|
if (refresh && keep_key_images)
|
||||||
|
finish_rescan_bc_keep_key_images(transfers_cnt, transfers_hash);
|
||||||
}
|
}
|
||||||
//----------------------------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------------------------
|
||||||
bool wallet2::is_transfer_unlocked(const transfer_details& td) const
|
bool wallet2::is_transfer_unlocked(const transfer_details& td) const
|
||||||
|
@ -11392,6 +11407,7 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
|
||||||
auto it = m_key_images.find(boost::get<cryptonote::txin_to_key>(in).k_image);
|
auto it = m_key_images.find(boost::get<cryptonote::txin_to_key>(in).k_image);
|
||||||
if (it != m_key_images.end())
|
if (it != m_key_images.end())
|
||||||
{
|
{
|
||||||
|
THROW_WALLET_EXCEPTION_IF(it->second >= m_transfers.size(), error::wallet_internal_error, std::string("Key images cache contains illegal transfer offset: ") + std::to_string(it->second) + std::string(" m_transfers.size() = ") + std::to_string(m_transfers.size()));
|
||||||
const transfer_details& td = m_transfers[it->second];
|
const transfer_details& td = m_transfers[it->second];
|
||||||
uint64_t amount = boost::get<cryptonote::txin_to_key>(in).amount;
|
uint64_t amount = boost::get<cryptonote::txin_to_key>(in).amount;
|
||||||
if (amount > 0)
|
if (amount > 0)
|
||||||
|
@ -12490,5 +12506,61 @@ void wallet2::throw_on_rpc_response_error(const boost::optional<std::string> &st
|
||||||
THROW_WALLET_EXCEPTION_IF(*status == CORE_RPC_STATUS_BUSY, tools::error::daemon_busy, method);
|
THROW_WALLET_EXCEPTION_IF(*status == CORE_RPC_STATUS_BUSY, tools::error::daemon_busy, method);
|
||||||
THROW_WALLET_EXCEPTION_IF(*status != CORE_RPC_STATUS_OK, tools::error::wallet_generic_rpc_error, method, m_trusted_daemon ? *status : "daemon error");
|
THROW_WALLET_EXCEPTION_IF(*status != CORE_RPC_STATUS_OK, tools::error::wallet_generic_rpc_error, method, m_trusted_daemon ? *status : "daemon error");
|
||||||
}
|
}
|
||||||
|
//----------------------------------------------------------------------------------------------------
|
||||||
|
void wallet2::hash_m_transfer(const transfer_details & transfer, crypto::hash &hash) const
|
||||||
|
{
|
||||||
|
KECCAK_CTX state;
|
||||||
|
keccak_init(&state);
|
||||||
|
keccak_update(&state, (const uint8_t *) transfer.m_txid.data, sizeof(transfer.m_txid.data));
|
||||||
|
keccak_update(&state, (const uint8_t *) transfer.m_internal_output_index, sizeof(transfer.m_internal_output_index));
|
||||||
|
keccak_update(&state, (const uint8_t *) transfer.m_global_output_index, sizeof(transfer.m_global_output_index));
|
||||||
|
keccak_update(&state, (const uint8_t *) transfer.m_amount, sizeof(transfer.m_amount));
|
||||||
|
keccak_finish(&state, (uint8_t *) hash.data);
|
||||||
|
}
|
||||||
|
//----------------------------------------------------------------------------------------------------
|
||||||
|
uint64_t wallet2::hash_m_transfers(int64_t transfer_height, crypto::hash &hash) const
|
||||||
|
{
|
||||||
|
CHECK_AND_ASSERT_THROW_MES(transfer_height > (int64_t)m_transfers.size(), "Hash height is greater than number of transfers");
|
||||||
|
|
||||||
|
KECCAK_CTX state;
|
||||||
|
crypto::hash tmp_hash{};
|
||||||
|
uint64_t current_height = 0;
|
||||||
|
|
||||||
|
keccak_init(&state);
|
||||||
|
for(const transfer_details & transfer : m_transfers){
|
||||||
|
if (transfer_height >= 0 && current_height >= (uint64_t)transfer_height){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
hash_m_transfer(transfer, tmp_hash);
|
||||||
|
keccak_update(&state, (const uint8_t *) transfer.m_block_height, sizeof(transfer.m_block_height));
|
||||||
|
keccak_update(&state, (const uint8_t *) tmp_hash.data, sizeof(tmp_hash.data));
|
||||||
|
current_height += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
keccak_finish(&state, (uint8_t *) hash.data);
|
||||||
|
return current_height;
|
||||||
|
}
|
||||||
|
//----------------------------------------------------------------------------------------------------
|
||||||
|
void wallet2::finish_rescan_bc_keep_key_images(uint64_t transfer_height, const crypto::hash &hash)
|
||||||
|
{
|
||||||
|
// Compute hash of m_transfers, if differs there had to be BC reorg.
|
||||||
|
crypto::hash new_transfers_hash{};
|
||||||
|
hash_m_transfers((int64_t) transfer_height, new_transfers_hash);
|
||||||
|
|
||||||
|
if (new_transfers_hash != hash)
|
||||||
|
{
|
||||||
|
// Soft-Reset to avoid inconsistency in case of BC reorg.
|
||||||
|
clear_soft(false); // keep_key_images works only with soft reset.
|
||||||
|
THROW_WALLET_EXCEPTION_IF(true, error::wallet_internal_error, "Transfers changed during rescan, soft or hard rescan is needed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore key images in m_transfers from m_key_images
|
||||||
|
for(auto it = m_key_images.begin(); it != m_key_images.end(); it++)
|
||||||
|
{
|
||||||
|
THROW_WALLET_EXCEPTION_IF(it->second >= m_transfers.size(), error::wallet_internal_error, "Key images cache contains illegal transfer offset");
|
||||||
|
m_transfers[it->second].m_key_image = it->first;
|
||||||
|
m_transfers[it->second].m_key_image_known = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -818,7 +818,7 @@ namespace tools
|
||||||
|
|
||||||
uint64_t get_blockchain_current_height() const { return m_light_wallet_blockchain_height ? m_light_wallet_blockchain_height : m_blockchain.size(); }
|
uint64_t get_blockchain_current_height() const { return m_light_wallet_blockchain_height ? m_light_wallet_blockchain_height : m_blockchain.size(); }
|
||||||
void rescan_spent();
|
void rescan_spent();
|
||||||
void rescan_blockchain(bool hard, bool refresh = true);
|
void rescan_blockchain(bool hard, bool refresh = true, bool keep_key_images = false);
|
||||||
bool is_transfer_unlocked(const transfer_details& td) const;
|
bool is_transfer_unlocked(const transfer_details& td) const;
|
||||||
bool is_transfer_unlocked(uint64_t unlock_time, uint64_t block_height) const;
|
bool is_transfer_unlocked(uint64_t unlock_time, uint64_t block_height) const;
|
||||||
|
|
||||||
|
@ -1248,6 +1248,9 @@ namespace tools
|
||||||
void set_tx_notify(const std::shared_ptr<tools::Notify> ¬ify) { m_tx_notify = notify; }
|
void set_tx_notify(const std::shared_ptr<tools::Notify> ¬ify) { m_tx_notify = notify; }
|
||||||
|
|
||||||
bool is_tx_spendtime_unlocked(uint64_t unlock_time, uint64_t block_height) const;
|
bool is_tx_spendtime_unlocked(uint64_t unlock_time, uint64_t block_height) const;
|
||||||
|
void hash_m_transfer(const transfer_details & transfer, crypto::hash &hash) const;
|
||||||
|
uint64_t hash_m_transfers(int64_t transfer_height, crypto::hash &hash) const;
|
||||||
|
void finish_rescan_bc_keep_key_images(uint64_t transfer_height, const crypto::hash &hash);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/*!
|
/*!
|
||||||
|
@ -1269,6 +1272,7 @@ namespace tools
|
||||||
void detach_blockchain(uint64_t height);
|
void detach_blockchain(uint64_t height);
|
||||||
void get_short_chain_history(std::list<crypto::hash>& ids, uint64_t granularity = 1) const;
|
void get_short_chain_history(std::list<crypto::hash>& ids, uint64_t granularity = 1) const;
|
||||||
bool clear();
|
bool clear();
|
||||||
|
void clear_soft(bool keep_key_images=false);
|
||||||
void pull_blocks(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices);
|
void pull_blocks(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices);
|
||||||
void pull_hashes(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<crypto::hash> &hashes);
|
void pull_hashes(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<crypto::hash> &hashes);
|
||||||
void fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, bool force = false);
|
void fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, bool force = false);
|
||||||
|
|
Loading…
Reference in a new issue