Skip to content

wallet: Fast rescan with BIP157 block filters#15845

Closed
maflcko wants to merge 6 commits into
bitcoin:masterfrom
maflcko:1904-walletFastRescan
Closed

wallet: Fast rescan with BIP157 block filters#15845
maflcko wants to merge 6 commits into
bitcoin:masterfrom
maflcko:1904-walletFastRescan

Conversation

@maflcko

@maflcko maflcko commented Apr 18, 2019

Copy link
Copy Markdown
Member

Use BIP157 block filters if they are available to speed up rescans

@gmaxwell

Copy link
Copy Markdown
Contributor

Concept ACK

Comment thread src/wallet/wallet.cpp Outdated
@jnewbery

Copy link
Copy Markdown
Contributor

Concept ACK

Comment thread src/wallet/wallet.cpp Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just because we know about a script does not imply we're watching it I believe.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but I'd rather err on the safe side of scanning a block too much than missing a block (and thus lose coins)

@jonasschnelli

Copy link
Copy Markdown
Contributor

Nice! That was quick.
Concept ACK

@jonasschnelli

Copy link
Copy Markdown
Contributor

Some rough benchmarks

{
"start_height": 0,
"stop_height": 572152
}

real 4m23.596s

A simple wallet,... filtered out roughly 2900 blocks and scanned them.

@maflcko maflcko force-pushed the 1904-walletFastRescan branch 3 times, most recently from a0aa855 to 106e7c9 Compare April 19, 2019 14:35
@DrahtBot

DrahtBot commented Apr 19, 2019

Copy link
Copy Markdown
Contributor

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #17384 (test: Create new test library by MarcoFalke)
  • #16528 ([WIP] Native Descriptor Wallets (take 2) by achow101)
  • #16442 (Serve BIP 157 compact filters by jimpo)

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

Coverage

Coverage Change (pull 15845, 77e593bc79216175fa4c2fd05278bf1b7e4c7146) Reference (master, 56376f3)
Lines +0.0064 % 87.5552 %
Functions -0.0143 % 84.6451 %
Branches +0.0017 % 51.5534 %

Updated at: 2019-04-19T22:20:19.826705.

@jimpo jimpo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yay, Concept ACK.

Comments:

  • I noticed there are lots of false positive hits because DEFAULT_KEYPOOL_SIZE is 1,000, with is multiplied by like 2 or 4 (I forget) with all of the scriptPubKey variants added for a key. Why is the default keypool size so large?
  • The scanning might be faster using the method to batch fetch filters, LookupFilterRange. It's probably not worth the additional complexity in the wallet and chain interface code though.

Comment thread src/wallet/wallet.cpp Outdated
Comment thread src/interfaces/chain.cpp Outdated
Comment thread src/interfaces/chain.cpp Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe make the BlockFilterType a parameter here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will there ever be a filter different from the basic filter?

Comment thread src/wallet/wallet.h Outdated
Comment thread src/init.cpp Outdated
@instagibbs

Copy link
Copy Markdown
Member

@jimpo

Why is the default keypool size so large?

