Skip to content

Commit 85f96b0

Browse files
committed
Merge bitcoin#30909: wallet, assumeutxo: Don't Assume m_chain_tx_count, Improve wallet RPC errors
9d2d9f7 rpc: Include assumeutxo as a failure reason of rescanblockchain (Fabian Jahr) 595edee test, assumeutxo: import descriptors during background sync (Alfonso Roman Zubeldia) d73ae60 rpc: Improve importdescriptor RPC error messages (Fabian Jahr) 27f99b6 validation: Don't assume m_chain_tx_count in GuessVerificationProgress (Fabian Jahr) 42d5d53 interfaces: Add helper function for wallet on pruning (Fabian Jahr) Pull request description: A test that is added as part of bitcoin#30455 uncovered this issue: The `GuessVerificationProgress` function is used during during descriptor import and relies on `m_chain_tx_count`. In bitcoin#29370 an [`Assume` was added](bitcoin@0fd915e) expecting the `m_chaint_tx_count` to be set. However, as the test uncovered, `GuessVerificationProgress` is called with background sync blocks that have `m_chaint_tx_count = 0` when they have not been downloaded and processed yet. The simple fix is to remove the `Assume`. Users should not be thrown off by the `Internal bug detected` error. The behavior of `importdescriptor` is kept consistent with the behavior for blocks missing due to pruning. The test by alfonsoromanz is cherry-picked here to show that the [CI errors](https://cirrus-ci.com/task/5110045812195328?logs=ci#L2535) should be fixed by this change. This PR also improves error messages returned by the `importdescriptors` and `rescanblockchain` RPCs. The error message now changes depending on the situation of the node, i.e. if pruning is happening or an assumutxo backgroundsync is active. ACKs for top commit: achow101: ACK 9d2d9f7 mzumsande: Code Review ACK 9d2d9f7 furszy: Code review ACK 9d2d9f7 Tree-SHA512: b841a9b371e5eb8eb3bfebca35645ff2fdded7a3e5e06308d46a33a51ca42cc4c258028c9958fbbb6cda9bb990e07ab8d8504dd9ec6705ef78afe0435912b365
2 parents 601a6a6 + 9d2d9f7 commit 85f96b0

File tree

6 files changed

+68
-18
lines changed

6 files changed

+68
-18
lines changed

src/interfaces/chain.h

+3
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,9 @@ class Chain
289289
//! Check if any block has been pruned.
290290
virtual bool havePruned() = 0;
291291

292+
//! Get the current prune height.
293+
virtual std::optional<int> getPruneHeight() = 0;
294+
292295
//! Check if the node is ready to broadcast transactions.
293296
virtual bool isReadyToBroadcast() = 0;
294297

src/node/interfaces.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
#include <policy/settings.h>
4747
#include <primitives/block.h>
4848
#include <primitives/transaction.h>
49+
#include <rpc/blockchain.h>
4950
#include <rpc/protocol.h>
5051
#include <rpc/server.h>
5152
#include <support/allocators/secure.h>
@@ -770,6 +771,11 @@ class ChainImpl : public Chain
770771
LOCK(::cs_main);
771772
return chainman().m_blockman.m_have_pruned;
772773
}
774+
std::optional<int> getPruneHeight() override
775+
{
776+
LOCK(chainman().GetMutex());
777+
return GetPruneHeight(chainman().m_blockman, chainman().ActiveChain());
778+
}
773779
bool isReadyToBroadcast() override { return !chainman().m_blockman.LoadingBlocks() && !isInitialBlockDownload(); }
774780
bool isInitialBlockDownload() override
775781
{

src/validation.cpp

+2-3
Original file line numberDiff line numberDiff line change
@@ -5623,9 +5623,8 @@ double ChainstateManager::GuessVerificationProgress(const CBlockIndex* pindex) c
56235623
return 0.0;
56245624
}
56255625

