Skip to content

Commit 10c4a46

Browse files
committed
Merge bitcoin#27469: wallet: improve IBD sync time by skipping block scanning prior birth time
82bb783 wallet: skip block scan if block was created before wallet birthday (furszy) a082434 refactor: single method to append new spkm to the wallet (furszy) Pull request description: During initial block download, the node's wallet(s) scans every arriving block looking for data that it owns. This process can be resource-intensive, as it involves sequentially scanning all transactions within each arriving block. 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). So, by skipping not relevant blocks in the wallet's IBD scanning process, we will also improve the chain synchronization time. ACKs for top commit: ishaanam: re-ACK 82bb783 achow101: re-ACK 82bb783 pinheadmz: ACK 82bb783 Tree-SHA512: 70158c9657f1fcc396badad2c4410b7b7f439466142640b31a9b1a8cea4555e45ea254e48043c9b27f783d5e4d24d91855f0d79d42f0484b8aa83cdbf3d6c50b
2 parents 8b59231 + 82bb783 commit 10c4a46

File tree

7 files changed

+68
-12
lines changed

7 files changed

+68
-12
lines changed

src/bench/wallet_balance.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include <bench/bench.h>
66
#include <interfaces/chain.h>
7+
#include <node/chainstate.h>
78
#include <node/context.h>
89
#include <test/util/mining.h>
910
#include <test/util/setup_common.h>
@@ -28,6 +29,9 @@ static void WalletBalance(benchmark::Bench& bench, const bool set_dirty, const b
2829

2930
const auto& ADDRESS_WATCHONLY = ADDRESS_BCRT1_UNSPENDABLE;
3031

32+
// Set clock to genesis block, so the descriptors/keys creation time don't interfere with the blocks scanning process.
33+
// The reason is 'generatetoaddress', which creates a chain with deterministic timestamps in the past.
34+
SetMockTime(test_setup->m_node.chainman->GetParams().GenesisBlock().nTime);
3135
CWallet wallet{test_setup->m_node.chain.get(), "", CreateMockableWalletDatabase()};
3236
{
3337
LOCK(wallet.cs_wallet);

src/interfaces/chain.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ struct BlockInfo {
8787
unsigned data_pos = 0;
8888
const CBlock* data = nullptr;
8989
const CBlockUndo* undo_data = nullptr;
90+
// The maximum time in the chain up to and including this block.
91+
// A timestamp that can only move forward.
92+
unsigned int chain_time_max{0};
9093

9194
BlockInfo(const uint256& hash LIFETIMEBOUND) : hash(hash) {}
9295
};

src/kernel/chain.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ interfaces::BlockInfo MakeBlockInfo(const CBlockIndex* index, const CBlock* data
1616
if (index) {
1717
info.prev_hash = index->pprev ? index->pprev->phashBlock : nullptr;
1818
info.height = index->nHeight;
19+
info.chain_time_max = index->GetBlockTimeMax();
1920
LOCK(::cs_main);
2021
info.file_number = index->nFile;
2122
info.data_pos = index->nDataPos;

src/wallet/scriptpubkeyman.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,8 @@ void LegacyScriptPubKeyMan::UpdateTimeFirstKey(int64_t nCreateTime)
710710
} else if (!nTimeFirstKey || nCreateTime < nTimeFirstKey) {
711711
nTimeFirstKey = nCreateTime;
712712
}
713+
714+
NotifyFirstKeyTimeChanged(this, nTimeFirstKey);
713715
}
714716

715717
bool LegacyScriptPubKeyMan::LoadKey(const CKey& key, const CPubKey &pubkey)
@@ -2736,6 +2738,8 @@ void DescriptorScriptPubKeyMan::UpdateWalletDescriptor(WalletDescriptor& descrip
27362738
m_map_script_pub_keys.clear();
27372739
m_max_cached_index = -1;
27382740
m_wallet_descriptor = descriptor;
2741+
2742+
NotifyFirstKeyTimeChanged(this, m_wallet_descriptor.creation_time);
27392743
}
27402744

27412745
bool DescriptorScriptPubKeyMan::CanUpdateToWalletDescriptor(const WalletDescriptor& descriptor, std::string& error)

src/wallet/scriptpubkeyman.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,9 @@ class ScriptPubKeyMan
256256

257257
/** Keypool has new keys */
258258
boost::signals2::signal<void ()> NotifyCanGetAddressesChanged;
259+
260+
/** Birth time changed */
261+
boost::signals2::signal<void (const ScriptPubKeyMan* spkm, int64_t new_birth_time)> NotifyFirstKeyTimeChanged;
259262
};
260263

261264
/** OutputTypes supported by the LegacyScriptPubKeyMan */

src/wallet/wallet.cpp

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1436,6 +1436,12 @@ void CWallet::blockConnected(const interfaces::BlockInfo& block)
14361436

14371437
m_last_block_processed_height = block.height;
14381438
m_last_block_processed = block.hash;
1439+
1440+
// No need to scan block if it was created before the wallet birthday.
1441+
// Uses chain max time and twice the grace period to adjust time for block time variability.
1442+
if (block.chain_time_max < m_birth_time.load() - (TIMESTAMP_WINDOW * 2)) return;
1443+
1444+
// Scan block
14391445
for (size_t index = 0; index < block.data->vtx.size(); index++) {
14401446
SyncTransaction(block.data->vtx[index], TxStateConfirmed{block.hash, block.height, static_cast<int>(index)});
14411447
transactionRemovedFromMempool(block.data->vtx[index], MemPoolRemovalReason::BLOCK);
@@ -1779,6 +1785,14 @@ bool CWallet::ImportScriptPubKeys(const std::string& label, const std::set<CScri
17791785
return true;
17801786
}
17811787

1788+
void CWallet::FirstKeyTimeChanged(const ScriptPubKeyMan* spkm, int64_t new_birth_time)
1789+
{
1790+
int64_t birthtime = m_birth_time.load();
1791+
if (new_birth_time < birthtime) {
1792+
m_birth_time = new_birth_time;
1793+
}
1794+
}
1795+
17821796
/**
17831797
* Scan active chain for relevant transactions after importing keys. This should
17841798
* 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
31073121
// Try to top up keypool. No-op if the wallet is locked.
31083122
walletInstance->TopUpKeyPool();
31093123

3124+
// Cache the first key time
3125+
std::optional<int64_t> time_first_key;
3126+
for (auto spk_man : walletInstance->GetAllScriptPubKeyMans()) {
3127+
int64_t time = spk_man->GetTimeFirstKey();
3128+
if (!time_first_key || time < *time_first_key) time_first_key = time;
3129+
}
3130+
if (time_first_key) walletInstance->m_birth_time = *time_first_key;
3131+
31103132
if (chain && !AttachChain(walletInstance, *chain, rescan_required, error, warnings)) {
31113133
return nullptr;
31123134
}
@@ -3182,11 +3204,7 @@ bool CWallet::AttachChain(const std::shared_ptr<CWallet>& walletInstance, interf
31823204
{
31833205
// No need to read and scan block if block was created before
31843206
// our wallet birthday (as adjusted for block time variability)
3185-
std::optional<int64_t> time_first_key;
3186-
for (auto spk_man : walletInstance->GetAllScriptPubKeyMans()) {
3187-
int64_t time = spk_man->GetTimeFirstKey();
3188-
if (!time_first_key || time < *time_first_key) time_first_key = time;
3189-
}
3207+
std::optional<int64_t> time_first_key = walletInstance->m_birth_time.load();
31903208
if (time_first_key) {
31913209
FoundBlock found = FoundBlock().height(rescan_height);
31923210
chain.findFirstBlockWithTimeAndHeight(*time_first_key - TIMESTAMP_WINDOW, rescan_height, found);
@@ -3498,6 +3516,14 @@ LegacyScriptPubKeyMan* CWallet::GetOrCreateLegacyScriptPubKeyMan()
34983516
return GetLegacyScriptPubKeyMan();
34993517
}
35003518

3519+
void CWallet::AddScriptPubKeyMan(const uint256& id, std::unique_ptr<ScriptPubKeyMan> spkm_man)
3520+
{
3521+
const auto& spkm = m_spk_managers[id] = std::move(spkm_man);
3522+
3523+
// Update birth time if needed
3524+
FirstKeyTimeChanged(spkm.get(), spkm->GetTimeFirstKey());
3525+
}
3526+
35013527
void CWallet::SetupLegacyScriptPubKeyMan()
35023528
{
35033529
if (!m_internal_spk_managers.empty() || !m_external_spk_managers.empty() || !m_spk_managers.empty() || IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
@@ -3509,7 +3535,8 @@ void CWallet::SetupLegacyScriptPubKeyMan()
35093535
m_internal_spk_managers[type] = spk_manager.get();
35103536
m_external_spk_managers[type] = spk_manager.get();
35113537
}
3512-
m_spk_managers[spk_manager->GetID()] = std::move(spk_manager);
3538+
uint256 id = spk_manager->GetID();
3539+
AddScriptPubKeyMan(id, std::move(spk_manager));
35133540
}
35143541

35153542
const CKeyingMaterial& CWallet::GetEncryptionKey() const
@@ -3527,17 +3554,18 @@ void CWallet::ConnectScriptPubKeyManNotifiers()
35273554
for (const auto& spk_man : GetActiveScriptPubKeyMans()) {
35283555
spk_man->NotifyWatchonlyChanged.connect(NotifyWatchonlyChanged);
35293556
spk_man->NotifyCanGetAddressesChanged.connect(NotifyCanGetAddressesChanged);
3557+
spk_man->NotifyFirstKeyTimeChanged.connect(std::bind(&CWallet::FirstKeyTimeChanged, this, std::placeholders::_1, std::placeholders::_2));
35303558
}
35313559
}
35323560

35333561
void CWallet::LoadDescriptorScriptPubKeyMan(uint256 id, WalletDescriptor& desc)
35343562
{
35353563
if (IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER)) {
35363564
auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new ExternalSignerScriptPubKeyMan(*this, desc, m_keypool_size));
3537-
m_spk_managers[id] = std::move(spk_manager);
3565+
AddScriptPubKeyMan(id, std::move(spk_manager));
35383566
} else {
35393567
auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc, m_keypool_size));
3540-
m_spk_managers[id] = std::move(spk_manager);
3568+
AddScriptPubKeyMan(id, std::move(spk_manager));
35413569
}
35423570
}
35433571

@@ -3558,7 +3586,7 @@ void CWallet::SetupDescriptorScriptPubKeyMans(const CExtKey& master_key)
35583586
}
35593587
spk_manager->SetupDescriptorGeneration(master_key, t, internal);
35603588
uint256 id = spk_manager->GetID();
3561-
m_spk_managers[id] = std::move(spk_manager);
3589+
AddScriptPubKeyMan(id, std::move(spk_manager));
35623590
AddActiveScriptPubKeyMan(id, t, internal);
35633591
}
35643592
}
@@ -3606,7 +3634,7 @@ void CWallet::SetupDescriptorScriptPubKeyMans()
36063634
auto spk_manager = std::unique_ptr<ExternalSignerScriptPubKeyMan>(new ExternalSignerScriptPubKeyMan(*this, m_keypool_size));
36073635
spk_manager->SetupDescriptor(std::move(desc));
36083636
uint256 id = spk_manager->GetID();
3609-
m_spk_managers[id] = std::move(spk_manager);
3637+
AddScriptPubKeyMan(id, std::move(spk_manager));
36103638
AddActiveScriptPubKeyMan(id, t, internal);
36113639
}
36123640
}
@@ -3723,7 +3751,8 @@ ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const Flat
37233751
spk_man = new_spk_man.get();
37243752

37253753
// Save the descriptor to memory
3726-
m_spk_managers[new_spk_man->GetID()] = std::move(new_spk_man);
3754+
uint256 id = new_spk_man->GetID();
3755+
AddScriptPubKeyMan(id, std::move(new_spk_man));
37273756
}
37283757

37293758
// Add the private keys to the descriptor
@@ -3866,7 +3895,8 @@ bool CWallet::ApplyMigrationData(MigrationData& data, bilingual_str& error)
38663895
error = _("Error: Duplicate descriptors created during migration. Your wallet may be corrupted.");
38673896
return false;
38683897
}
3869-
m_spk_managers[desc_spkm->GetID()] = std::move(desc_spkm);
3898+
uint256 id = desc_spkm->GetID();
3899+
AddScriptPubKeyMan(id, std::move(desc_spkm));
38703900
}
38713901

38723902
// Remove the LegacyScriptPubKeyMan from disk

src/wallet/wallet.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,10 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
299299
// Local time that the tip block was received. Used to schedule wallet rebroadcasts.
300300
std::atomic<int64_t> m_best_block_time {0};
301301

302+
// First created key time. Used to skip blocks prior to this time.
303+
// 'std::numeric_limits<int64_t>::max()' if wallet is blank.
304+
std::atomic<int64_t> m_birth_time{std::numeric_limits<int64_t>::max()};
305+
302306
/**
303307
* Used to keep track of spent outpoints, and
304308
* detect and report conflicts (double-spends or
@@ -380,6 +384,10 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
380384
// ScriptPubKeyMan::GetID. In many cases it will be the hash of an internal structure
381385
std::map<uint256, std::unique_ptr<ScriptPubKeyMan>> m_spk_managers;
382386

387+
// Appends spk managers into the main 'm_spk_managers'.
388+
// Must be the only method adding data to it.
389+
void AddScriptPubKeyMan(const uint256& id, std::unique_ptr<ScriptPubKeyMan> spkm_man);
390+
383391
/**
384392
* Catch wallet up to current chain, scanning new blocks, updating the best
385393
* block locator and m_last_block_processed, and registering for
@@ -641,6 +649,9 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
641649
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);
642650
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);
643651

652+
/** Updates wallet birth time if 'new_birth_time' is below it */
653+
void FirstKeyTimeChanged(const ScriptPubKeyMan* spkm, int64_t new_birth_time);
654+
644655
CFeeRate m_pay_tx_fee{DEFAULT_PAY_TX_FEE};
645656
unsigned int m_confirm_target{DEFAULT_TX_CONFIRM_TARGET};
646657
/** Allow Coin Selection to pick unconfirmed UTXOs that were sent from our own wallet if it

0 commit comments

Comments
 (0)