@@ -394,6 +394,62 @@ def test_dump_createfromdump(self):
394394 self .assert_raises_tool_error ('Error: Checksum is not the correct size' , '-wallet=badload' , '-dumpfile={}' .format (bad_sum_wallet_dump ), 'createfromdump' )
395395 assert not (self .nodes [0 ].wallets_path / "badload" ).is_dir ()
396396
397+ def test_chainless_conflicts (self ):
398+ self .log .info ("Test wallet tool when wallet contains conflicting transactions" )
399+ self .restart_node (0 )
400+ self .generate (self .nodes [0 ], 101 )
401+
402+ def_wallet = self .nodes [0 ].get_wallet_rpc (self .default_wallet_name )
403+
404+ self .nodes [0 ].createwallet ("conflicts" )
405+ wallet = self .nodes [0 ].get_wallet_rpc ("conflicts" )
406+ def_wallet .sendtoaddress (wallet .getnewaddress (), 10 )
407+ self .generate (self .nodes [0 ], 1 )
408+
409+ # parent tx
410+ parent_txid = wallet .sendtoaddress (wallet .getnewaddress (), 9 )
411+ parent_txid_bytes = bytes .fromhex (parent_txid )[::- 1 ]
412+ conflict_utxo = wallet .gettransaction (txid = parent_txid , verbose = True )["decoded" ]["vin" ][0 ]
413+
414+ # The specific assertion in MarkConflicted being tested requires that the parent tx is already loaded
415+ # by the time the child tx is loaded. Since transactions end up being loaded in txid order due to how both
416+ # and sqlite store things, we can just grind the child tx until it has a txid that is greater than the parent's.
417+ locktime = 500000000 # Use locktime as nonce, starting at unix timestamp minimum
418+ addr = wallet .getnewaddress ()
419+ while True :
420+ child_send_res = wallet .send (outputs = [{addr : 8 }], add_to_wallet = False , locktime = locktime )
421+ child_txid = child_send_res ["txid" ]
422+ child_txid_bytes = bytes .fromhex (child_txid )[::- 1 ]
423+ if (child_txid_bytes > parent_txid_bytes ):
424+ wallet .sendrawtransaction (child_send_res ["hex" ])
425+ break
426+ locktime += 1
427+
428+ # conflict with parent
429+ conflict_unsigned = self .nodes [0 ].createrawtransaction (inputs = [conflict_utxo ], outputs = [{wallet .getnewaddress (): 9.9999 }])
430+ conflict_signed = wallet .signrawtransactionwithwallet (conflict_unsigned )["hex" ]
431+ conflict_txid = self .nodes [0 ].sendrawtransaction (conflict_signed )
432+ self .generate (self .nodes [0 ], 1 )
433+ assert_equal (wallet .gettransaction (txid = parent_txid )["confirmations" ], - 1 )
434+ assert_equal (wallet .gettransaction (txid = child_txid )["confirmations" ], - 1 )
435+ assert_equal (wallet .gettransaction (txid = conflict_txid )["confirmations" ], 1 )
436+
437+ self .stop_node (0 )
438+
439+ # Wallet tool should successfully give info for this wallet
440+ expected_output = textwrap .dedent (f'''\
441+ Wallet info
442+ ===========
443+ Name: conflicts
444+ Format: { "sqlite" if self .options .descriptors else "bdb" }
445+ Descriptors: { "yes" if self .options .descriptors else "no" }
446+ Encrypted: no
447+ HD (hd seed available): yes
448+ Keypool Size: { "8" if self .options .descriptors else "1" }
449+ Transactions: 4
450+ Address Book: 4
451+ ''' )
452+ self .assert_tool_output (expected_output , "-wallet=conflicts" , "info" )
397453
398454 def run_test (self ):
399455 self .wallet_path = os .path .join (self .nodes [0 ].wallets_path , self .default_wallet_name , self .wallet_data_filename )
@@ -407,6 +463,7 @@ def run_test(self):
407463 # Salvage is a legacy wallet only thing
408464 self .test_salvage ()
409465 self .test_dump_createfromdump ()
466+ self .test_chainless_conflicts ()
410467
411468if __name__ == '__main__' :
412469 ToolWalletTest ().main ()
0 commit comments