19
19
from test_framework .test_framework import BitcoinTestFramework
20
20
from test_framework .util import (
21
21
assert_equal ,
22
+ assert_raises_rpc_error
22
23
)
23
24
24
25
class ReorgsRestoreTest (BitcoinTestFramework ):
@@ -31,6 +32,65 @@ def set_test_params(self):
31
32
def skip_test_if_missing_module (self ):
32
33
self .skip_if_no_wallet ()
33
34
35
+ def test_coinbase_automatic_abandon_during_startup (self ):
36
+ ##########################################################################################################
37
+ # Verify the wallet marks coinbase transactions, and their descendants, as abandoned during startup when #
38
+ # the block is no longer part of the best chain. #
39
+ ##########################################################################################################
40
+ self .log .info ("Test automatic coinbase abandonment during startup" )
41
+ # Test setup: Sync nodes for the coming test, ensuring both are at the same block, then disconnect them to
42
+ # generate two competing chains. After disconnection, verify no other peer connection exists.
43
+ self .connect_nodes (1 , 0 )
44
+ self .sync_blocks (self .nodes [:2 ])
45
+ self .disconnect_nodes (1 , 0 )
46
+ assert all (len (node .getpeerinfo ()) == 0 for node in self .nodes [:2 ])
47
+
48
+ # Create a new block in node0, coinbase going to wallet0
49
+ self .nodes [0 ].createwallet (wallet_name = "w0" , load_on_startup = True )
50
+ wallet0 = self .nodes [0 ].get_wallet_rpc ("w0" )
51
+ self .generatetoaddress (self .nodes [0 ], 1 , wallet0 .getnewaddress (), sync_fun = self .no_op )
52
+ node0_coinbase_tx_hash = wallet0 .getblock (wallet0 .getbestblockhash (), verbose = 1 )['tx' ][0 ]
53
+
54
+ # Mine 100 blocks on top to mature the coinbase and create a descendant
55
+ self .generate (self .nodes [0 ], 101 , sync_fun = self .no_op )
56
+ # Make descendant, send-to-self
57
+ descendant_tx_id = wallet0 .sendtoaddress (wallet0 .getnewaddress (), 1 )
58
+
59
+ # Verify balance
60
+ wallet0 .syncwithvalidationinterfacequeue ()
61
+ assert (wallet0 .getbalances ()['mine' ]['trusted' ] > 0 )
62
+
63
+ # Now create a fork in node1. This will be used to replace node0's chain later.
64
+ self .nodes [1 ].createwallet (wallet_name = "w1" , load_on_startup = True )
65
+ wallet1 = self .nodes [1 ].get_wallet_rpc ("w1" )
66
+ self .generatetoaddress (self .nodes [1 ], 1 , wallet1 .getnewaddress (), sync_fun = self .no_op )
67
+ wallet1 .syncwithvalidationinterfacequeue ()
68
+
69
+ # Verify both nodes are on a different chain
70
+ block0_best_hash , block1_best_hash = wallet0 .getbestblockhash (), wallet1 .getbestblockhash ()
71
+ assert (block0_best_hash != block1_best_hash )
72
+
73
+ # Stop both nodes and replace node0 chain entirely for the node1 chain
74
+ self .stop_nodes ()
75
+ for path in ["chainstate" , "blocks" ]:
76
+ shutil .rmtree (self .nodes [0 ].chain_path / path )
77
+ shutil .copytree (self .nodes [1 ].chain_path / path , self .nodes [0 ].chain_path / path )
78
+
79
+ # Start node0 and verify that now it has node1 chain and no info about its previous best block
80
+ self .start_node (0 )
81
+ wallet0 = self .nodes [0 ].get_wallet_rpc ("w0" )
82
+ assert_equal (wallet0 .getbestblockhash (), block1_best_hash )
83
+ assert_raises_rpc_error (- 5 , "Block not found" , wallet0 .getblock , block0_best_hash )
84
+
85
+ # Verify the coinbase tx was marked as abandoned and balance correctly computed
86
+ tx_info = wallet0 .gettransaction (node0_coinbase_tx_hash )['details' ][0 ]
87
+ assert_equal (tx_info ['abandoned' ], True )
88
+ assert_equal (tx_info ['category' ], 'orphan' )
89
+ assert (wallet0 .getbalances ()['mine' ]['trusted' ] == 0 )
90
+ # Verify the coinbase descendant was also marked as abandoned
91
+ assert_equal (wallet0 .gettransaction (descendant_tx_id )['details' ][0 ]['abandoned' ], True )
92
+
93
+
34
94
def run_test (self ):
35
95
# Send a tx from which to conflict outputs later
36
96
txid_conflict_from = self .nodes [0 ].sendtoaddress (self .nodes [0 ].getnewaddress (), Decimal ("10" ))
@@ -100,5 +160,9 @@ def run_test(self):
100
160
assert_equal (conflicted_after_reorg ["confirmations" ], 1 )
101
161
assert conflicting ["blockhash" ] != conflicted_after_reorg ["blockhash" ]
102
162
163
+ # Verify we mark coinbase txs, and their descendants, as abandoned during startup
164
+ self .test_coinbase_automatic_abandon_during_startup ()
165
+
166
+
103
167
if __name__ == '__main__' :
104
168
ReorgsRestoreTest (__file__ ).main ()
0 commit comments