-
Notifications
You must be signed in to change notification settings - Fork 37.7k
wallet: Fast rescan with BIP157 block filters #15845
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Concept ACK |
Concept ACK |
src/wallet/wallet.cpp
Outdated
for (const auto& i : CBasicKeyStore::GetAllWatchedScriptPubKeys()) { | ||
ret.emplace(i.begin(), i.end()); | ||
} | ||
for (const auto& bs : CBasicKeyStore::GetAllBareScripts()) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
Nice! That was quick. |
Some rough benchmarks { real 4m23.596s A simple wallet,... filtered out roughly 2900 blocks and scanned them. |
a0aa855
to
106e7c9
Compare
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ConflictsReviewers, this pull request conflicts with the following ones:
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
Updated at: 2019-04-19T22:20:19.826705. |
There was a problem hiding this comment.
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.
src/interfaces/chain.cpp
Outdated
@@ -263,6 +264,19 @@ class ChainImpl : public Chain | |||
return std::move(result); | |||
} | |||
std::unique_ptr<Chain::Lock> assumeLocked() override { return MakeUnique<LockImpl>(); } | |||
bool filterMatchesAny(const uint256& hash, const GCSFilter::ElementSet& filter_set) override |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
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). |
and the obligatory concept ACK! |
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. :( |
@gmaxwell What specialized parser are you comparing to? Does it include the time to match all outputs/inputs to a set of 6000 sPKs? |
@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. |
3ddbb2a
to
abcddec
Compare
@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. |
abcddec
to
474f9b9
Compare
474f9b9
to
5c9bbcf
Compare
ce61001
to
7b024b8
Compare
7b024b8
to
faee7b6
Compare
Thanks for the review @mzumsande , @luke-jr , @jkczyz . Addressed all your feedback in the latest push |
const BlockFilterIndex* block_filter_index = GetBlockFilterIndex(BlockFilterType::BASIC); | ||
if (!block_filter_index) return {}; | ||
BlockFilter filter; | ||
const CBlockIndex* index; |
There was a problem hiding this comment.
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 {};
@@ -248,6 +249,20 @@ class ChainImpl : public Chain | |||
// LockImpl to Lock pointer | |||
return std::move(result); | |||
} | |||
Optional<bool> filterMatchesAny(const uint256& hash, const GCSFilter::ElementSet& filter_set) override | |||
{ | |||
const BlockFilterIndex* block_filter_index = GetBlockFilterIndex(BlockFilterType::BASIC); |
There was a problem hiding this comment.
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 = ...
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 */ |
There was a problem hiding this comment.
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.
ScanResult result; | ||
|
||
WalletLogPrintf("Rescan started from block %s...\n", start_block.ToString()); | ||
|
||
uint256 block_hash = start_block; | ||
const auto filter_set = GetLegacyScriptPubKeyMan()->GetAllRelevantScriptPubKeys(); |
There was a problem hiding this comment.
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.
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. |
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
Needs rebase |
{ | ||
LOCK(cs_KeyStore); | ||
GCSFilter::ElementSet ret; | ||
for (const auto& k : mapKeys) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 thinkmapKeys
is used on encrypted wallets?
Yes, this should just loop over mapCryptedKeys
as well. mapKeys
should be empty in encryped wallets.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
@MarcoFalke - do you mind documenting the problems with this approach, so people who find this PR in future know why you closed it? |
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
Use BIP157 block filters if they are available to speed up rescans