Skip to content

Commit

Permalink
Merge bitcoin#27469: wallet: improve IBD sync time by skipping block …
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
achow101 committed May 27, 2023
2 parents 8b59231 + 82bb783 commit 10c4a46
Show file tree
Hide file tree
Showing 7 changed files with 68 additions and 12 deletions.
4 changes: 4 additions & 0 deletions src/bench/wallet_balance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions src/interfaces/chain.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) {}
};
Expand Down
1 change: 1 addition & 0 deletions src/kernel/chain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions src/wallet/scriptpubkeyman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions src/wallet/scriptpubkeyman.h
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,9 @@ class ScriptPubKeyMan

/** 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 */
Expand Down
54 changes: 42 additions & 12 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -3498,6 +3516,14 @@ LegacyScriptPubKeyMan* CWallet::GetOrCreateLegacyScriptPubKeyMan()
return GetLegacyScriptPubKeyMan();
}

void CWallet::AddScriptPubKeyMan(const uint256& id, std::unique_ptr<ScriptPubKeyMan> 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()
{
if (!m_internal_spk_managers.empty() || !m_external_spk_managers.empty() || !m_spk_managers.empty() || IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
Expand All @@ -3509,7 +3535,8 @@ void CWallet::SetupLegacyScriptPubKeyMan()
m_internal_spk_managers[type] = spk_manager.get();
m_external_spk_managers[type] = spk_manager.get();
}
m_spk_managers[spk_manager->GetID()] = std::move(spk_manager);
uint256 id = spk_manager->GetID();
AddScriptPubKeyMan(id, std::move(spk_manager));
}

const CKeyingMaterial& CWallet::GetEncryptionKey() const
Expand All @@ -3527,17 +3554,18 @@ 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));
}
}

void CWallet::LoadDescriptorScriptPubKeyMan(uint256 id, WalletDescriptor& desc)
{
if (IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER)) {
auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new ExternalSignerScriptPubKeyMan(*this, desc, m_keypool_size));
m_spk_managers[id] = std::move(spk_manager);
AddScriptPubKeyMan(id, std::move(spk_manager));
} else {
auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new DescriptorScriptPubKeyMan(*this, desc, m_keypool_size));
m_spk_managers[id] = std::move(spk_manager);
AddScriptPubKeyMan(id, std::move(spk_manager));
}
}

Expand All @@ -3558,7 +3586,7 @@ void CWallet::SetupDescriptorScriptPubKeyMans(const CExtKey& master_key)
}
spk_manager->SetupDescriptorGeneration(master_key, t, internal);
uint256 id = spk_manager->GetID();
m_spk_managers[id] = std::move(spk_manager);
AddScriptPubKeyMan(id, std::move(spk_manager));
AddActiveScriptPubKeyMan(id, t, internal);
}
}
Expand Down Expand Up @@ -3606,7 +3634,7 @@ void CWallet::SetupDescriptorScriptPubKeyMans()
auto spk_manager = std::unique_ptr<ExternalSignerScriptPubKeyMan>(new ExternalSignerScriptPubKeyMan(*this, m_keypool_size));
spk_manager->SetupDescriptor(std::move(desc));
uint256 id = spk_manager->GetID();
m_spk_managers[id] = std::move(spk_manager);
AddScriptPubKeyMan(id, std::move(spk_manager));
AddActiveScriptPubKeyMan(id, t, internal);
}
}
Expand Down Expand Up @@ -3723,7 +3751,8 @@ ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const Flat
spk_man = new_spk_man.get();

// Save the descriptor to memory
m_spk_managers[new_spk_man->GetID()] = std::move(new_spk_man);
uint256 id = new_spk_man->GetID();
AddScriptPubKeyMan(id, std::move(new_spk_man));
}

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

// Remove the LegacyScriptPubKeyMan from disk
Expand Down
11 changes: 11 additions & 0 deletions src/wallet/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,10 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
// 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
Expand Down Expand Up @@ -380,6 +384,10 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
// ScriptPubKeyMan::GetID. In many cases it will be the hash of an internal structure
std::map<uint256, std::unique_ptr<ScriptPubKeyMan>> m_spk_managers;

// Appends spk managers into the main 'm_spk_managers'.
// Must be the only method adding data to it.
void AddScriptPubKeyMan(const uint256& id, std::unique_ptr<ScriptPubKeyMan> spkm_man);

/**
* Catch wallet up to current chain, scanning new blocks, updating the best
* block locator and m_last_block_processed, and registering for
Expand Down Expand Up @@ -641,6 +649,9 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
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
Expand Down

0 comments on commit 10c4a46

Please sign in to comment.