aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/interfaces/chain.h8
-rw-r--r--src/logging.cpp3
-rw-r--r--src/logging.h1
-rw-r--r--src/node/interfaces.cpp16
-rw-r--r--src/wallet/rpc/backup.cpp7
-rw-r--r--src/wallet/rpc/transactions.cpp4
-rw-r--r--src/wallet/scriptpubkeyman.cpp14
-rw-r--r--src/wallet/scriptpubkeyman.h2
-rw-r--r--src/wallet/wallet.cpp140
9 files changed, 161 insertions, 34 deletions
diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h
index 5fc0e540a9..7a3d88b18f 100644
--- a/src/interfaces/chain.h
+++ b/src/interfaces/chain.h
@@ -5,6 +5,7 @@
#ifndef BITCOIN_INTERFACES_CHAIN_H
#define BITCOIN_INTERFACES_CHAIN_H
+#include <blockfilter.h>
#include <primitives/transaction.h> // For CTransactionRef
#include <util/settings.h> // For util::SettingsValue
@@ -143,6 +144,13 @@ public:
//! or one of its ancestors.
virtual std::optional<int> findLocatorFork(const CBlockLocator& locator) = 0;
+ //! Returns whether a block filter index is available.
+ virtual bool hasBlockFilterIndex(BlockFilterType filter_type) = 0;
+
+ //! Returns whether any of the elements match the block via a BIP 157 block filter
+ //! or std::nullopt if the block filter for this block couldn't be found.
+ virtual std::optional<bool> blockFilterMatchesAny(BlockFilterType filter_type, const uint256& block_hash, const GCSFilter::ElementSet& filter_set) = 0;
+
//! Return whether node has the block and optionally return block metadata
//! or contents.
virtual bool findBlock(const uint256& hash, const FoundBlock& block={}) = 0;
diff --git a/src/logging.cpp b/src/logging.cpp
index c95c0b7e37..ed0c2a56a5 100644
--- a/src/logging.cpp
+++ b/src/logging.cpp
@@ -181,6 +181,7 @@ const CLogCategoryDesc LogCategories[] =
{BCLog::UTIL, "util"},
{BCLog::BLOCKSTORE, "blockstorage"},
{BCLog::TXRECONCILIATION, "txreconciliation"},
+ {BCLog::SCAN, "scan"},
{BCLog::ALL, "1"},
{BCLog::ALL, "all"},
};
@@ -283,6 +284,8 @@ std::string LogCategoryToStr(BCLog::LogFlags category)
return "blockstorage";
case BCLog::LogFlags::TXRECONCILIATION:
return "txreconciliation";
+ case BCLog::LogFlags::SCAN:
+ return "scan";
case BCLog::LogFlags::ALL:
return "all";
}
diff --git a/src/logging.h b/src/logging.h
index 5ee6665c76..14a0f08f8d 100644
--- a/src/logging.h
+++ b/src/logging.h
@@ -67,6 +67,7 @@ namespace BCLog {
UTIL = (1 << 25),
BLOCKSTORE = (1 << 26),
TXRECONCILIATION = (1 << 27),
+ SCAN = (1 << 28),
ALL = ~(uint32_t)0,
};
enum class Level {
diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp
index 8a0011a629..979c625463 100644
--- a/src/node/interfaces.cpp
+++ b/src/node/interfaces.cpp
@@ -4,10 +4,12 @@
#include <addrdb.h>
#include <banman.h>
+#include <blockfilter.h>
#include <chain.h>
#include <chainparams.h>
#include <deploymentstatus.h>
#include <external_signer.h>
+#include <index/blockfilterindex.h>
#include <init.h>
#include <interfaces/chain.h>
#include <interfaces/handler.h>
@@ -536,6 +538,20 @@ public:
}
return std::nullopt;
}
+ bool hasBlockFilterIndex(BlockFilterType filter_type) override
+ {
+ return GetBlockFilterIndex(filter_type) != nullptr;
+ }
+ std::optional<bool> blockFilterMatchesAny(BlockFilterType filter_type, const uint256& block_hash, const GCSFilter::ElementSet& filter_set) override
+ {
+ const BlockFilterIndex* block_filter_index{GetBlockFilterIndex(filter_type)};
+ if (!block_filter_index) return std::nullopt;
+
+ BlockFilter filter;
+ const CBlockIndex* index{WITH_LOCK(::cs_main, return chainman().m_blockman.LookupBlockIndex(block_hash))};
+ if (index == nullptr || !block_filter_index->LookupFilter(index, filter)) return std::nullopt;
+ return filter.GetFilter().MatchAny(filter_set);
+ }
bool findBlock(const uint256& hash, const FoundBlock& block) override
{
WAIT_LOCK(cs_main, lock);
diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp
index 1206a428fc..1d2d7d2a10 100644
--- a/src/wallet/rpc/backup.cpp
+++ b/src/wallet/rpc/backup.cpp
@@ -1589,7 +1589,8 @@ RPCHelpMan importdescriptors()
return RPCHelpMan{"importdescriptors",
"\nImport descriptors. This will trigger a rescan of the blockchain based on the earliest timestamp of all descriptors being imported. Requires a new wallet backup.\n"
"\nNote: This call can take over an hour to complete if using an early timestamp; during that time, other rpc calls\n"
- "may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n",
+ "may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n"
+ "The rescan is significantly faster if block filters are available (using startup option \"-blockfilterindex=1\").\n",
{
{"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported",
{
@@ -1887,7 +1888,9 @@ RPCHelpMan restorewallet()
{
return RPCHelpMan{
"restorewallet",
- "\nRestore and loads a wallet from backup.\n",
+ "\nRestore and loads a wallet from backup.\n"
+ "\nThe rescan is significantly faster if a descriptor wallet is restored"
+ "\nand block filters are available (using startup option \"-blockfilterindex=1\").\n",
{
{"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name that will be applied to the restored wallet"},
{"backup_file", RPCArg::Type::STR, RPCArg::Optional::NO, "The backup file that will be used to restore the wallet."},
diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp
index 0e13e4756b..0fe4ed703d 100644
--- a/src/wallet/rpc/transactions.cpp
+++ b/src/wallet/rpc/transactions.cpp
@@ -839,7 +839,9 @@ RPCHelpMan rescanblockchain()
{
return RPCHelpMan{"rescanblockchain",
"\nRescan the local blockchain for wallet related transactions.\n"
- "Note: Use \"getwalletinfo\" to query the scanning progress.\n",
+ "Note: Use \"getwalletinfo\" to query the scanning progress.\n"
+ "The rescan is significantly faster when used on a descriptor wallet\n"
+ "and block filters are available (using startup option \"-blockfilterindex=1\").\n",
{
{"start_height", RPCArg::Type::NUM, RPCArg::Default{0}, "block height where the rescan should start"},
{"stop_height", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "the last block height that should be scanned. If none is provided it will rescan up to the tip at return time of this call."},
diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp
index 39afb79600..4c534d64ec 100644
--- a/src/wallet/scriptpubkeyman.cpp
+++ b/src/wallet/scriptpubkeyman.cpp
@@ -2645,16 +2645,26 @@ const WalletDescriptor DescriptorScriptPubKeyMan::GetWalletDescriptor() const
const std::unordered_set<CScript, SaltedSipHasher> DescriptorScriptPubKeyMan::GetScriptPubKeys() const
{
+ return GetScriptPubKeys(0);
+}
+
+const std::unordered_set<CScript, SaltedSipHasher> DescriptorScriptPubKeyMan::GetScriptPubKeys(int32_t minimum_index) const
+{
LOCK(cs_desc_man);
std::unordered_set<CScript, SaltedSipHasher> script_pub_keys;
script_pub_keys.reserve(m_map_script_pub_keys.size());
- for (auto const& script_pub_key: m_map_script_pub_keys) {
- script_pub_keys.insert(script_pub_key.first);
+ for (auto const& [script_pub_key, index] : m_map_script_pub_keys) {
+ if (index >= minimum_index) script_pub_keys.insert(script_pub_key);
}
return script_pub_keys;
}
+int32_t DescriptorScriptPubKeyMan::GetEndRange() const
+{
+ return m_max_cached_index + 1;
+}
+
bool DescriptorScriptPubKeyMan::GetDescriptorString(std::string& out, const bool priv) const
{
LOCK(cs_desc_man);
diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h
index 3ab489c374..eb77015956 100644
--- a/src/wallet/scriptpubkeyman.h
+++ b/src/wallet/scriptpubkeyman.h
@@ -643,6 +643,8 @@ public:
const WalletDescriptor GetWalletDescriptor() const EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man);
const std::unordered_set<CScript, SaltedSipHasher> GetScriptPubKeys() const override;
+ const std::unordered_set<CScript, SaltedSipHasher> GetScriptPubKeys(int32_t minimum_index) const;
+ int32_t GetEndRange() const;
bool GetDescriptorString(std::string& out, const bool priv) const;
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index e2c8f6eda3..431e970edc 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -5,6 +5,7 @@
#include <wallet/wallet.h>
+#include <blockfilter.h>
#include <chain.h>
#include <consensus/amount.h>
#include <consensus/consensus.h>
@@ -261,6 +262,64 @@ std::shared_ptr<CWallet> LoadWalletInternal(WalletContext& context, const std::s
return nullptr;
}
}
+
+class FastWalletRescanFilter
+{
+public:
+ FastWalletRescanFilter(const CWallet& wallet) : m_wallet(wallet)
+ {
+ // fast rescanning via block filters is only supported by descriptor wallets right now
+ assert(!m_wallet.IsLegacy());
+
+ // create initial filter with scripts from all ScriptPubKeyMans
+ for (auto spkm : m_wallet.GetAllScriptPubKeyMans()) {
+ auto desc_spkm{dynamic_cast<DescriptorScriptPubKeyMan*>(spkm)};
+ assert(desc_spkm != nullptr);
+ AddScriptPubKeys(desc_spkm);
+ // save each range descriptor's end for possible future filter updates
+ if (desc_spkm->IsHDEnabled()) {
+ m_last_range_ends.emplace(desc_spkm->GetID(), desc_spkm->GetEndRange());
+ }
+ }
+ }
+
+ void UpdateIfNeeded()
+ {
+ // repopulate filter with new scripts if top-up has happened since last iteration
+ for (const auto& [desc_spkm_id, last_range_end] : m_last_range_ends) {
+ auto desc_spkm{dynamic_cast<DescriptorScriptPubKeyMan*>(m_wallet.GetScriptPubKeyMan(desc_spkm_id))};
+ assert(desc_spkm != nullptr);
+ int32_t current_range_end{desc_spkm->GetEndRange()};
+ if (current_range_end > last_range_end) {
+ AddScriptPubKeys(desc_spkm, last_range_end);
+ m_last_range_ends.at(desc_spkm->GetID()) = current_range_end;
+ }
+ }
+ }
+
+ std::optional<bool> MatchesBlock(const uint256& block_hash) const
+ {
+ return m_wallet.chain().blockFilterMatchesAny(BlockFilterType::BASIC, block_hash, m_filter_set);
+ }
+
+private:
+ const CWallet& m_wallet;
+ /** Map for keeping track of each range descriptor's last seen end range.
+ * This information is used to detect whether new addresses were derived
+ * (that is, if the current end range is larger than the saved end range)
+ * after processing a block and hence a filter set update is needed to
+ * take possible keypool top-ups into account.
+ */
+ std::map<uint256, int32_t> m_last_range_ends;
+ GCSFilter::ElementSet m_filter_set;
+
+ void AddScriptPubKeys(const DescriptorScriptPubKeyMan* desc_spkm, int32_t last_range_end = 0)
+ {
+ for (const auto& script_pub_key : desc_spkm->GetScriptPubKeys(last_range_end)) {
+ m_filter_set.emplace(script_pub_key.begin(), script_pub_key.end());
+ }
+ }
+};
} // namespace
std::shared_ptr<CWallet> LoadWallet(WalletContext& context, const std::string& name, std::optional<bool> load_on_start, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings)
@@ -1755,7 +1814,11 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
uint256 block_hash = start_block;
ScanResult result;
- WalletLogPrintf("Rescan started from block %s...\n", start_block.ToString());
+ std::unique_ptr<FastWalletRescanFilter> fast_rescan_filter;
+ if (!IsLegacy() && chain().hasBlockFilterIndex(BlockFilterType::BASIC)) fast_rescan_filter = std::make_unique<FastWalletRescanFilter>(*this);
+
+ WalletLogPrintf("Rescan started from block %s... (%s)\n", start_block.ToString(),
+ fast_rescan_filter ? "fast variant using block filters" : "slow variant inspecting all blocks");
fAbortRescan = false;
ShowProgress(strprintf("%s " + _("Rescanning…").translated, GetDisplayName()), 0); // show rescan progress in GUI as dialog or on splashscreen, if rescan required on startup (e.g. due to corruption)
@@ -1782,9 +1845,22 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", block_height, progress_current);
}
- // Read block data
- CBlock block;
- chain().findBlock(block_hash, FoundBlock().data(block));
+ bool fetch_block{true};
+ if (fast_rescan_filter) {
+ fast_rescan_filter->UpdateIfNeeded();
+ auto matches_block{fast_rescan_filter->MatchesBlock(block_hash)};
+ if (matches_block.has_value()) {
+ if (*matches_block) {
+ LogPrint(BCLog::SCAN, "Fast rescan: inspect block %d [%s] (filter matched)\n", block_height, block_hash.ToString());
+ } else {
+ result.last_scanned_block = block_hash;
+ result.last_scanned_height = block_height;
+ fetch_block = false;
+ }
+ } else {
+ LogPrint(BCLog::SCAN, "Fast rescan: inspect block %d [%s] (WARNING: block filter not found!)\n", block_height, block_hash.ToString());
+ }
+ }
// Find next block separately from reading data above, because reading
// is slow and there might be a reorg while it is read.
@@ -1793,35 +1869,41 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
uint256 next_block_hash;
chain().findBlock(block_hash, FoundBlock().inActiveChain(block_still_active).nextBlock(FoundBlock().inActiveChain(next_block).hash(next_block_hash)));
- if (!block.IsNull()) {
- LOCK(cs_wallet);
- if (!block_still_active) {
- // Abort scan if current block is no longer active, to prevent
- // marking transactions as coming from the wrong block.
- result.last_failed_block = block_hash;
- result.status = ScanResult::FAILURE;
- break;
- }
- for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) {
- SyncTransaction(block.vtx[posInBlock], TxStateConfirmed{block_hash, block_height, static_cast<int>(posInBlock)}, fUpdate, /*rescanning_old_block=*/true);
- }
- // scan succeeded, record block as most recent successfully scanned
- result.last_scanned_block = block_hash;
- result.last_scanned_height = block_height;
+ if (fetch_block) {
+ // Read block data
+ CBlock block;
+ chain().findBlock(block_hash, FoundBlock().data(block));
+
+ if (!block.IsNull()) {
+ LOCK(cs_wallet);
+ if (!block_still_active) {
+ // Abort scan if current block is no longer active, to prevent
+ // marking transactions as coming from the wrong block.
+ result.last_failed_block = block_hash;
+ result.status = ScanResult::FAILURE;
+ break;
+ }
+ for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) {
+ SyncTransaction(block.vtx[posInBlock], TxStateConfirmed{block_hash, block_height, static_cast<int>(posInBlock)}, fUpdate, /*rescanning_old_block=*/true);
+ }
+ // scan succeeded, record block as most recent successfully scanned
+ result.last_scanned_block = block_hash;
+ result.last_scanned_height = block_height;
- if (save_progress && next_interval) {
- CBlockLocator loc = m_chain->getActiveChainLocator(block_hash);
+ if (save_progress && next_interval) {
+ CBlockLocator loc = m_chain->getActiveChainLocator(block_hash);
- if (!loc.IsNull()) {
- WalletLogPrintf("Saving scan progress %d.\n", block_height);
- WalletBatch batch(GetDatabase());
- batch.WriteBestBlock(loc);
+ if (!loc.IsNull()) {
+ WalletLogPrintf("Saving scan progress %d.\n", block_height);
+ WalletBatch batch(GetDatabase());
+ batch.WriteBestBlock(loc);
+ }
}
+ } else {
+ // could not scan block, keep scanning but record this block as the most recent failure
+ result.last_failed_block = block_hash;
+ result.status = ScanResult::FAILURE;
}
- } else {
- // could not scan block, keep scanning but record this block as the most recent failure
- result.last_failed_block = block_hash;
- result.status = ScanResult::FAILURE;
}
if (max_height && block_height >= *max_height) {
break;