Skip to content

Commit 88cd2ce

Browse files
committed
Merge 90a5786 into merged_master (Bitcoin PR bitcoin/bitcoin#30678)
2 parents 5e863b6 + 90a5786 commit 88cd2ce

3 files changed

Lines changed: 69 additions & 5 deletions

File tree

src/wallet/wallet.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3836,6 +3836,14 @@ void CWallet::postInitProcess()
38363836

38373837
bool CWallet::BackupWallet(const std::string& strDest) const
38383838
{
3839+
if (m_chain) {
3840+
CBlockLocator loc;
3841+
WITH_LOCK(cs_wallet, chain().findBlock(m_last_block_processed, FoundBlock().locator(loc)));
3842+
if (!loc.IsNull()) {
3843+
WalletBatch batch(GetDatabase());
3844+
batch.WriteBestBlock(loc);
3845+
}
3846+
}
38393847
return GetDatabase().Backup(strDest);
38403848
}
38413849

@@ -5204,6 +5212,11 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(const std::string& walle
52045212
return util::Error{_("Error: This wallet is already a descriptor wallet")};
52055213
}
52065214

5215+
// Flush chain state before unloading wallet
5216+
CBlockLocator locator;
5217+
WITH_LOCK(wallet->cs_wallet, context.chain->findBlock(wallet->GetLastBlockHash(), FoundBlock().locator(locator)));
5218+
if (!locator.IsNull()) wallet->chainStateFlushed(ChainstateRole::NORMAL, locator);
5219+
52075220
if (!RemoveWallet(context, wallet, /*load_on_start=*/std::nullopt, warnings)) {
52085221
return util::Error{_("Unable to unload the wallet before migrating")};
52095222
}

test/functional/wallet_assumeutxo.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
- TODO: test loading a wallet (backup) on a pruned node
1212
1313
"""
14+
from test_framework.address import address_to_scriptpubkey
1415
from test_framework.test_framework import BitcoinTestFramework
16+
from test_framework.messages import COIN
1517
from test_framework.util import (
1618
assert_equal,
1719
assert_raises_rpc_error,
@@ -62,8 +64,16 @@ def run_test(self):
6264
for n in self.nodes:
6365
n.setmocktime(n.getblockheader(n.getbestblockhash())['time'])
6466

67+
# Create a wallet that we will create a backup for later (at snapshot height)
6568
n0.createwallet('w')
6669
w = n0.get_wallet_rpc("w")
70+
w_address = w.getnewaddress()
71+
72+
# Create another wallet and backup now (before snapshot height)
73+
n0.createwallet('w2')
74+
w2 = n0.get_wallet_rpc("w2")
75+
w2_address = w2.getnewaddress()
76+
w2.backupwallet("backup_w2.dat")
6777

6878
# Generate a series of blocks that `n0` will have in the snapshot,
6979
# but that n1 doesn't yet see. In order for the snapshot to activate,
@@ -84,6 +94,8 @@ def run_test(self):
8494
assert_equal(n.getblockchaininfo()[
8595
"headers"], SNAPSHOT_BASE_HEIGHT)
8696

97+
# This backup is created at the snapshot height, so it's
98+
# not part of the background sync anymore
8799
w.backupwallet("backup_w.dat")
88100

89101
self.log.info("-- Testing assumeutxo")
@@ -103,7 +115,13 @@ def run_test(self):
103115

104116
# Mine more blocks on top of the snapshot that n1 hasn't yet seen. This
105117
# will allow us to test n1's sync-to-tip on top of a snapshot.
106-
self.generate(n0, nblocks=100, sync_fun=self.no_op)
118+
w_skp = address_to_scriptpubkey(w_address)
119+
w2_skp = address_to_scriptpubkey(w2_address)
120+
for i in range(100):
121+
if i % 3 == 0:
122+
self.mini_wallet.send_to(from_node=n0, scriptPubKey=w_skp, amount=1 * COIN)
123+
self.mini_wallet.send_to(from_node=n0, scriptPubKey=w2_skp, amount=10 * COIN)
124+
self.generate(n0, nblocks=1, sync_fun=self.no_op)
107125

108126
assert_equal(n0.getblockcount(), FINAL_HEIGHT)
109127
assert_equal(n1.getblockcount(), START_HEIGHT)
@@ -126,8 +144,13 @@ def run_test(self):
126144

127145
assert_equal(n1.getblockchaininfo()["blocks"], SNAPSHOT_BASE_HEIGHT)
128146

129-
self.log.info("Backup can't be loaded during background sync")
130-
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, "w", "backup_w.dat")
147+
self.log.info("Backup from the snapshot height can be loaded during background sync")
148+
n1.restorewallet("w", "backup_w.dat")
149+
# Balance of w wallet is still still 0 because n1 has not synced yet
150+
assert_equal(n1.getbalance()['bitcoin'], 0)
151+
152+
self.log.info("Backup from before the snapshot height can't be loaded during background sync")
153+
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")
131154

132155
PAUSE_HEIGHT = FINAL_HEIGHT - 40
133156

@@ -159,8 +182,15 @@ def run_test(self):
159182
self.log.info("Ensuring background validation completes")
160183
self.wait_until(lambda: len(n1.getchainstates()['chainstates']) == 1)
161184

162-
self.log.info("Ensuring wallet can be restored from backup")
163-
n1.restorewallet("w", "backup_w.dat")
185+
self.log.info("Ensuring wallet can be restored from a backup that was created before the snapshot height")
186+
n1.restorewallet("w2", "backup_w2.dat")
187+
# Check balance of w2 wallet
188+
assert_equal(n1.getbalance()['bitcoin'], 340)
189+
190+
# Check balance of w wallet after node is synced
191+
n1.loadwallet("w")
192+
w = n1.get_wallet_rpc("w")
193+
assert_equal(w.getbalance()['bitcoin'], 34)
164194

165195

166196
if __name__ == '__main__':

test/functional/wallet_backup.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,25 @@ def restore_wallet_existent_name(self):
140140
assert_raises_rpc_error(-36, error_message, node.restorewallet, wallet_name, backup_file)
141141
assert wallet_file.exists()
142142

143+
def test_pruned_wallet_backup(self):
144+
self.log.info("Test loading backup on a pruned node when the backup was created close to the prune height of the restoring node")
145+
node = self.nodes[3]
146+
self.restart_node(3, ["-prune=1", "-fastprune=1"])
147+
# Ensure the chain tip is at height 214, because this test assume it is.
148+
assert_equal(node.getchaintips()[0]["height"], 214)
149+
# We need a few more blocks so we can actually get above an realistic
150+
# minimal prune height
151+
self.generate(node, 50, sync_fun=self.no_op)
152+
# Backup created at block height 264
153+
node.backupwallet(node.datadir_path / 'wallet_pruned.bak')
154+
# Generate more blocks so we can actually prune the older blocks
155+
self.generate(node, 300, sync_fun=self.no_op)
156+
# This gives us an actual prune height roughly in the range of 220 - 240
157+
node.pruneblockchain(250)
158+
# The backup should be updated with the latest height (locator) for
159+
# the backup to load successfully this close to the prune height
160+
node.restorewallet(f'pruned', node.datadir_path / 'wallet_pruned.bak')
161+
143162
def run_test(self):
144163
self.log.info("Generating initial blockchain")
145164
self.generate(self.nodes[0], 1)
@@ -242,6 +261,8 @@ def run_test(self):
242261
for sourcePath in sourcePaths:
243262
assert_raises_rpc_error(-4, "backup failed", self.nodes[0].backupwallet, sourcePath)
244263

264+
self.test_pruned_wallet_backup()
265+
245266

246267
if __name__ == '__main__':
247268
WalletBackupTest(__file__).main()

0 commit comments

Comments
 (0)