5626-
if (!Assume(pindex->m_chain_tx_count > 0)) {
5627-
LogWarning("Internal bug detected: block %d has unset m_chain_tx_count (%s %s). Please report this issue here: %s\n",
5628-
pindex->nHeight, CLIENT_NAME, FormatFullVersion(), CLIENT_BUGREPORT);
5626+
if (pindex->m_chain_tx_count == 0) {
5627+
LogDebug(BCLog::VALIDATION, "Block %d has unset m_chain_tx_count. Unable to estimate verification progress.\n", pindex->nHeight);
56295628
return 0.0;
56305629
}
56315630

src/wallet/rpc/backup.cpp

+19-12
Original file line numberDiff line numberDiff line change
@@ -1745,20 +1745,27 @@ RPCHelpMan importdescriptors()
17451745
if (scanned_time <= GetImportTimestamp(request, now) || results.at(i).exists("error")) {
17461746
response.push_back(results.at(i));
17471747
} else {
1748+
std::string error_msg{strprintf("Rescan failed for descriptor with timestamp %d. There "
1749+
"was an error reading a block from time %d, which is after or within %d seconds "
1750+
"of key creation, and could contain transactions pertaining to the desc. As a "
1751+
"result, transactions and coins using this desc may not appear in the wallet.",
1752+
GetImportTimestamp(request, now), scanned_time - TIMESTAMP_WINDOW - 1, TIMESTAMP_WINDOW)};
1753+
if (pwallet->chain().havePruned()) {
1754+
error_msg += strprintf(" This error could be caused by pruning or data corruption "
1755+
"(see bitcoind log for details) and could be dealt with by downloading and "
1756+
"rescanning the relevant blocks (see -reindex option and rescanblockchain RPC).");
1757+
} else if (pwallet->chain().hasAssumedValidChain()) {
1758+
error_msg += strprintf(" This error is likely caused by an in-progress assumeutxo "
1759+
"background sync. Check logs or getchainstates RPC for assumeutxo background "
1760+
"sync progress and try again later.");
1761+
} else {
1762+
error_msg += strprintf(" This error could potentially caused by data corruption. If "
1763+
"the issue persists you may want to reindex (see -reindex option).");
1764+
}
1765+
17481766
UniValue result = UniValue(UniValue::VOBJ);
17491767
result.pushKV("success", UniValue(false));
1750-
result.pushKV(
1751-
"error",
1752-
JSONRPCError(
1753-
RPC_MISC_ERROR,
1754-
strprintf("Rescan failed for descriptor with timestamp %d. There was an error reading a "
1755-
"block from time %d, which is after or within %d seconds of key creation, and "
1756-
"could contain transactions pertaining to the desc. As a result, transactions "
1757-
"and coins using this desc may not appear in the wallet. This error could be "
1758-
"caused by pruning or data corruption (see bitcoind log for details) and could "
1759-
"be dealt with by downloading and rescanning the relevant blocks (see -reindex "
1760-
"option and rescanblockchain RPC).",
1761-
GetImportTimestamp(request, now), scanned_time - TIMESTAMP_WINDOW - 1, TIMESTAMP_WINDOW)));
1768+
result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, error_msg));
17621769
response.push_back(std::move(result));
17631770
}
17641771
}

src/wallet/rpc/transactions.cpp

+9-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <key_io.h>
77
#include <policy/rbf.h>
88
#include <rpc/util.h>
9+
#include <rpc/blockchain.h>
910
#include <util/vector.h>
1011
#include <wallet/receive.h>
1112
#include <wallet/rpc/util.h>
@@ -909,9 +910,15 @@ RPCHelpMan rescanblockchain()
909910
}
910911
}
911912

912-
// We can't rescan beyond non-pruned blocks, stop and throw an error
913+
// We can't rescan unavailable blocks, stop and throw an error
913914
if (!pwallet->chain().hasBlocks(pwallet->GetLastBlockHash(), start_height, stop_height)) {
914-
throw JSONRPCError(RPC_MISC_ERROR, "Can't rescan beyond pruned data. Use RPC call getblockchaininfo to determine your pruned height.");
915+
if (pwallet->chain().havePruned() && pwallet->chain().getPruneHeight() >= start_height) {
916+
throw JSONRPCError(RPC_MISC_ERROR, "Can't rescan beyond pruned data. Use RPC call getblockchaininfo to determine your pruned height.");
917+
}
918+
if (pwallet->chain().hasAssumedValidChain()) {
919+
throw JSONRPCError(RPC_MISC_ERROR, "Failed to rescan unavailable blocks likely due to an in-progress assumeutxo background sync. Check logs or getchainstates RPC for assumeutxo background sync progress and try again later.");
920+
}
921+
throw JSONRPCError(RPC_MISC_ERROR, "Failed to rescan unavailable blocks, potentially caused by data corruption. If the issue persists you may want to reindex (see -reindex option).");
915922
}
916923

