Skip to content

Commit 9e51a48

Browse files
Antoine Riardfurszy
authored andcommitted
Add a test wallet_reorgsrestore
Test we change tx status at loading in case of reorgs while wallet was shutdown.
1 parent 370c200 commit 9e51a48

File tree

2 files changed

+108
-0
lines changed

2 files changed

+108
-0
lines changed

test/functional/test_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
# Longest test should go first, to favor running tests in parallel
5959
'wallet_basic.py', # ~ 498 sec
6060
'wallet_backup.py', # ~ 477 sec
61+
'wallet_reorgsrestore.py', # ~ 391 sec
6162
'mempool_persist.py', # ~ 417 sec
6263

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

0 commit comments

Comments
 (0)