Skip to content

Commit 442a87c

Browse files
author
Antoine Riard
committed
Add a test wallet_reorgsrestore
Test we change tx status at loading in case of reorgs while wallet was shutdown.
1 parent 40ede99 commit 442a87c

File tree

2 files changed

+106
-0
lines changed

2 files changed

+106
-0
lines changed

test/functional/test_runner.py

+1
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@
131131
'wallet_createwallet.py --usecli',
132132
'wallet_watchonly.py',
133133
'wallet_watchonly.py --usecli',
134+
'wallet_reorgsrestore.py',
134135
'interface_http.py',
135136
'interface_rpc.py',
136137
'rpc_psbt.py',
+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2019 The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
6+
"""Test tx status in case of reorgs while wallet being shutdown.
7+
8+
Wallet txn status rely on block connection/disconnection for its
9+
accuracy. In case of reorgs happening while wallet being shutdown
10+
block updates are not going to be received. At wallet loading, we
11+
check against chain if confirmed txn are still in chain and change
12+
their status if block in which they have been included has been
13+
disconnected.
14+
"""
15+
16+
from decimal import Decimal
17+
import os
18+
import shutil
19+
20+
from test_framework.test_framework import BitcoinTestFramework
21+
from test_framework.util import (
22+
assert_equal,
23+
connect_nodes,
24+
disconnect_nodes,
25+
)
26+
27+
class ReorgsRestoreTest(BitcoinTestFramework):
28+
def set_test_params(self):
29+
self.num_nodes = 3
30+
31+
def skip_test_if_missing_module(self):
32+
self.skip_if_no_wallet()
33+
34+
def run_test(self):
35+
# Send a tx from which to conflict outputs later
36+
txid_conflict_from = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10"))
37+
self.nodes[0].generate(1)
38+
self.sync_blocks()
39+
40+
# Disconnect node1 from others to reorg its chain later
41+
disconnect_nodes(self.nodes[0], 1)
42+
disconnect_nodes(self.nodes[1], 2)
43+
connect_nodes(self.nodes[0], 2)
44+
45+
# Send a tx to be unconfirmed later
46+
txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10"))
47+
tx = self.nodes[0].gettransaction(txid)
48+
self.nodes[0].generate(4)
49+
tx_before_reorg = self.nodes[0].gettransaction(txid)
50+
assert_equal(tx_before_reorg["confirmations"], 4)
51+
52+
# Disconnect node0 from node2 to broadcast a conflict on their respective chains
53+
disconnect_nodes(self.nodes[0], 2)
54+
nA = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction(txid_conflict_from)["details"] if tx_out["amount"] == Decimal("10"))
55+
inputs = []
56+
inputs.append({"txid": txid_conflict_from, "vout": nA})
57+
outputs_1 = {}
58+
outputs_2 = {}
59+
60+
# Create a conflicted tx broadcast on node0 chain and conflicting tx broadcast on node1 chain. Both spend from txid_conflict_from
61+
outputs_1[self.nodes[0].getnewaddress()] = Decimal("9.99998")
62+
outputs_2[self.nodes[0].getnewaddress()] = Decimal("9.99998")
63+
conflicted = self.nodes[0].signrawtransactionwithwallet(self.nodes[0].createrawtransaction(inputs, outputs_1))
64+
conflicting = self.nodes[0].signrawtransactionwithwallet(self.nodes[0].createrawtransaction(inputs, outputs_2))
65+
66+
conflicted_txid = self.nodes[0].sendrawtransaction(conflicted["hex"])
67+
self.nodes[0].generate(1)
68+
conflicting_txid = self.nodes[2].sendrawtransaction(conflicting["hex"])
69+
self.nodes[2].generate(9)
70+
71+
# Reconnect node0 and node2 and check that conflicted_txid is effectively conflicted
72+
connect_nodes(self.nodes[0], 2)
73+
self.sync_blocks([self.nodes[0], self.nodes[2]])
74+
conflicted = self.nodes[0].gettransaction(conflicted_txid)
75+
conflicting = self.nodes[0].gettransaction(conflicting_txid)
76+
assert_equal(conflicted["confirmations"], -9)
77+
assert_equal(conflicted["walletconflicts"][0], conflicting["txid"])
78+
79+
# Node0 wallet is shutdown
80+
self.stop_node(0)
81+
self.start_node(0)
82+
83+
# The block chain re-orgs and the tx is included in a different block
84+
self.nodes[1].generate(9)
85+
self.nodes[1].sendrawtransaction(tx["hex"])
86+
self.nodes[1].generate(1)
87+
self.nodes[1].sendrawtransaction(conflicted["hex"])
88+
self.nodes[1].generate(1)
89+
90+
# Node0 wallet file is loaded on longest sync'ed node1
91+
self.stop_node(1)
92+
self.nodes[0].backupwallet(os.path.join(self.nodes[0].datadir, 'wallet.bak'))
93+
shutil.copyfile(os.path.join(self.nodes[0].datadir, 'wallet.bak'), os.path.join(self.nodes[1].datadir, 'regtest', 'wallet.dat'))
94+
self.start_node(1)
95+
tx_after_reorg = self.nodes[1].gettransaction(txid)
96+
# Check that normal confirmed tx is confirmed again but with different blockhash
97+
assert_equal(tx_after_reorg["confirmations"], 2)
98+
assert(tx_before_reorg["blockhash"] != tx_after_reorg["blockhash"])
99+
conflicted_after_reorg = self.nodes[1].gettransaction(conflicted_txid)
100+
# Check that conflicted tx is confirmed again with blockhash different than previously conflicting tx
101+
assert_equal(conflicted_after_reorg["confirmations"], 1)
102+
assert(conflicting["blockhash"] != conflicted_after_reorg["blockhash"])
103+
104+
if __name__ == '__main__':
105+
ReorgsRestoreTest().main()

0 commit comments

Comments
 (0)