wallet: skip block scan if block was created before wallet birthday

To avoid wasting processing power, we can skip blocks that occurred
before the wallet's creation time,  since these blocks are guaranteed
not to contain any relevant wallet data.

This has direct implications (an speed improvement) on the underlying
blockchain synchronization process as well.

The reason is that the validation interface queue is limited to
10 tasks per time. This means that no more than 10 blocks can be
waiting for the wallet(s) to be processed while we are synchronizing
the chain (activating the best chain to be more precise).
Which can be a bottleneck if blocks arrive and are processed faster
from the network than what they are  processed by the wallet(s).
This commit is contained in:
furszy 2023-04-13 22:14:41 -03:00
parent a082434d12
commit 82bb7831fa
No known key found for this signature in database
GPG key ID: 5DD23CCC686AA623
7 changed files with 50 additions and 6 deletions

View file

@ -4,6 +4,7 @@
#include <bench/bench.h>
#include <interfaces/chain.h>
#include <node/chainstate.h>
#include <node/context.h>
#include <test/util/mining.h>
#include <test/util/setup_common.h>
@ -28,6 +29,9 @@ static void WalletBalance(benchmark::Bench& bench, const bool set_dirty, const b
const auto& ADDRESS_WATCHONLY = ADDRESS_BCRT1_UNSPENDABLE;
// Set clock to genesis block, so the descriptors/keys creation time don't interfere with the blocks scanning process.
// The reason is 'generatetoaddress', which creates a chain with deterministic timestamps in the past.
SetMockTime(test_setup->m_node.chainman->GetParams().GenesisBlock().nTime);
CWallet wallet{test_setup->m_node.chain.get(), "", CreateMockableWalletDatabase()};
{
LOCK(wallet.cs_wallet);

View file

@ -87,6 +87,9 @@ struct BlockInfo {
unsigned data_pos = 0;
const CBlock* data = nullptr;
const CBlockUndo* undo_data = nullptr;
// The maximum time in the chain up to and including this block.
// A timestamp that can only move forward.
unsigned int chain_time_max{0};
BlockInfo(const uint256& hash LIFETIMEBOUND) : hash(hash) {}
};

View file

@ -16,6 +16,7 @@ interfaces::BlockInfo MakeBlockInfo(const CBlockIndex* index, const CBlock* data
if (index) {
info.prev_hash = index->pprev ? index->pprev->phashBlock : nullptr;
info.height = index->nHeight;
info.chain_time_max = index->GetBlockTimeMax();
LOCK(::cs_main);
info.file_number = index->nFile;
info.data_pos = index->nDataPos;

View file

@ -710,6 +710,8 @@ void LegacyScriptPubKeyMan::UpdateTimeFirstKey(int64_t nCreateTime)
} else if (!nTimeFirstKey || nCreateTime < nTimeFirstKey) {
nTimeFirstKey = nCreateTime;
}
NotifyFirstKeyTimeChanged(this, nTimeFirstKey);
}
bool LegacyScriptPubKeyMan::LoadKey(const CKey& key, const CPubKey &pubkey)
@ -2736,6 +2738,8 @@ void DescriptorScriptPubKeyMan::UpdateWalletDescriptor(WalletDescriptor& descrip
m_map_script_pub_keys.clear();
m_max_cached_index = -1;
m_wallet_descriptor = descriptor;
NotifyFirstKeyTimeChanged(this, m_wallet_descriptor.creation_time);
}
bool DescriptorScriptPubKeyMan::CanUpdateToWalletDescriptor(const WalletDescriptor& descriptor, std::string& error)

View file

@ -256,6 +256,9 @@ public:
/** Keypool has new keys */
boost::signals2::signal<void ()> NotifyCanGetAddressesChanged;
/** Birth time changed */
boost::signals2::signal<void (const ScriptPubKeyMan* spkm, int64_t new_birth_time)> NotifyFirstKeyTimeChanged;
};
/** OutputTypes supported by the LegacyScriptPubKeyMan */

View file

@ -1436,6 +1436,12 @@ void CWallet::blockConnected(const interfaces::BlockInfo& block)
m_last_block_processed_height = block.height;
m_last_block_processed = block.hash;
// No need to scan block if it was created before the wallet birthday.
// Uses chain max time and twice the grace period to adjust time for block time variability.
if (block.chain_time_max < m_birth_time.load() - (TIMESTAMP_WINDOW * 2)) return;
// Scan block
for (size_t index = 0; index < block.data->vtx.size(); index++) {
SyncTransaction(block.data->vtx[index], TxStateConfirmed{block.hash, block.height, static_cast<int>(index)});
transactionRemovedFromMempool(block.data->vtx[index], MemPoolRemovalReason::BLOCK);
@ -1779,6 +1785,14 @@ bool CWallet::ImportScriptPubKeys(const std::string& label, const std::set<CScri
return true;
}
void CWallet::FirstKeyTimeChanged(const ScriptPubKeyMan* spkm, int64_t new_birth_time)
{
int64_t birthtime = m_birth_time.load();
if (new_birth_time < birthtime) {
m_birth_time = new_birth_time;
}
}
/**
* Scan active chain for relevant transactions after importing keys. This should
* be called whenever new keys are added to the wallet, with the oldest key
@ -3107,6 +3121,14 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri
// Try to top up keypool. No-op if the wallet is locked.
walletInstance->TopUpKeyPool();
// Cache the first key time
std::optional<int64_t> time_first_key;
for (auto spk_man : walletInstance->GetAllScriptPubKeyMans()) {
int64_t time = spk_man->GetTimeFirstKey();
if (!time_first_key || time < *time_first_key) time_first_key = time;
}
if (time_first_key) walletInstance->m_birth_time = *time_first_key;
if (chain && !AttachChain(walletInstance, *chain, rescan_required, error, warnings)) {
return nullptr;
}
@ -3182,11 +3204,7 @@ bool CWallet::AttachChain(const std::shared_ptr<CWallet>& walletInstance, interf
{
// No need to read and scan block if block was created before
// our wallet birthday (as adjusted for block time variability)
std::optional<int64_t> time_first_key;
for (auto spk_man : walletInstance->GetAllScriptPubKeyMans()) {
int64_t time = spk_man->GetTimeFirstKey();
if (!time_first_key || time < *time_first_key) time_first_key = time;
}
std::optional<int64_t> time_first_key = walletInstance->m_birth_time.load();
if (time_first_key) {
FoundBlock found = FoundBlock().height(rescan_height);
chain.findFirstBlockWithTimeAndHeight(*time_first_key - TIMESTAMP_WINDOW, rescan_height, found);
@ -3500,7 +3518,10 @@ LegacyScriptPubKeyMan* CWallet::GetOrCreateLegacyScriptPubKeyMan()
void CWallet::AddScriptPubKeyMan(const uint256& id, std::unique_ptr<ScriptPubKeyMan> spkm_man)
{
m_spk_managers[id] = std::move(spkm_man);
const auto& spkm = m_spk_managers[id] = std::move(spkm_man);
// Update birth time if needed
FirstKeyTimeChanged(spkm.get(), spkm->GetTimeFirstKey());
}
void CWallet::SetupLegacyScriptPubKeyMan()
@ -3533,6 +3554,7 @@ void CWallet::ConnectScriptPubKeyManNotifiers()
for (const auto& spk_man : GetActiveScriptPubKeyMans()) {
spk_man->NotifyWatchonlyChanged.connect(NotifyWatchonlyChanged);
spk_man->NotifyCanGetAddressesChanged.connect(NotifyCanGetAddressesChanged);
spk_man->NotifyFirstKeyTimeChanged.connect(std::bind(&CWallet::FirstKeyTimeChanged, this, std::placeholders::_1, std::placeholders::_2));
}
}

View file

@ -299,6 +299,10 @@ private:
// Local time that the tip block was received. Used to schedule wallet rebroadcasts.
std::atomic<int64_t> m_best_block_time {0};
// First created key time. Used to skip blocks prior to this time.
// 'std::numeric_limits<int64_t>::max()' if wallet is blank.
std::atomic<int64_t> m_birth_time{std::numeric_limits<int64_t>::max()};
/**
* Used to keep track of spent outpoints, and
* detect and report conflicts (double-spends or
@ -645,6 +649,9 @@ public:
bool ImportPubKeys(const std::vector<CKeyID>& ordered_pubkeys, const std::map<CKeyID, CPubKey>& pubkey_map, const std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>>& key_origins, const bool add_keypool, const bool internal, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool ImportScriptPubKeys(const std::string& label, const std::set<CScript>& script_pub_keys, const bool have_solving_data, const bool apply_label, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/** Updates wallet birth time if 'new_birth_time' is below it */
void FirstKeyTimeChanged(const ScriptPubKeyMan* spkm, int64_t new_birth_time);
CFeeRate m_pay_tx_fee{DEFAULT_PAY_TX_FEE};
unsigned int m_confirm_target{DEFAULT_TX_CONFIRM_TARGET};
/** Allow Coin Selection to pick unconfirmed UTXOs that were sent from our own wallet if it