Add non-blocking isRefreshing(); make height retrieval non-blocking

This commit is contained in:
Jason Rhinelander 2021-07-07 18:10:29 -03:00
parent e09b23a143
commit d8662ace7b
6 changed files with 85 additions and 33 deletions

View File

@ -163,7 +163,7 @@ uint64_t PendingTransactionImpl::amount() const
result += dest.amount;
}
service_nodes::staking_components sc;
uint64_t height = m_wallet.blockChainHeight(w);
uint64_t height = m_wallet.blockChainHeight();
std::optional<uint8_t> hf_version = m_wallet.hardForkVersion();
if (hf_version)
{

View File

@ -1085,19 +1085,16 @@ std::vector<std::pair<std::string, uint64_t>>* WalletImpl::listCurrentStakes() c
return stakes;
}
uint64_t WalletImpl::blockChainHeight(LockedWallet& w) {
EXPORT
uint64_t WalletImpl::blockChainHeight() const
{
// This call is thread-safe
auto& w = m_wallet_ptr;
if(w->light_wallet()) {
return w->get_light_wallet_scanned_block_height();
}
return w->get_blockchain_current_height();
}
EXPORT
uint64_t WalletImpl::blockChainHeight() const
{
auto w = wallet();
return blockChainHeight(w);
}
EXPORT
uint64_t WalletImpl::approximateBlockChainHeight() const
{
@ -1113,7 +1110,10 @@ uint64_t WalletImpl::estimateBlockChainHeight() const
EXPORT
uint64_t WalletImpl::daemonBlockChainHeight() const
{
auto w = wallet();
// I *think* the calls here are thread-safe, so we can do this without locking
//auto w = wallet();
auto& w = m_wallet_ptr;
if(w->light_wallet()) {
return w->get_light_wallet_scanned_block_height();
}
@ -1134,7 +1134,10 @@ uint64_t WalletImpl::daemonBlockChainHeight() const
EXPORT
uint64_t WalletImpl::daemonBlockChainTargetHeight() const
{
auto w = wallet();
// As above
//auto w = wallet();
auto& w = m_wallet_ptr;
if(w->light_wallet()) {
return w->get_light_wallet_blockchain_height();
}
@ -1188,6 +1191,12 @@ void WalletImpl::refreshAsync()
m_refreshCV.notify_one();
}
EXPORT
bool WalletImpl::isRefreshing(std::chrono::milliseconds max_wait) {
std::unique_lock lock{m_refreshMutex2, std::defer_lock};
return !lock.try_lock_for(max_wait);
}
EXPORT
bool WalletImpl::rescanBlockchain()
{

View File

@ -49,13 +49,23 @@ class SubaddressImpl;
class SubaddressAccountImpl;
struct Wallet2CallbackImpl;
// Wrapper that holds a lock to prevent background refreshes, which kill things; provides ->
// Wrapper that holds a lock to prevent background refreshes, which kill things; provides `->`
// indirection into the tools::wallet2 instance.
struct LockedWallet {
std::unique_lock<std::mutex> refresh_lock;
std::unique_lock<std::recursive_timed_mutex> refresh_lock;
tools::wallet2* const wallet;
LockedWallet(const std::unique_ptr<tools::wallet2>& w, std::mutex& refresh_mutex)
// Constructs a wallet wrapper from a moved existing unique_lock which may be initially locked
// or unlocked (if unlocked, it will be immediately locked).
LockedWallet(const std::unique_ptr<tools::wallet2>& w, std::unique_lock<std::recursive_timed_mutex>&& lock)
: refresh_lock{std::move(lock)}, wallet{w.get()} {
if (!refresh_lock) refresh_lock.lock();
}
// Constructs a wallet wrapper from a wallet and the refresh mutex; locks the mutex immediately.
LockedWallet(const std::unique_ptr<tools::wallet2>& w, std::recursive_timed_mutex& refresh_mutex)
: refresh_lock{refresh_mutex}, wallet{w.get()} {}
// Returns the wallet2 pointer, to allow `w->whatever()` to call into wallet functions through
// the locking wrapper.
tools::wallet2* operator->() { return wallet; }
};
@ -109,15 +119,17 @@ public:
uint64_t balance(uint32_t accountIndex = 0) const override;
uint64_t unlockedBalance(uint32_t accountIndex = 0) const override;
std::vector<std::pair<std::string, uint64_t>>* listCurrentStakes() const override;
static uint64_t blockChainHeight(LockedWallet& w);
uint64_t blockChainHeight() const override;
uint64_t approximateBlockChainHeight() const override;
uint64_t estimateBlockChainHeight() const override;
// Returns the current daemon height, either from the wallet's current cached value or (if the
// cache is too old) via a request to the daemon.
uint64_t daemonBlockChainHeight() const override;
uint64_t daemonBlockChainTargetHeight() const override;
bool synchronized() const override;
bool refresh() override;
void refreshAsync() override;
bool isRefreshing(std::chrono::milliseconds max_wait = std::chrono::milliseconds{50}) override;
bool rescanBlockchain() override;
void rescanBlockchainAsync() override;
void setAutoRefreshInterval(int millis) override;
@ -259,7 +271,7 @@ private:
std::mutex m_refreshMutex;
// synchronizing sync and async refresh
mutable std::mutex m_refreshMutex2;
mutable std::recursive_timed_mutex m_refreshMutex2;
std::condition_variable m_refreshCV;
std::thread m_refreshThread;
std::thread m_longPollThread;

View File

@ -30,6 +30,7 @@
#pragma once
#include <chrono>
#include <string>
#include <string_view>
#include <vector>
@ -614,8 +615,8 @@ struct Wallet
virtual bool watchOnly() const = 0;
/**
* @brief blockChainHeight - returns current blockchain height
* @return
* @brief blockChainHeight - returns current blockchain height. This is thread-safe and will
* not block if called from different threads.
*/
virtual uint64_t blockChainHeight() const = 0;
@ -632,7 +633,7 @@ struct Wallet
**/
virtual uint64_t estimateBlockChainHeight() const = 0;
/**
* @brief daemonBlockChainHeight - returns daemon blockchain height
* @brief daemonBlockChainHeight - returns daemon blockchain height; thread-safe.
* @return 0 - in case error communicating with the daemon.
* status() will return Status_Error and a return verbose error description
*/
@ -690,6 +691,21 @@ struct Wallet
*/
virtual void refreshAsync() = 0;
/**
* @brief refreshing - returns true if a refresh is currently underway; this is done *without*
* requiring a lock, unlike most other wallet-interacting functions.
*
* @param max_wait - the maximum time to try to obtain the refresh thread lock. If this time
* expires without acquiring a lock, we return true, otherwise we return false. Defaults to
* 50ms; can be set to 0ms to always return immediately.
*
* @return - true if the refresh thread is currently active, false otherwise. If true, very few
* other methods here will work (i.e. they will block until the refresh finishes). The most
* notably non-blocking, thread-safe methods that can be used when this returns true are
* blockChainHeight and daemonBlockChainHeight.
*/
virtual bool isRefreshing(std::chrono::milliseconds max_wait = std::chrono::milliseconds{50}) = 0;
/**
* @brief rescanBlockchain - rescans the wallet, updating transactions from daemon
* @return - true if refreshed successfully;

View File

@ -2645,6 +2645,7 @@ void wallet2::process_new_blockchain_entry(const cryptonote::block& b, const cry
LOG_PRINT_L2( "Skipped block by timestamp, height: " << height << ", block time " << b.timestamp << ", account time " << m_account.get_createtime());
}
m_blockchain.push_back(bl_id);
m_cached_height++;
if (0 != m_callback)
m_callback->on_new_block(height, b);
@ -3311,6 +3312,7 @@ void wallet2::fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height,
m_blockchain.push_back(crypto::null_hash); // maybe a bit suboptimal, but deque won't do huge reallocs like vector
m_blockchain.push_back(checkpoint_hash);
m_blockchain.trim(checkpoint_height);
m_cached_height = m_blockchain.size();
short_chain_history.clear();
get_short_chain_history(short_chain_history);
}
@ -3343,6 +3345,7 @@ void wallet2::fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height,
if (!(current_index % 1024))
LOG_PRINT_L2( "Skipped block by height: " << current_index);
m_blockchain.push_back(bl_id);
m_cached_height++;
if (0 != m_callback)
{ // FIXME: this isn't right, but simplewallet just logs that we got a block.
@ -3550,6 +3553,7 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
generate_genesis(b);
m_blockchain.clear();
m_blockchain.push_back(get_block_hash(b));
m_cached_height++;
short_chain_history.clear();
get_short_chain_history(short_chain_history);
fast_refresh(stop_height, blocks_start_height, short_chain_history, true);
@ -3557,6 +3561,7 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
THROW_WALLET_EXCEPTION_IF(m_blockchain.offset() != 0, error::wallet_internal_error, "Unexpected hashchain offset");
for (const auto &h: tip)
m_blockchain.push_back(h);
m_cached_height = m_blockchain.size();
short_chain_history.clear();
get_short_chain_history(short_chain_history);
start_height = stop_height;
@ -3794,6 +3799,7 @@ void wallet2::detach_blockchain(uint64_t height, std::map<std::pair<uint64_t, ui
size_t blocks_detached = m_blockchain.size() - height;
m_blockchain.crop(height);
m_cached_height = m_blockchain.size();
for (auto it = m_payments.begin(); it != m_payments.end(); )
{
@ -3825,6 +3831,7 @@ bool wallet2::deinit()
bool wallet2::clear()
{
m_blockchain.clear();
m_cached_height = m_blockchain.size();
m_transfers.clear();
m_key_images.clear();
m_pub_keys.clear();
@ -3861,6 +3868,7 @@ void wallet2::clear_soft(bool keep_key_images)
cryptonote::block b;
generate_genesis(b);
m_blockchain.push_back(get_block_hash(b));
m_cached_height = m_blockchain.size();
m_last_block_reward = cryptonote::get_outs_money_amount(b.miner_tx);
}
@ -4607,6 +4615,7 @@ void wallet2::setup_new_blockchain()
cryptonote::block b;
generate_genesis(b);
m_blockchain.push_back(get_block_hash(b));
m_cached_height = m_blockchain.size();
m_last_block_reward = cryptonote::get_outs_money_amount(b.miner_tx);
add_subaddress_account(tr("Primary account"));
}
@ -5792,6 +5801,7 @@ void wallet2::load(const fs::path& wallet_, const epee::wipeable_string& passwor
{
m_blockchain.push_back(genesis_hash);
m_last_block_reward = cryptonote::get_outs_money_amount(genesis.miner_tx);
m_cached_height = m_blockchain.size();
}
else
{
@ -5856,6 +5866,7 @@ void wallet2::trim_hashchain()
MDEBUG("trimming to " << height << ", offset " << m_blockchain.offset());
m_blockchain.trim(height);
}
m_cached_height = m_blockchain.size();
}
//----------------------------------------------------------------------------------------------------
void wallet2::check_genesis(const crypto::hash& genesis_hash) const {
@ -13771,36 +13782,36 @@ void wallet2::import_payments_out(const std::list<std::pair<crypto::hash,wallet2
}
}
std::tuple<size_t,crypto::hash,std::vector<crypto::hash>> wallet2::export_blockchain() const
std::tuple<size_t, crypto::hash, std::vector<crypto::hash>> wallet2::export_blockchain() const
{
std::tuple<size_t, crypto::hash, std::vector<crypto::hash>> bc;
std::get<0>(bc) = m_blockchain.offset();
std::get<1>(bc) = m_blockchain.empty() ? crypto::null_hash: m_blockchain.genesis();
auto& [offset, genesis_hash, hashes] = bc;
offset = m_blockchain.offset();
genesis_hash = m_blockchain.empty() ? crypto::null_hash: m_blockchain.genesis();
for (size_t n = m_blockchain.offset(); n < m_blockchain.size(); ++n)
{
std::get<2>(bc).push_back(m_blockchain[n]);
}
hashes.push_back(m_blockchain[n]);
return bc;
}
void wallet2::import_blockchain(const std::tuple<size_t, crypto::hash, std::vector<crypto::hash>> &bc)
{
const auto& [offset, genesis_h, hashes] = bc;
m_blockchain.clear();
if (std::get<0>(bc))
if (offset)
{
for (size_t n = std::get<0>(bc); n > 0; --n)
m_blockchain.push_back(std::get<1>(bc));
m_blockchain.trim(std::get<0>(bc));
for (size_t n = offset; n > 0; --n)
m_blockchain.push_back(genesis_h);
m_blockchain.trim(offset);
}
for (auto const &b : std::get<2>(bc))
{
for (auto const &b : hashes)
m_blockchain.push_back(b);
}
cryptonote::block genesis;
generate_genesis(genesis);
crypto::hash genesis_hash = get_block_hash(genesis);
check_genesis(genesis_hash);
m_last_block_reward = cryptonote::get_outs_money_amount(genesis.miner_tx);
m_cached_height = m_blockchain.size();
}
//----------------------------------------------------------------------------------------------------
std::pair<size_t, std::vector<tools::wallet2::transfer_details>> wallet2::export_outputs(bool all) const

View File

@ -835,7 +835,9 @@ private:
std::unordered_map<std::string, ons_detail> get_ons_cache();
uint64_t get_blockchain_current_height() const { return m_light_wallet_blockchain_height ? m_light_wallet_blockchain_height : m_blockchain.size(); }
// Returns the current height up to which the wallet has synchronized the blockchain. Thread
// safe (though the value may be behind if another thread is in the middle of adding blocks).
uint64_t get_blockchain_current_height() const { return m_cached_height; }
void rescan_spent();
void rescan_blockchain(bool hard, bool refresh = true, bool keep_key_images = false);
bool is_transfer_unlocked(const transfer_details &td) const;
@ -864,6 +866,7 @@ private:
{
a & m_blockchain;
}
m_cached_height = m_blockchain.size();
a & m_transfers;
a & m_account_public_address;
a & m_key_images;
@ -1575,6 +1578,7 @@ private:
fs::path m_keys_file;
fs::path m_mms_file;
hashchain m_blockchain;
std::atomic<uint64_t> m_cached_height; // Tracks m_blockchain.size(), but thread-safe.
std::unordered_map<crypto::hash, unconfirmed_transfer_details> m_unconfirmed_txs;
std::unordered_map<crypto::hash, confirmed_transfer_details> m_confirmed_txs;
std::unordered_multimap<crypto::hash, pool_payment_details> m_unconfirmed_payments;