@@ -14,7 +14,15 @@ def __init__(self):
1414 self .setup_clean_chain = True
1515 self .num_nodes = 4
1616
17- def run_test (self ):
17+ def run_test (self ):
18+ self .nodes [2 ].generate (101 )
19+ self .sync_all ()
20+
21+ self .test_reorg ()
22+ self .test_double_spend ()
23+ self .test_double_send ()
24+
25+ def test_reorg (self ):
1826 '''
1927 `listsinceblock` did not behave correctly when handed a block that was
2028 no longer in the main chain:
@@ -43,14 +51,6 @@ def run_test (self):
4351 This test only checks that [tx0] is present.
4452 '''
4553
46- self .nodes [2 ].generate (101 )
47- self .sync_all ()
48-
49- assert_equal (self .nodes [0 ].getbalance (), 0 )
50- assert_equal (self .nodes [1 ].getbalance (), 0 )
51- assert_equal (self .nodes [2 ].getbalance (), 50 )
52- assert_equal (self .nodes [3 ].getbalance (), 0 )
53-
5454 # Split network into two
5555 self .split_network ()
5656
@@ -73,7 +73,177 @@ def run_test (self):
7373 if tx ['txid' ] == senttx :
7474 found = True
7575 break
76- assert_equal (found , True )
76+ assert found
77+
78+ def test_double_spend (self ):
79+ '''
80+ This tests the case where the same UTXO is spent twice on two separate
81+ blocks as part of a reorg.
82+
83+ ab0
84+ / \
85+ aa1 [tx1] bb1 [tx2]
86+ | |
87+ aa2 bb2
88+ | |
89+ aa3 bb3
90+ |
91+ bb4
92+
93+ Problematic case:
94+
95+ 1. User 1 receives BTC in tx1 from utxo1 in block aa1.
96+ 2. User 2 receives BTC in tx2 from utxo1 (same) in block bb1
97+ 3. User 1 sees 2 confirmations at block aa3.
98+ 4. Reorg into bb chain.
99+ 5. User 1 asks `listsinceblock aa3` and does not see that tx1 is now
100+ invalidated.
101+
102+ Currently the solution to this is to detect that a reorg'd block is
103+ asked for in listsinceblock, and to iterate back over existing blocks up
104+ until the fork point, and to include all transactions that relate to the
105+ node wallet.
106+ '''
107+
108+ self .sync_all ()
109+
110+ # Split network into two
111+ self .split_network ()
112+
113+ # share utxo between nodes[1] and nodes[2]
114+ utxos = self .nodes [2 ].listunspent ()
115+ utxo = utxos [0 ]
116+ privkey = self .nodes [2 ].dumpprivkey (utxo ['address' ])
117+ self .nodes [1 ].importprivkey (privkey )
118+
119+ # send from nodes[1] using utxo to nodes[0]
120+ change = '%.8f' % (float (utxo ['amount' ]) - 1.0003 )
121+ recipientDict = {
122+ self .nodes [0 ].getnewaddress (): 1 ,
123+ self .nodes [1 ].getnewaddress (): change ,
124+ }
125+ utxoDicts = [{
126+ 'txid' : utxo ['txid' ],
127+ 'vout' : utxo ['vout' ],
128+ }]
129+ txid1 = self .nodes [1 ].sendrawtransaction (
130+ self .nodes [1 ].signrawtransaction (
131+ self .nodes [1 ].createrawtransaction (utxoDicts , recipientDict ))['hex' ])
132+
133+ # send from nodes[2] using utxo to nodes[3]
134+ recipientDict2 = {
135+ self .nodes [3 ].getnewaddress (): 1 ,
136+ self .nodes [2 ].getnewaddress (): change ,
137+ }
138+ self .nodes [2 ].sendrawtransaction (
139+ self .nodes [2 ].signrawtransaction (
140+ self .nodes [2 ].createrawtransaction (utxoDicts , recipientDict2 ))['hex' ])
141+
142+ # generate on both sides
143+ lastblockhash = self .nodes [1 ].generate (3 )[2 ]
144+ self .nodes [2 ].generate (4 )
145+
146+ self .join_network ()
147+
148+ self .sync_all ()
149+
150+ # gettransaction should work for txid1
151+ assert self .nodes [0 ].gettransaction (txid1 )['txid' ] == txid1 , "gettransaction failed to find txid1"
152+
153+ # listsinceblock(lastblockhash) should now include txid1, as seen from nodes[0]
154+ lsbres = self .nodes [0 ].listsinceblock (lastblockhash )
155+ assert any (tx ['txid' ] == txid1 for tx in lsbres ['removed' ])
156+
157+ # but it should not include 'removed' if include_removed=false
158+ lsbres2 = self .nodes [0 ].listsinceblock (blockhash = lastblockhash , include_removed = False )
159+ assert 'removed' not in lsbres2
160+
161+ def test_double_send (self ):
162+ '''
163+ This tests the case where the same transaction is submitted twice on two
164+ separate blocks as part of a reorg. The former will vanish and the
165+ latter will appear as the true transaction (with confirmations dropping
166+ as a result).
167+
168+ ab0
169+ / \
170+ aa1 [tx1] bb1
171+ | |
172+ aa2 bb2
173+ | |
174+ aa3 bb3 [tx1]
175+ |
176+ bb4
177+
178+ Asserted:
179+
180+ 1. tx1 is listed in listsinceblock.
181+ 2. It is included in 'removed' as it was removed, even though it is now
182+ present in a different block.
183+ 3. It is listed with a confirmations count of 2 (bb3, bb4), not
184+ 3 (aa1, aa2, aa3).
185+ '''
186+
187+ self .sync_all ()
188+
189+ # Split network into two
190+ self .split_network ()
191+
192+ # create and sign a transaction
193+ utxos = self .nodes [2 ].listunspent ()
194+ utxo = utxos [0 ]
195+ change = '%.8f' % (float (utxo ['amount' ]) - 1.0003 )
196+ recipientDict = {
197+ self .nodes [0 ].getnewaddress (): 1 ,
198+ self .nodes [2 ].getnewaddress (): change ,
199+ }
200+ utxoDicts = [{
201+ 'txid' : utxo ['txid' ],
202+ 'vout' : utxo ['vout' ],
203+ }]
204+ signedtxres = self .nodes [2 ].signrawtransaction (
205+ self .nodes [2 ].createrawtransaction (utxoDicts , recipientDict ))
206+ assert signedtxres ['complete' ]
207+
208+ signedtx = signedtxres ['hex' ]
209+
210+ # send from nodes[1]; this will end up in aa1
211+ txid1 = self .nodes [1 ].sendrawtransaction (signedtx )
212+
213+ # generate bb1-bb2 on right side
214+ self .nodes [2 ].generate (2 )
215+
216+ # send from nodes[2]; this will end up in bb3
217+ txid2 = self .nodes [2 ].sendrawtransaction (signedtx )
218+
219+ assert_equal (txid1 , txid2 )
220+
221+ # generate on both sides
222+ lastblockhash = self .nodes [1 ].generate (3 )[2 ]
223+ self .nodes [2 ].generate (2 )
224+
225+ self .join_network ()
226+
227+ self .sync_all ()
228+
229+ # gettransaction should work for txid1
230+ self .nodes [0 ].gettransaction (txid1 )
231+
232+ # listsinceblock(lastblockhash) should now include txid1 in transactions
233+ # as well as in removed
234+ lsbres = self .nodes [0 ].listsinceblock (lastblockhash )
235+ assert any (tx ['txid' ] == txid1 for tx in lsbres ['transactions' ])
236+ assert any (tx ['txid' ] == txid1 for tx in lsbres ['removed' ])
237+
238+ # find transaction and ensure confirmations is valid
239+ for tx in lsbres ['transactions' ]:
240+ if tx ['txid' ] == txid1 :
241+ assert_equal (tx ['confirmations' ], 2 )
242+
243+ # the same check for the removed array; confirmations should STILL be 2
244+ for tx in lsbres ['removed' ]:
245+ if tx ['txid' ] == txid1 :
246+ assert_equal (tx ['confirmations' ], 2 )
77247
78248if __name__ == '__main__' :
79249 ListSinceBlockTest ().main ()
0 commit comments