Join GitHub today
GitHub is home to over 20 million developers working together to host and review code, manage projects, and build software together.
[Wallet] Replace -rescan with a new RPC call "rescanblockchain" #7061
Conversation
jonasschnelli
added Wallet RPC
labels
Nov 19, 2015
|
I'd rather have it that the API is such that an explicit rescan is never needed. Wasn't there some work on a multi-import w/ timestamps? |
|
Yes. There is a PR (see PR description). I agree that it would be better to avoid rescans at all, although it might be complicated to catch all edge-cases and a manual trigger can help in situations where someone needs to deal with multiple/complex imports (you only want to do one rescan). And I think some people will cancel a rescan because they want to do other stuff and/or had not considered that a rescan can take a long time. Afterward calling |
|
But manually specifying a block # to rescan from is extremely fragile... it's very easy to get this wrong. Also, rescanning doesn't interact with pruning which will be more and more common in the future. |
|
@lannwj I thought thats part of what the height parameter here was for-- addressing pruning comparability? |
|
How about we call this rescanfromheight instead, to make it possible to add a rescanfromtime later if users demand? Equally, some kind of RPC call that finds the first block with a nTime after a specific time might be useful here. Do we have a way of querying what block # is the oldest non-pruned block? |
|
It also occurs to me that for this usecase we might instead want to have pruning not happen automatically, but rather be an on-demand thing where the user specifies the oldest time they're interested in. |
|
So the biggest negative I personally see here is that it furthers this misunderstanding that rescan is some thing users generally need to be doing. Until we added these non-rescan imports a user initiated rescan is something that never should have been needed (and indicated a serious bug we'd like to know about if it was). As a result of the -rescan argument there is now this whole cargo cult of people that rescan every time they're scarred by a shadow. I hate to further that. But I can't deny how really useful this will be. |
|
@gmaxwell A possible way around that would be to make the rescan check if you have any addresses that haven't yet been scanned in that range and error out if not. (basically make it say "no rescan needed") |
|
Hm. this could also take a stop argument, allowing you to scan single blocks or avoid rescan overlap. Also, I think all the wallet re-scanning should traverse its interval backwards-- for more instant gratification; though this would dork with the wallet transaction ordering... actually import at all breaks that, I should go talk to luke-jr about that. |
promag
and 1 other
commented on an outdated diff
Nov 23, 2015
| + + HelpExampleRpc("rescanblockchain", "\"100000\"") | ||
| + ); | ||
| + | ||
| + LOCK2(cs_main, pwalletMain->cs_wallet); | ||
| + | ||
| + CBlockIndex *pIndexRescan = NULL; | ||
| + if (params.size() > 0 && params[0].isNum()) | ||
| + pIndexRescan = chainActive[params[0].get_int()]; | ||
| + | ||
| + if (!pIndexRescan) | ||
| + pIndexRescan = chainActive.Genesis(); | ||
| + | ||
| + //We can't rescan beyond non-pruned blocks, stop and throw an error | ||
| + if (fPruneMode) | ||
| + { | ||
| + if (fPruneMode) |
|
|
|
agree with gmaxwell that this should scan a range of blocks |
|
Agree with the stop parameter. Working on a implementation.... |
|
I would still prefer an approach that imports with birthdate instead of explicit rescanning. |
|
Added a commit that allows providing a optional parameter with a height where the rescan should stop. @sipa: I agree that rescan height over a key/address birthday would be nice to have (see #6570). But a explicit rescan RPC call can be useful IMO. It's trivial to maintain and it can save lots of rescan-time on the user side. But agree, it has to be considered as "experts" feature. What about implementing a threshold for autodetecting wether the parameter is a blockheight or timestamp (similar to |
|
@jonasschnelli see #6570 (comment) regarding your last comment. |
GIJensen
commented
Dec 7, 2015
|
ACK |
|
If this is still moving forward - I tested it a bit (including pruned mode) and it looks fine to me. One suggestion is to make the start-height a required parameter so that the user specifies "rescanblockchain 1" to scan from genesis. If the start-height is < 1 or higher than current height, throw an error. |
MarcoFalke
referenced
this pull request
May 1, 2016
Closed
Optional parameter for rescans to start at a specified height #7984
promag
commented on an outdated diff
May 2, 2016
laanwj
added the
Feature
label
Jun 16, 2016
luke-jr
added a commit
to bitcoinknots/bitcoin
that referenced
this pull request
Jun 28, 2016
|
|
luke-jr |
8c521c7
|
|
Rebased. |
|
This seems better #7984, but I still prefer not furthering the usage of various rescans. We need APIs that don't require users to keep track of the concept of rescanning IMHO. |
|
I agree. Ideally, there will be no need to rescan. But in practice, rescans are sometimes required (I guess everyone who gave some users support has encountered that). IMO a rpc rescan commend with an optional hight is much more flexible then |
|
I think we should work on |
Yes. I can agree with that. |
luke-jr
referenced
this pull request
Sep 6, 2016
Open
Feature Request: -rescan pruned blockchain #8668
|
Another thing where this could be useful is restoring hd wallets |
|
Since importmulti #7551 is merged, some use cases are covered by that. |
Tend to agree. Closing this for now. |
laanwj
closed this
Nov 2, 2016
|
IMO something like that would be very handy if you rescan a HD wallet (old backup). |
jonasschnelli
reopened this
May 24, 2017
jonasschnelli
changed the title from
[Wallet] add rescanblockchain <height> RPC command to [Wallet] Replace -rescan with a new RPC call "rescanblockchain"
May 24, 2017
|
Reopened and overhauled.
Using |
ryanofsky
reviewed
May 24, 2017
Should add some test coverage for this. One easy way might be to add a new Rescan.manual enum value here: https://github.com/bitcoin/bitcoin/blob/46771514fa86b9a5a0e0af34c1abfa1da22212f7/test/functional/import-rescan.py#L32, then put
if self.rescan == Rescan.manual:
self.node.rescanblockchain()
at the end of the do_import method.
| @@ -367,7 +367,7 @@ std::string HelpMessage(HelpMessageMode mode) | ||
| #ifndef WIN32 | ||
| strUsage += HelpMessageOpt("-pid=<file>", strprintf(_("Specify pid file (default: %s)"), BITCOIN_PID_FILENAME)); | ||
| #endif | ||
| - strUsage += HelpMessageOpt("-prune=<n>", strprintf(_("Reduce storage requirements by enabling pruning (deleting) of old blocks. This allows the pruneblockchain RPC to be called to delete specific blocks, and enables automatic pruning of old blocks if a target size in MiB is provided. This mode is incompatible with -txindex and -rescan. " | ||
| + strUsage += HelpMessageOpt("-prune=<n>", strprintf(_("Reduce storage requirements by enabling pruning (deleting) of old blocks. This allows the pruneblockchain RPC to be called to delete specific blocks, and enables automatic pruning of old blocks if a target size in MiB is provided. This mode is incompatible with -txindex" |
ryanofsky
May 24, 2017
Contributor
Maybe still worth mentioning that pruning can get in the way of successfully importing wallet keys & rescanning.
| + "rescanblockchain (\"start-height\") (\"stop-height\")\n" | ||
| + "\nRescan the local blockchain for wallet related transactions.\n" | ||
| + "\nArguments:\n" | ||
| + "1. \"start-height\" (number, optional) blockheight where the rescan should start\n" |
ryanofsky
May 24, 2017
Contributor
Maybe this should take either times or heights like the pruneblockchain RPC: https://github.com/bitcoin/bitcoin/blob/46771514fa86b9a5a0e0af34c1abfa1da22212f7/src/rpc/blockchain.cpp#L838
jnewbery
May 24, 2017
Member
suggest you change the argument names to startheight and stopheight. No other rpcs use - as word delimiter.
| + pIndexStart = chainActive.Genesis(); | ||
| + | ||
| + //We can't rescan beyond non-pruned blocks, stop and throw an error | ||
| + if (fPruneMode) |
ryanofsky
May 24, 2017
Contributor
Style might need to be updated for this code (moving opening brace to same line here, adding missing braces other lines)
| + block = block->pprev; | ||
| + | ||
| + if (pIndexStart->nHeight < block->nHeight) | ||
| + throw JSONRPCError(RPC_WALLET_ERROR, "Can't rescan beyond pruned data."); |
ryanofsky
May 24, 2017
Contributor
Might be helpful if error message could mention getblockchaininfo RPC for getting pruned height.
| @@ -90,7 +90,7 @@ Reduce storage requirements by enabling pruning (deleting) of old | ||
| blocks. This allows the pruneblockchain RPC to be called to | ||
| delete specific blocks, and enables automatic pruning of old | ||
| blocks if a target size in MiB is provided. This mode is | ||
| -incompatible with \fB\-txindex\fR and \fB\-rescan\fR. Warning: Reverting this | ||
| +incompatible with \fB\-txindex\fR. Warning: Reverting this |
ryanofsky
May 24, 2017
Contributor
I think these files are automatically generated from -help output and could be reverted in the PR.
| @@ -1508,6 +1512,9 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool f | ||
| } else { | ||
| ret = nullptr; | ||
| } | ||
| + if (pindex == pindexStop) { |
ryanofsky
May 24, 2017
Contributor
I don't understand what the use-case is for the stop argument. Can you describe a scenario where you would want to provide it? I can see how it might have been desirable before there was an abortrescan RPC to break a big scan up into little scans, but I don't see why you'd want it now.
| @@ -3865,7 +3871,7 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile) | ||
| RegisterValidationInterface(walletInstance); | ||
| CBlockIndex *pindexRescan = chainActive.Genesis(); | ||
| - if (!GetBoolArg("-rescan", false)) |
ryanofsky
May 24, 2017
Contributor
Is there any advantage to getting rid of the -rescan option if we still have keep all the rescan logic below? Is there a future cleanup or new feature that will be easier to implement with the option gone?
jonasschnelli
May 24, 2017
Member
The -rescan option is global while this PR changes it (partially) to per-wallet. Still, -zapwallettx, etc. need to also be moved to per-wallet basis (I don't know how right now).
But removing the global -rescan option has to be done sooner or later if we want proper multiwallet.
ryanofsky
May 24, 2017
Contributor
But removing the global -rescan option has to be done sooner or later if we want proper multiwallet.
Do -zapwallettx, etc also need to be removed to support proper multiwallet?
jonasschnelli
May 24, 2017
Member
IMO Yes. It's pure db utility. Either we move it to RPC or to a new wallet/db tool.
jnewbery
May 24, 2017
Member
This is confusing: the if statement requires !needsRescan. Shouldn't that be the other way around? If needsRescan is true, we should rescan. I think the if statement should be:
if (needsRescan || GetBoolArg("-salvagewallet", true) || GetBoolArg("-zapwallettxes", true))
re: zapwallet and salvagewallet. I agree that they should be changed to RPCs for multiwallet.
jonasschnelli
May 25, 2017
Member
No. I don't think so.
If that if statement is true, we load the wallet best block which prevents a complete rescan (so it's the opposite).
So we only load the wallet best block (== no rescan) if needsRescan is false and -salvagewallet and --zapwallettxes` have not been set.
| @@ -1468,11 +1468,15 @@ void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived, | ||
| * before CWallet::nTimeFirstKey). Returns null if there is no such range, or |
jnewbery
May 24, 2017
Member
nit: update function comment to say that the rescan is up to pindexStop if it is non-null
| @@ -1485,7 +1489,7 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool f | ||
| while (pindex && nTimeFirstKey && (pindex->GetBlockTime() < (nTimeFirstKey - TIMESTAMP_WINDOW))) | ||
| pindex = chainActive.Next(pindex); | ||
| - ShowProgress(_("Rescanning..."), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup | ||
| + ShowProgress(_("Rescanning..."), 0); // show rescan progress in GUI as dialog or on splashscreen |
jnewbery
May 24, 2017
Member
Need to remove more of this comment, so that it just reads:
// show rescan progress in GUI as dialog
jonasschnelli
May 25, 2017
Member
But can't it also happens during startup when the GUI will show the progress only in the splash-screen and not in a dialog?
| @@ -3865,7 +3871,7 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile) | ||
| RegisterValidationInterface(walletInstance); | ||
| CBlockIndex *pindexRescan = chainActive.Genesis(); | ||
| - if (!GetBoolArg("-rescan", false)) |
jnewbery
May 24, 2017
Member
This is confusing: the if statement requires !needsRescan. Shouldn't that be the other way around? If needsRescan is true, we should rescan. I think the if statement should be:
if (needsRescan || GetBoolArg("-salvagewallet", true) || GetBoolArg("-zapwallettxes", true))
re: zapwallet and salvagewallet. I agree that they should be changed to RPCs for multiwallet.
| + "rescanblockchain (\"start-height\") (\"stop-height\")\n" | ||
| + "\nRescan the local blockchain for wallet related transactions.\n" | ||
| + "\nArguments:\n" | ||
| + "1. \"start-height\" (number, optional) blockheight where the rescan should start\n" |
jnewbery
May 24, 2017
Member
suggest you change the argument names to startheight and stopheight. No other rpcs use - as word delimiter.
| + | ||
| + CBlockIndex *pIndexStart = NULL; | ||
| + CBlockIndex *pIndexStop = NULL; | ||
| + if (request.params.size() > 0 && request.params[0].isNum()) |
jnewbery
May 24, 2017
Member
This should throw an error if the argument isn't a number, rather than continue. It should allow the argument to be Null. I think the if test you need is:
if (request.params.size() > 0 && !request.params[0].isNull()) {
| + if (request.params.size() > 0 && request.params[0].isNum()) | ||
| + pIndexStart = chainActive[request.params[0].get_int()]; | ||
| + | ||
| + if (request.params.size() > 1 && request.params[1].isNum()) |
jnewbery
May 24, 2017
Member
should this return an error if heightstop is lower than heightstart? If heightstop is higher than the tip height?
| + if (pwallet) | ||
| + pwallet->ScanForWalletTransactions(pIndexStart, pIndexStop, true); | ||
| + | ||
| + return NullUniValue; |
jnewbery
May 24, 2017
Member
This RPC should return a message to the user if it is successful, eg "Blockchain rescanned from block <hash> height <height> to block <hash> height <height>. <numtx> transactions added to wallet". Otherwise it's impossible for the user to know whether the call was successful or not.
ryanofsky
Jul 25, 2017
Contributor
Should at least throw an error this isn't successful (the ScanForWalletTransactions call will return null on success, otherwise a pointer to the last failing block, so this should be pretty easy).
| @@ -167,8 +167,8 @@ bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*reco | ||
| // Call Salvage with fAggressive=true to | ||
| // get as much data as possible. | ||
| // Rewrite salvaged data to fresh wallet file | ||
| - // Set -rescan so any missing transactions will be | ||
| - // found. | ||
| + // call rescanblockchain (RPC) so any missing |
jnewbery
May 24, 2017
Member
I can't see where rescanblockchain() is being called. Am I missing something?
jonasschnelli
May 25, 2017
•
Member
It happens outside of this call (somewhere in wallet.cpp). But the comment doesn't say "It" calls rescanblockchain, it either says "you" can call rescanblockchain().
jnewbery
May 25, 2017
Member
I don't understand. The comment is:
// Recovery procedure:
// move wallet file to wallet.timestamp.bak
// Call Salvage with fAggressive=true to
// get as much data as possible.
// Rewrite salvaged data to fresh wallet file
// call rescanblockchain (RPC) so any missing
// transactions will be found.
This function does:
- move wallet file to wallet.timestamp.bak
- Call Salvage with fAggressive=true to get as much data as possible.
- Rewrite salvaged data to fresh wallet file
but it doesn't call rescanblockchain, and I can't see rescanblockchain being called by the callers.
jonasschnelli
May 25, 2017
Member
Yes. You right. The rescan is not part of the call. But the part that calls CDB::Recover does always call a rescan. But now I got your point. We should refer to ScanForWalletTransactions() instead to rescanblockchain (RPC). Right?
jnewbery
May 25, 2017
Member
sorry, I'm still not seeing it. You say "the part that calls CDB::Recover does always call a rescan". Where?
jonasschnelli
May 25, 2017
•
Member
It's confusing. But CDB::Recover only gets calls by setting -salvagewallet which does set -rescan. Maybe I should remove that comment part... I don't know what's best because -rescan is currently mentioned there.
jnewbery
May 25, 2017
Member
ah, makes sense now. Thank you. I wasn't looking in init.cpp, but now I think I see how this fits together. CWallet::Verify() is called first, which calls CWalletDB::Recover() if -salvagewallet is set. Later in AppInitMain(), we call CWallet::AppInitMain(), which is where the rescan happens if -salvagewallet is set.
Perhaps change the comment to something like:
// Try to recover the wallet:
// - move wallet file to wallet.timestamp.bak
// - call Salvage with fAggressive=true to get as much data as possible.
// - rewrite salvaged data to fresh wallet file
//
// CWallet::AppInitMain() will later run a full blockchain rescan to find
// any missing transactions.
luke-jr
added a commit
to bitcoinknots/bitcoin
that referenced
this pull request
Jun 15, 2017
|
|
jonasschnelli + luke-jr |
e6e0efb
|
|
This needs rebase (probably just wait till after 15 at this point). I think outstanding objections to the idea have all largely been removed, Concept ACK from me, at least. |
jonasschnelli
added some commits
Nov 19, 2015
|
Rebased. |
ryanofsky
reviewed
Jul 25, 2017
utACK 7858b9f, but I would prefer if this just deprecated the -rescan option instead of removing it. Removing it with no warning seems needless and kind of jerky, since it barely saves any code.
| + "rescanblockchain (\"startheight\") (\"stopheight\")\n" | ||
| + "\nRescan the local blockchain for wallet related transactions.\n" | ||
| + "\nArguments:\n" | ||
| + "1. \"startHeight\" (number, optional) blockheight where the rescan should start\n" |
| + "\nRescan the local blockchain for wallet related transactions.\n" | ||
| + "\nArguments:\n" | ||
| + "1. \"startHeight\" (number, optional) blockheight where the rescan should start\n" | ||
| + "2. \"stopHeight\" (number, optional) blockheight where the rescan should stop\n" |
ryanofsky
Jul 25, 2017
Contributor
Could do what pruneblockchain does and allow height to be specified either as a physical height or a time: https://github.com/bitcoin/bitcoin/blob/1caafa6cde3b88d926611771f9b4c06fcc6e0007/src/rpc/blockchain.cpp#L855
| + | ||
| + CBlockIndex *pIndexStart = NULL; | ||
| + CBlockIndex *pIndexStop = NULL; | ||
| + if (request.params.size() > 0 && request.params[0].isNum()) { |
ryanofsky
Jul 25, 2017
Contributor
Size checks can be dropped here and below. params[0] will return a null value if the param is missing.
| + //We can't rescan beyond non-pruned blocks, stop and throw an error | ||
| + if (fPruneMode) { | ||
| + CBlockIndex *block = chainActive.Tip(); | ||
| + while (block && block->pprev && (block->pprev->nStatus & BLOCK_HAVE_DATA)) { |
ryanofsky
Jul 25, 2017
Contributor
This should check against pIndexStop too, so error won't trigger unnecessarily in cases where missing blocks are noncontinuous (which can happen with manual pruning).
| + if (pwallet) | ||
| + pwallet->ScanForWalletTransactions(pIndexStart, pIndexStop, true); | ||
| + | ||
| + return NullUniValue; |
ryanofsky
Jul 25, 2017
Contributor
Should at least throw an error this isn't successful (the ScanForWalletTransactions call will return null on success, otherwise a pointer to the last failing block, so this should be pretty easy).
|
If #10941 goes first then the test can be extended to do a |
jonasschnelli commentedNov 19, 2015
•
edited
A RPC rescan command is much more flexible for the following reasons: