Skip to content

Commit 782701c

Browse files
committed
test: Test loading wallets with conflicts without a chain
Loading a wallet with conflicts without a chain (e.g. wallet tool and migration) would previously result in an assertion due to -1 being both a valid number of conflict confirmations, and the indicator that that member has not been set yet.
1 parent 4660fc8 commit 782701c

File tree

2 files changed

+101
-0
lines changed

2 files changed

+101
-0
lines changed

test/functional/tool_wallet.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

411468
if __name__ == '__main__':
412469
ToolWalletTest().main()

test/functional/wallet_migration.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,49 @@ def send_to_script(script, amount):
727727
self.nodes[0].loadwallet(info_migration["watchonly_name"])
728728
assert_equal(wallet_wo.getbalances()['mine']['trusted'], 5)
729729

730+
def test_conflict_txs(self):
731+
self.log.info("Test migration when wallet contains conflicting transactions")
732+
def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
733+
734+
wallet = self.create_legacy_wallet("conflicts")
735+
def_wallet.sendtoaddress(wallet.getnewaddress(), 10)
736+
self.generate(self.nodes[0], 1)
737+
738+
# parent tx
739+
parent_txid = wallet.sendtoaddress(wallet.getnewaddress(), 9)
740+
parent_txid_bytes = bytes.fromhex(parent_txid)[::-1]
741+
conflict_utxo = wallet.gettransaction(txid=parent_txid, verbose=True)["decoded"]["vin"][0]
742+
743+
# The specific assertion in MarkConflicted being tested requires that the parent tx is already loaded
744+
# by the time the child tx is loaded. Since transactions end up being loaded in txid order due to how both
745+
# and sqlite store things, we can just grind the child tx until it has a txid that is greater than the parent's.
746+
locktime = 500000000 # Use locktime as nonce, starting at unix timestamp minimum
747+
addr = wallet.getnewaddress()
748+
while True:
749+
child_send_res = wallet.send(outputs=[{addr: 8}], add_to_wallet=False, locktime=locktime)
750+
child_txid = child_send_res["txid"]
751+
child_txid_bytes = bytes.fromhex(child_txid)[::-1]
752+
if (child_txid_bytes > parent_txid_bytes):
753+
wallet.sendrawtransaction(child_send_res["hex"])
754+
break
755+
locktime += 1
756+
757+
# conflict with parent
758+
conflict_unsigned = self.nodes[0].createrawtransaction(inputs=[conflict_utxo], outputs=[{wallet.getnewaddress(): 9.9999}])
759+
conflict_signed = wallet.signrawtransactionwithwallet(conflict_unsigned)["hex"]
760+
conflict_txid = self.nodes[0].sendrawtransaction(conflict_signed)
761+
self.generate(self.nodes[0], 1)
762+
assert_equal(wallet.gettransaction(txid=parent_txid)["confirmations"], -1)
763+
assert_equal(wallet.gettransaction(txid=child_txid)["confirmations"], -1)
764+
assert_equal(wallet.gettransaction(txid=conflict_txid)["confirmations"], 1)
765+
766+
wallet.migratewallet()
767+
assert_equal(wallet.gettransaction(txid=parent_txid)["confirmations"], -1)
768+
assert_equal(wallet.gettransaction(txid=child_txid)["confirmations"], -1)
769+
assert_equal(wallet.gettransaction(txid=conflict_txid)["confirmations"], 1)
770+
771+
wallet.unloadwallet()
772+
730773
def run_test(self):
731774
self.generate(self.nodes[0], 101)
732775

@@ -743,6 +786,7 @@ def run_test(self):
743786
self.test_direct_file()
744787
self.test_addressbook()
745788
self.test_migrate_raw_p2sh()
789+
self.test_conflict_txs()
746790

747791
if __name__ == '__main__':
748792
WalletMigrationTest().main()

0 commit comments

Comments
 (0)