IIRC this makes it much less likely typical usage will hit a case when an encrypted wallet runs out of viable keys and starts to miss funds during a sync(since it currently doesn't pause a sync and ask the user for password).

@instagibbs

Copy link
Copy Markdown
Member

and the obligatory concept ACK!

@gmaxwell

Copy link
Copy Markdown
Contributor

Why is the default keypool size so large?

It isn't. It's small... the whole concept of 'gap' scanning is fairly broken and easily results in funds loss... e.g. when a user hands out multiple addresses to people who haven't used them yet. The brokenness can be largely addressed by setting the lookahead pretty far.

In some prelim testing this appears to be slower than just rescanning the block files directly with a specialized parser. :(

@sipa

sipa commented Apr 20, 2019

Copy link
Copy Markdown
Member

@gmaxwell What specialized parser are you comparing to? Does it include the time to match all outputs/inputs to a set of 6000 sPKs?

@sipa

sipa commented Apr 20, 2019

Copy link
Copy Markdown
Member

@jimpo 2 keypools (external and change), each 1000 keys, each key 3 sPKs (p2pkh, p2sh-p2wpkh, native p2wpkh), so I expect 6000 entries to match against.

@maflcko maflcko force-pushed the 1904-walletFastRescan branch 5 times, most recently from 3ddbb2a to abcddec Compare April 24, 2019 17:34
@gmaxwell

Copy link
Copy Markdown
Contributor

@gmaxwell modified copy of the old scanner from bitcoin talk, doesn't check the inputs. I think the main issue is that rehashing all addresses for each block sequentially is pegging a single core and limiting the speed.

@maflcko maflcko force-pushed the 1904-walletFastRescan branch from abcddec to 474f9b9 Compare May 17, 2019 16:11
@maflcko maflcko force-pushed the 1904-walletFastRescan branch from 474f9b9 to 5c9bbcf Compare May 24, 2019 11:06
@maflcko maflcko force-pushed the 1904-walletFastRescan branch from ce61001 to 7b024b8 Compare November 5, 2019 13:19
@maflcko maflcko force-pushed the 1904-walletFastRescan branch from 7b024b8 to faee7b6 Compare November 5, 2019 13:40
@maflcko

maflcko commented Nov 5, 2019

Copy link
Copy Markdown
Member Author

Thanks for the review @mzumsande , @luke-jr , @jkczyz . Addressed all your feedback in the latest push

Comment thread src/interfaces/chain.cpp
const BlockFilterIndex* block_filter_index = GetBlockFilterIndex(BlockFilterType::BASIC);
if (!block_filter_index) return {};
BlockFilter filter;
const CBlockIndex* index;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could lock even smaller block of code:

        {
            LOCK(cs_main);
            index = LookupBlockIndex(hash);
        }
        if (!index) return {};

Or maybe you would care to use ES.28: Use lambdas for complex initialization, especially of const variables guideline (note const for index pointer itself):

const CBlockIndex* const index = [&]{ 
    LOCK(cs_main);
    return LookupBlockIndex(hash);
}();
if (!index) return {};

Comment thread src/interfaces/chain.cpp
}
Optional<bool> filterMatchesAny(const uint256& hash, const GCSFilter::ElementSet& filter_set) override
{
const BlockFilterIndex* block_filter_index = GetBlockFilterIndex(BlockFilterType::BASIC);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the pointer itself could be even const too:
const BlockFilterIndex* const block_filter_index = ...

Comment thread src/test/util.cpp Outdated
Comment thread src/test/util.h
/** Returns the generated coin */
/** Returns the generated coin (output at index 0 in the coinbase tx) */
CTxIn MineBlock(const CScript& coinbase_scriptPubKey);
/** Returns the generated coins (output at index 0 in the coinbase tx), or nothing if the block was invalid */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/coins/coin/

I also don't like the (mis)use of the word coin here, which is generally understood to mean a utxo. These functions return a CTxIn that points to the first coinbase output.

Comment thread src/wallet/wallet.cpp
WalletLogPrintf("Rescan started from block %s...\n", start_block.ToString());

uint256 block_hash = start_block;
const auto filter_set = GetLegacyScriptPubKeyMan()->GetAllRelevantScriptPubKeys();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting the filter_set outside the rescan loop means that this doesn't get updated when the keypool is topped up. That means that any payments to an address beyond the first 1000 will be missed on rescan.

@jnewbery

jnewbery commented Nov 6, 2019

Copy link
Copy Markdown
Contributor

I think as well as fixing the bug here: #15845 (comment), it'd be useful to add a test that would catch cases like this, where a rescan exhausts the keypool.

sidhujag pushed a commit to syscoin/syscoin that referenced this pull request Nov 7, 2019
fa0a731 test: Add RegTestingSetup to setup_common (MarcoFalke)
fa54b3e test: move-only ComputeFilter to src/test/lib/blockfilter (MarcoFalke)

Pull request description:

  The default chain for `TestingSetup` is the main chain. However, any test that wants to mine blocks on demand needs to switch to regtest. This is done manually and in-line right now.

  Fix that by creating an explicit `RegTestingSetup` and use it where appropriate.

  Also, add a move-only commit to move `ComputeFilter` into the newly created unit test library.

  Both commits are part of bitcoin#15845, but split up because they are useful on their own.

ACKs for top commit:
  practicalswift:
    ACK fa0a731 -- diff looks correct

Tree-SHA512: 02b9765580b355ed8d1be555f8ae11fa6e3d575f5cb177bbdda0319378837e29de5555c126c477dc8a1e8a5be47335afdcff152cf2dea2fbdd1a988ddde3689b
@DrahtBot

DrahtBot commented Nov 7, 2019

Copy link
Copy Markdown
Contributor
Needs rebase

{
LOCK(cs_KeyStore);
GCSFilter::ElementSet ret;
for (const auto& k : mapKeys) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about mapCryptedKeys? I don't think mapKeys is used on encrypted wallets?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about mapCryptedKeys? I don't think mapKeys is used on encrypted wallets?

Yes, this should just loop over mapCryptedKeys as well. mapKeys should be empty in encryped wallets.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function in general does not match everything that IsMine would match.In particular it doesn't enumerate all possible combinations of multisig policies with all combinations of pubkeys or all of the weird nested scripts that are possible. There probably needs to be a release note that if you have some weird and non-standard script stuff, the fast rescan won't see those things.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Such a warning makes sense; people with such wallets will presumably understand. And descriptor wallets won't have this problem.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IsMine itself won't test weird combinations. I don't see why fast rescan shouldn't be able to check for everything IsMine does... users shouldn't be exposed to internal implementation details like this.

@maflcko maflcko closed this May 7, 2020
@maflcko maflcko deleted the 1904-walletFastRescan branch May 7, 2020 23:20
@jnewbery

jnewbery commented May 8, 2020

Copy link
Copy Markdown
Contributor

@MarcoFalke - do you mind documenting the problems with this approach, so people who find this PR in future know why you closed it?

@maflcko

maflcko commented May 8, 2020

Copy link
Copy Markdown
Member Author

I don't think there are any problems with this approach, once this and this is addressed.

As pointed out here multisig should work just fine, but I haven't had the time or interest to look into this, so I closed it.

pstratem pushed a commit to pstratem/bitcoin that referenced this pull request May 31, 2020
pstratem pushed a commit to pstratem/bitcoin that referenced this pull request Jun 6, 2020
@andronoob andronoob mentioned this pull request Jul 28, 2020
sidhujag pushed a commit to syscoin-core/syscoin that referenced this pull request Nov 10, 2020
fa0a731 test: Add RegTestingSetup to setup_common (MarcoFalke)
fa54b3e test: move-only ComputeFilter to src/test/lib/blockfilter (MarcoFalke)

Pull request description:

  The default chain for `TestingSetup` is the main chain. However, any test that wants to mine blocks on demand needs to switch to regtest. This is done manually and in-line right now.

  Fix that by creating an explicit `RegTestingSetup` and use it where appropriate.

  Also, add a move-only commit to move `ComputeFilter` into the newly created unit test library.

  Both commits are part of bitcoin#15845, but split up because they are useful on their own.

ACKs for top commit:
  practicalswift:
    ACK fa0a731 -- diff looks correct

Tree-SHA512: 02b9765580b355ed8d1be555f8ae11fa6e3d575f5cb177bbdda0319378837e29de5555c126c477dc8a1e8a5be47335afdcff152cf2dea2fbdd1a988ddde3689b
@maflcko maflcko mentioned this pull request Dec 10, 2021
4 tasks
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Feb 15, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.