-
Notifications
You must be signed in to change notification settings - Fork 37.8k
wallet: Only fail rescan when blocks have actually been pruned #15870
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
fa00f6e
to
fa12b93
Compare
Concept ACK. There is an argument against doing things like this: that it's better to fail deterministically rather than fail randomly at some point in the future and have created incorrect expectations of functionality. But setting prune to some large "future" amount is a perfectly reasonable configuration. And this doesn't cause it to fail randomly, but rather work until it doesn't. So I don't believe the general argument against this sort of thing offsets the benefit. |
This seems to affect importaddress, importpubkey, importprivkey, and importwallet but not importmulti. I wonder how the new behavior compares to importmulti behavior, and if there's a difference, whether that's justified. |
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ConflictsNo conflicts as of last run. |
@ryanofsky The only difference between importmulti and the other calls is that in importmulti you can "opt-out" of scanning early blocks by setting a later timestamp. Otherwise, my change makes all calls behave in the same way. Edit: I believe the only difference is that importmulti will import the keys and then fail the rescan, whereas the other calls will exit early and not attempt to import anything. |
1ceacd8
to
fad8451
Compare
Why a different behavior? It's already possible to call |
I don't know. I didn't write the importmulti RPC and I don't plan to change it in this pull request. |
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.
@MarcoFalke I wasn't suggesting changing that behavior.
Concept ACK. Correct me if I'm wrong, but it doesn't have to fail if all required blocks are available right?
@@ -326,7 +326,11 @@ class ChainImpl : public Chain | |||
CFeeRate relayIncrementalFee() override { return ::incrementalRelayFee; } | |||
CFeeRate relayDustFee() override { return ::dustRelayFee; } | |||
CAmount maxTxFee() override { return ::maxTxFee; } | |||
bool getPruneMode() override { return ::fPruneMode; } | |||
bool havePruned() 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.
Move to the chain lock?
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.
Why?
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.
Sorry for the brief reply, but see here:
bitcoin/src/interfaces/chain.h
Lines 69 to 73 in fad8451
//! Interface for querying locked chain state, used by legacy code that | |
//! assumes state won't change between calls. New code should avoid using | |
//! the Lock interface and instead call higher-level Chain methods | |
//! that return more information so the chain doesn't need to stay locked | |
//! between calls. |
The chain lock is deprecated and will be removed in the future.
@@ -157,8 +157,8 @@ UniValue importprivkey(const JSONRPCRequest& request) | |||
if (!request.params[2].isNull()) | |||
fRescan = request.params[2].get_bool(); | |||
|
|||
if (fRescan && pwallet->chain().getPruneMode()) { | |||
throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled in pruned mode"); | |||
if (fRescan && pwallet->chain().havePruned()) { |
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.
A block can be pruned before RescanWallet (L202). What would be the error then?
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.
It will throw an error "Rescan was unable to fully rescan the blockchain. Some transactions may be missing."
importmulti does not fail the rescan when all blocks after the timestamp are available, but it fails after the import if a block is missing. |
fad8451
to
faa7e29
Compare
faa7e29
to
fa6838e
Compare
Let's use prune height instead, so |
I am not touching |
I mean it would make sense to use a common interface with |
Oh, the RPCs I touch are already deprecated in favor of importmulti, so I'd rather not add features to them. |
I'm commenting on the src/interfaces changes specifically. |
Ah. Yeah, since we don't store the prune height, this would complicate my changes slightly. I think the additional code can be added later (when needed in rescanblockchain). |
I'm a little concerned about this change. Anywhere that The four call sites in rpcdump do not hold |
@jnewbery The check is only there to provide a nice error message. As you correctly observe it is not needed for correctness. Should I clarify that with a comment? The error message otherwise would be #15870 (comment) |
Yes, that sounds good. Thanks! |
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.
utACK fa9fa5d
Will happily reACK a squashed branch.
fa9fa5d
to
fa9f3cc
Compare
utACK fa9f3cc |
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.
A few code comments. More documentation and tests might make this change and the expected behavior before/after clearer, at least to me.
src/wallet/rpcdump.cpp
Outdated
if (pwallet->chain().havePruned()) { | ||
// Exit early and print an error. | ||
// If a block is pruned after this check, we will import the key(s), | ||
// but fail the rescan with a generic error. | ||
throw JSONRPCError(RPC_WALLET_ERROR, "Importing wallets is disabled in pruned mode"); |
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.
Does the code doc in 588 correspond correctly with the error message in 589?
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.
Yes, "importing wallet" means "importing key(s)", I believe.
// Exit early and print an error. | ||
// If a block is pruned after this check, we will import the key(s), | ||
// but fail the rescan with a generic error. | ||
throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled when blocks are pruned"); |
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.
Would it be worth extracting this code block repeated in importprivkey
, importaddress
, importpubkey
, and importwallet
(the latter with a different error message).
if (fRescan && pwallet->chain().havePruned()) {
// Exit early and print an error.
// If a block is pruned after this check, we will import the key(s),
// but fail the rescan with a generic error.
throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled when blocks are pruned");
}
Edit: I understand it may be a premature or unnecessary optimisation. Just an observation.
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.
Yeah, could make sense. Will do if I have to touch for other reasons.
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.
Yeah, in a follow up is fine.
-BEGIN VERIFY SCRIPT- ./contrib/devtools/copyright_header.py update ./src/wallet/ -END VERIFY SCRIPT-
fa9f3cc
to
aaaa57c
Compare
Concept ACK. My first read of this was that it'd permit rescanning pruned chains, as long as the to-be-rescanned blocks are not pruned, but it seems to just be about whether or not any blocks are actually pruned (independent of the ones to be rescanned), right? |
The legacy calls would always rescan the whole chain. As soon as one block is pruned, it fails. |
Out of scope for this PR but somewhat related -- would there be any reason to not attempt to fetch the pruned blocks from the P2P network to allow the rescan to happen? |
Jup, but that might be something to do only after #15946 |
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.
utACK aaaa57c. The change seems fine, though it's not really clear what the motivation for it is. It might be good to add a release note describing the change and what benefits it has from a user perspective.
@@ -39,23 +38,17 @@ | |||
class Variant(collections.namedtuple("Variant", "call data rescan prune")): | |||
"""Helper for importing one key and verifying scanned transactions.""" | |||
|
|||
def try_rpc(self, func, *args, **kwargs): | |||
if self.expect_disabled: | |||
assert_raises_rpc_error(-4, "Rescan is disabled in pruned mode", func, *args, **kwargs) |
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.
Now there is no longer any test coverage for the "rescan is disabled" cases, but I'm not sure if there's an easy way to add it back. Maybe a pruneblockchain
RPC call could be added.
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.
If a test was added, it should also (or more importantly) test importmulti
, so I am not actually decreasing test coverage 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.
I agree that a test would be nice for that case, but can be done in a follow up pull request.
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.
it should also (or more importantly) test importmulti
In case it helps, there is currently a unit test for importmulti behavior with pruning:
bitcoin/src/wallet/test/wallet_tests.cpp
Line 158 in 2d16fb7
UniValue response = importmulti(request); |
utACK aaaa57c Makes a lot of sense to allow rescanning for as long as we can as @gmaxwell notes above - and indeed it might even be prudent for most users to set a future-proofing prune value that reflects their disk limitations at startup time. Better that your node start pruning vs. run out of disk space and exit. Maybe in the future we could even default to such a thing based on the datadir device if pruning-enabled-but-not-done-yet behavior is equivalent to a non-pruning node. Or introduce an "adaptive" pruning mode, which periodically polls for available disk space and adjusts the prune target accordingly. But of course this is all out of scope for this change. |
@jamesob you mean an option to ensure a minimum free space? |
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.
// Exit early and print an error. | ||
// If a block is pruned after this check, we will import the key(s), | ||
// but fail the rescan with a generic error. | ||
throw JSONRPCError(RPC_WALLET_ERROR, "Importing wallets is disabled when blocks are pruned"); |
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 should be in 1s commit.
// Exit early and print an error. | ||
// If a block is pruned after this check, we will import the key(s), | ||
// but fail the rescan with a generic error. | ||
throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled when blocks are pruned"); |
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.
Yeah, in a follow up is fine.
@@ -573,7 +582,10 @@ UniValue importwallet(const JSONRPCRequest& request) | |||
}, | |||
}.ToString()); | |||
|
|||
if (pwallet->chain().getPruneMode()) { | |||
if (pwallet->chain().havePruned()) { |
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.
The dump file contains timestamps so, correct me if I'm wrong, it could only throw this error if earliest is pruned - the same for importmulti
if timestamps are given.
Edit: especially when manual pruning.
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.
Sounds like a nice follow up pull request
If you mean "prune as little as possible and only until necessitated by lack of space," yes. Some kind of adaptive prune would just be a measure to avoid hitting an out-of-disk error for as long as possible. But this is out of scope in terms of this PR so maybe I'll file an issue later or something. |
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.
utACK fa7e311
bool getPruneMode() override { return ::fPruneMode; } | ||
bool havePruned() override | ||
{ | ||
LOCK(cs_main); |
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.
I'm not suggesting you change this PR, but for future reference we now have a handy WITH_LOCK
macro:
Line 209 in fd61b9f
#define WITH_LOCK(cs, code) [&] { LOCK(cs); code; }() |
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.
I fail to see how this makes the code any easier to read
…ly been pruned fa7e311 [doc] rpcwallet: Only fail rescan when blocks have been pruned (MarcoFalke) aaaa57c scripted-diff: Bump copyright headers in wallet (MarcoFalke) faf3729 wallet: Only fail rescan when blocks have actually been pruned (MarcoFalke) Pull request description: This brings the behaviour of the import* calls closer to importmulti. After this change, the difference between importmulti and the other import* calls is * that in importmulti you can "opt-out" of scanning early blocks by setting a later timestamp. * that in importmulti the wallet will successfully import the data, but fail to rescan. Whereas in the other calls, the wallet will abort before importing the data. ACKs for commit fa7e31: promag: utACK fa7e311. jnewbery: utACK fa7e311 Tree-SHA512: a57d52ffea94b64e0eb9b5d3a7a63031325833908297dd14eb0c5251ffea3b2113b131003f1db4e9599e014369165a57f107a7150bb65e4c791e5fe742f33cb8
post-merge utACK |
…ly been pruned fa7e311 [doc] rpcwallet: Only fail rescan when blocks have been pruned (MarcoFalke) aaaa57c scripted-diff: Bump copyright headers in wallet (MarcoFalke) faf3729 wallet: Only fail rescan when blocks have actually been pruned (MarcoFalke) Pull request description: This brings the behaviour of the import* calls closer to importmulti. After this change, the difference between importmulti and the other import* calls is * that in importmulti you can "opt-out" of scanning early blocks by setting a later timestamp. * that in importmulti the wallet will successfully import the data, but fail to rescan. Whereas in the other calls, the wallet will abort before importing the data. ACKs for commit fa7e31: promag: utACK fa7e311. jnewbery: utACK fa7e311 Tree-SHA512: a57d52ffea94b64e0eb9b5d3a7a63031325833908297dd14eb0c5251ffea3b2113b131003f1db4e9599e014369165a57f107a7150bb65e4c791e5fe742f33cb8
This brings the behaviour of the import* calls closer to importmulti. After this change, the difference between importmulti and the other import* calls is