Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/interfaces/chain.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -147,6 +148,14 @@ class Chain

//! Return list of MN Collateral from outputs
virtual std::vector<COutPoint> listMNCollaterials(const std::vector<std::pair<const CTransactionRef&, uint32_t>>& outputs) = 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;
Expand Down
3 changes: 3 additions & 0 deletions src/logging.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ const CLogCategoryDesc LogCategories[] =
#endif
{BCLog::BLOCKSTORE, "blockstorage"},
{BCLog::TXRECONCILIATION, "txreconciliation"},
{BCLog::SCAN, "scan"},
{BCLog::ALL, "1"},
{BCLog::ALL, "all"},

Expand Down Expand Up @@ -296,6 +297,8 @@ std::string LogCategoryToStr(BCLog::LogFlags category)
return "blockstorage";
case BCLog::LogFlags::TXRECONCILIATION:
return "txreconciliation";
case BCLog::LogFlags::SCAN:
return "scan";
/* Start Dash */
case BCLog::LogFlags::CHAINLOCKS:
return "chainlocks";
Expand Down
1 change: 1 addition & 0 deletions src/logging.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ namespace BCLog {
#endif
BLOCKSTORE = (1 << 26),
TXRECONCILIATION = (1 << 27),
SCAN = (1 << 28),

//Start Dash
CHAINLOCKS = ((uint64_t)1 << 32),
Expand Down
16 changes: 16 additions & 0 deletions src/node/interfaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include <addrdb.h>
#include <banman.h>
#include <blockfilter.h>
#include <chain.h>
#include <chainlock/chainlock.h>
#include <chainparams.h>
Expand All @@ -18,6 +19,7 @@
#include <governance/governance.h>
#include <governance/object.h>
#include <governance/vote.h>
#include <index/blockfilterindex.h>
#include <init.h>
#include <interfaces/chain.h>
#include <interfaces/coinjoin.h>
Expand Down Expand Up @@ -989,6 +991,20 @@ class ChainImpl : public Chain
}
return listRet;
}
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);
Expand Down
7 changes: 5 additions & 2 deletions src/wallet/rpc/backup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1824,7 +1824,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",
{
Expand Down Expand Up @@ -2121,7 +2122,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."},
Expand Down
36 changes: 19 additions & 17 deletions src/wallet/rpc/transactions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -846,23 +846,25 @@ RPCHelpMan abandontransaction()
RPCHelpMan rescanblockchain()
{
return RPCHelpMan{"rescanblockchain",
"\nRescan the local blockchain for wallet related transactions.\n"
"Note: Use \"getwalletinfo\" to query the scanning progress.\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."},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::NUM, "start_height", "The block height where the rescan started (the requested height or 0)"},
{RPCResult::Type::NUM, "stop_height", "The height of the last rescanned block. May be null in rare cases if there was a reorg and the call didn't scan any blocks because they were already scanned in the background."},
}
},
RPCExamples{
HelpExampleCli("rescanblockchain", "100000 120000")
+ HelpExampleRpc("rescanblockchain", "100000, 120000")
},
"\nRescan the local blockchain for wallet related transactions.\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."},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::NUM, "start_height", "The block height where the rescan started (the requested height or 0)"},
{RPCResult::Type::NUM, "stop_height", "The height of the last rescanned block. May be null in rare cases if there was a reorg and the call didn't scan any blocks because they were already scanned in the background."},
}
},
RPCExamples{
HelpExampleCli("rescanblockchain", "100000 120000")
+ HelpExampleRpc("rescanblockchain", "100000, 120000")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
Expand Down
20 changes: 15 additions & 5 deletions src/wallet/scriptpubkeyman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2482,23 +2482,33 @@ void DescriptorScriptPubKeyMan::WriteDescriptor()
}
}

WalletDescriptor DescriptorScriptPubKeyMan::GetWalletDescriptor() const
const WalletDescriptor DescriptorScriptPubKeyMan::GetWalletDescriptor() const
{
return m_wallet_descriptor;
}

std::vector<CScript> DescriptorScriptPubKeyMan::GetScriptPubKeys() 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::vector<CScript> script_pub_keys;
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.push_back(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);
Expand Down
8 changes: 6 additions & 2 deletions src/wallet/scriptpubkeyman.h
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,8 @@ class ScriptPubKeyMan

virtual uint256 GetID() const { return uint256(); }

virtual const std::unordered_set<CScript, SaltedSipHasher> GetScriptPubKeys() const { return {}; };

/** Prepends the wallet name in logging output to ease debugging in multi-wallet use cases */
template<typename... Params>
void WalletLogPrintf(std::string fmt, Params... parameters) const {
Expand Down Expand Up @@ -623,8 +625,10 @@ class DescriptorScriptPubKeyMan : public ScriptPubKeyMan
void AddDescriptorKey(const CKey& key, const CPubKey &pubkey);
void WriteDescriptor();

WalletDescriptor GetWalletDescriptor() const EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man);
std::vector<CScript> GetScriptPubKeys() const;
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;
bool GetMnemonicString(SecureString& mnemonic_out, SecureString& mnemonic_passphrase_out) const;
Expand Down
142 changes: 112 additions & 30 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include <wallet/wallet.h>

#include <blockfilter.h>
#include <chain.h>
#include <chainparams.h>
#include <consensus/amount.h>
Expand Down Expand Up @@ -280,6 +281,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_FILTER, 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)
Expand Down Expand Up @@ -1766,7 +1825,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_FILTER)) 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");

ShowProgress(strprintf("%s " + _("Rescanning…").translated, GetDisplayName()), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup
uint256 tip_hash = WITH_LOCK(cs_wallet, return GetLastBlockHash());
Expand All @@ -1793,9 +1856,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.
Expand All @@ -1804,35 +1880,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)}, batch, 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)}, batch, 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;
Expand Down Expand Up @@ -4026,7 +4108,7 @@ ScriptPubKeyMan* CWallet::AddWalletDescriptor(WalletDescriptor& desc, const Flat
}

CTxDestination dest;
if (!internal && ExtractDestination(script_pub_keys.at(0), dest)) {
if (!internal && ExtractDestination(*script_pub_keys.begin(), dest)) {
SetAddressBook(dest, label, "receive");
}
}
Expand Down
Loading
Loading