Skip to content

Commit 11f8ab1

Browse files
mzumsandefurszy
andcommitted
test: wallet, coverage for crash on dup block disconnection during unclean shutdown
Co-authored-by: furszy <[email protected]>
1 parent 9ef429b commit 11f8ab1

File tree

1 file changed

+52
-0
lines changed

1 file changed

+52
-0
lines changed

test/functional/wallet_reorgsrestore.py

+52
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from test_framework.test_framework import BitcoinTestFramework
2020
from test_framework.util import (
2121
assert_equal,
22+
assert_greater_than,
2223
assert_raises_rpc_error
2324
)
2425

@@ -90,6 +91,54 @@ def test_coinbase_automatic_abandon_during_startup(self):
9091
# Verify the coinbase descendant was also marked as abandoned
9192
assert_equal(wallet0.gettransaction(descendant_tx_id)['details'][0]['abandoned'], True)
9293

94+
def test_reorg_handling_during_unclean_shutdown(self):
95+
self.log.info("Test that wallet doesn't crash due to a duplicate block disconnection event after an unclean shutdown")
96+
node = self.nodes[0]
97+
# Receive coinbase reward on a new wallet
98+
node.createwallet(wallet_name="reorg_crash", load_on_startup=True)
99+
wallet = node.get_wallet_rpc("reorg_crash")
100+
self.generatetoaddress(node, 1, wallet.getnewaddress(), sync_fun=self.no_op)
101+
102+
# Restart to ensure node and wallet are flushed
103+
self.restart_node(0)
104+
wallet = node.get_wallet_rpc("reorg_crash")
105+
assert_greater_than(wallet.getwalletinfo()['immature_balance'], 0)
106+
107+
# Disconnect tip and sync wallet state
108+
tip = wallet.getbestblockhash()
109+
wallet.invalidateblock(tip)
110+
wallet.syncwithvalidationinterfacequeue()
111+
112+
# Tip was disconnected, ensure coinbase has been abandoned
113+
assert_equal(wallet.getwalletinfo()['immature_balance'], 0)
114+
coinbase_tx_id = wallet.getblock(tip, verbose=1)["tx"][0]
115+
assert_equal(wallet.gettransaction(coinbase_tx_id)['details'][0]['abandoned'], True)
116+
117+
# Abort process abruptly to mimic an unclean shutdown (no chain state flush to disk)
118+
node.process.kill()
119+
120+
# Restart the node and confirm that it has not persisted the last chain state changes to disk
121+
self.start_node(0)
122+
assert_equal(node.getbestblockhash(), tip)
123+
124+
# Due to an existing bug, the wallet incorrectly keeps the transaction in an abandoned state, even though that's
125+
# no longer the case (after the unclean shutdown, the node's chain returned to the pre-invalidation tip).
126+
# This issue blocks any future spending and results in an incorrect balance display.
127+
wallet = node.get_wallet_rpc("reorg_crash")
128+
assert_equal(wallet.getwalletinfo()['immature_balance'], 0) # FIXME: #31824.
129+
130+
# Previously, a bug caused the node to crash if two block disconnection events occurred consecutively.
131+
# Ensure this is no longer the case by simulating a new reorg.
132+
node.invalidateblock(tip)
133+
assert(node.getbestblockhash() != tip)
134+
# Ensure wallet state is consistent now
135+
assert_equal(wallet.gettransaction(coinbase_tx_id)['details'][0]['abandoned'], True)
136+
assert_equal(wallet.getwalletinfo()['immature_balance'], 0)
137+
138+
# And finally, verify the state if the block ends up being into the best chain again
139+
node.reconsiderblock(tip)
140+
assert_equal(wallet.gettransaction(coinbase_tx_id)['details'][0]['abandoned'], False)
141+
assert_greater_than(wallet.getwalletinfo()['immature_balance'], 0)
93142

94143
def run_test(self):
95144
# Send a tx from which to conflict outputs later
@@ -163,6 +212,9 @@ def run_test(self):
163212
# Verify we mark coinbase txs, and their descendants, as abandoned during startup
164213
self.test_coinbase_automatic_abandon_during_startup()
165214

215+
# Verify reorg behavior during an unclean shutdown
216+
self.test_reorg_handling_during_unclean_shutdown()
217+
166218

167219
if __name__ == '__main__':
168220
ReorgsRestoreTest(__file__).main()

0 commit comments

Comments
 (0)