917924
CHECK_NONFATAL(pwallet->chain().findAncestorByHeight(pwallet->GetLastBlockHash(), start_height, FoundBlock().hash(start_block)));

test/functional/wallet_assumeutxo.py

+29-1
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
88
## Possible test improvements
99
10-
- TODO: test import descriptors while background sync is in progress
1110
- TODO: test loading a wallet (backup) on a pruned node
1211
1312
"""
1413
from test_framework.address import address_to_scriptpubkey
14+
from test_framework.descriptors import descsum_create
1515
from test_framework.test_framework import BitcoinTestFramework
1616
from test_framework.messages import COIN
1717
from test_framework.util import (
@@ -20,6 +20,7 @@
2020
ensure_for,
2121
)
2222
from test_framework.wallet import MiniWallet
23+
from test_framework.wallet_util import get_generate_key
2324

2425
START_HEIGHT = 199
2526
SNAPSHOT_BASE_HEIGHT = 299
@@ -49,6 +50,13 @@ def setup_network(self):
4950
self.add_nodes(3)
5051
self.start_nodes(extra_args=self.extra_args)
5152

53+
def import_descriptor(self, node, wallet_name, key, timestamp):
54+
import_request = [{"desc": descsum_create("pkh(" + key.pubkey + ")"),
55+
"timestamp": timestamp,
56+
"label": "Descriptor import test"}]
57+
wrpc = node.get_wallet_rpc(wallet_name)
58+
return wrpc.importdescriptors(import_request)
59+
5260
def run_test(self):
5361
"""
5462
Bring up two (disconnected) nodes, mine some new blocks on the first,
@@ -157,6 +165,21 @@ def run_test(self):
157165
self.log.info("Backup from before the snapshot height can't be loaded during background sync")
158166
assert_raises_rpc_error(-4, "Wallet loading failed. Error loading wallet. Wallet requires blocks to be downloaded, and software does not currently support loading wallets while blocks are being downloaded out of order when using assumeutxo snapshots. Wallet should be able to load successfully after node sync reaches height 299", n1.restorewallet, "w2", "backup_w2.dat")
159167

168+
self.log.info("Test loading descriptors during background sync")
169+
wallet_name = "w1"
170+
n1.createwallet(wallet_name, disable_private_keys=True)
171+
key = get_generate_key()
172+
time = n1.getblockchaininfo()['time']
173+
timestamp = 0
174+
expected_error_message = f"Rescan failed for descriptor with timestamp {timestamp}. There was an error reading a block from time {time}, which is after or within 7200 seconds of key creation, and could contain transactions pertaining to the desc. As a result, transactions and coins using this desc may not appear in the wallet. This error is likely caused by an in-progress assumeutxo background sync. Check logs or getchainstates RPC for assumeutxo background sync progress and try again later."
175+
result = self.import_descriptor(n1, wallet_name, key, timestamp)
176+
assert_equal(result[0]['error']['code'], -1)
177+
assert_equal(result[0]['error']['message'], expected_error_message)
178+
179+
self.log.info("Test that rescanning blocks from before the snapshot fails when blocks are not available from the background sync yet")
180+
w1 = n1.get_wallet_rpc(wallet_name)
181+
assert_raises_rpc_error(-1, "Failed to rescan unavailable blocks likely due to an in-progress assumeutxo background sync. Check logs or getchainstates RPC for assumeutxo background sync progress and try again later.", w1.rescanblockchain, 100)
182+
160183
PAUSE_HEIGHT = FINAL_HEIGHT - 40
161184

162185
self.log.info("Restarting node to stop at height %d", PAUSE_HEIGHT)
@@ -204,6 +227,11 @@ def run_test(self):
204227
self.wait_until(lambda: len(n2.getchainstates()['chainstates']) == 1)
205228
ensure_for(duration=1, f=lambda: (n2.getbalance() == 34))
206229

230+
self.log.info("Ensuring descriptors can be loaded after background sync")
231+
n1.loadwallet(wallet_name)
232+
result = self.import_descriptor(n1, wallet_name, key, timestamp)
233+
assert_equal(result[0]['success'], True)
234+
207235

208236
if __name__ == '__main__':
209237
AssumeutxoTest(__file__).main()

0 commit comments

Comments
 (0)