Skip to content

Commit c000fc9

Browse files
vincenzopalazzorustyrussell
authored andcommitted
tests: add regression tests for withdraw returning unsigned tx
Adds two tests to reproduce issue #8701 where the withdraw command returns an unsigned raw transaction in the 'tx' response field: 1. test_withdraw_returns_signed_tx: verifies that withdraw's 'tx' field contains witness data for all inputs (basic wallet UTXOs). 2. test_withdraw_close_output_signed: verifies signing works when withdrawing funds that include channel close outputs (anchor/P2WSH with CSV locks), which was the exact scenario in the reported issue. The root cause is that psbt_txid() uses WALLY_PSBT_EXTRACT_NON_FINAL which strips signatures/witnesses, and the withdraw response returns this unsigned tx instead of the finalized one. Changelog-None Signed-off-by: Vincenzo Palazzo <vincenzopalazzo@member.fsf.org> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent fcd6671 commit c000fc9

1 file changed

Lines changed: 75 additions & 0 deletions

File tree

tests/test_wallet.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2072,6 +2072,81 @@ def test_fundchannel_listtransaction(node_factory, bitcoind):
20722072
assert tx['blockheight'] == 0
20732073

20742074

2075+
@pytest.mark.xfail(strict=True)
2076+
@unittest.skipIf(TEST_NETWORK != 'regtest', "Uss p2tr")
2077+
def test_withdraw_returns_signed_tx(node_factory, bitcoind):
2078+
"""
2079+
Test that withdraw returns a fully signed transaction in the 'tx' field.
2080+
2081+
Regression test for https://github.com/ElementsProject/lightning/issues/8701
2082+
where withdraw returned an unsigned transaction (empty witnesses) because
2083+
psbt_txid() used WALLY_PSBT_EXTRACT_NON_FINAL to extract the tx.
2084+
"""
2085+
l1 = node_factory.get_node(random_hsm=True)
2086+
2087+
# Fund the wallet with a few UTXOs
2088+
addr = l1.rpc.newaddr('p2tr')['p2tr']
2089+
for i in range(3):
2090+
l1.bitcoin.rpc.sendtoaddress(addr, 0.01)
2091+
bitcoind.generate_block(1)
2092+
wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == 3)
2093+
2094+
waddr = l1.bitcoin.rpc.getnewaddress()
2095+
out = l1.rpc.withdraw(waddr, 'all')
2096+
2097+
# The tx field must be a fully signed transaction
2098+
decoded = bitcoind.rpc.decoderawtransaction(out['tx'])
2099+
2100+
# Every segwit input must have witness data (txinwitness)
2101+
for i, vin in enumerate(decoded['vin']):
2102+
assert 'txinwitness' in vin, \
2103+
f"Input {i} has no witness data - tx is unsigned! (issue #8701)"
2104+
assert len(vin['txinwitness']) > 0, \
2105+
f"Input {i} has empty witness stack"
2106+
2107+
# The returned tx must be directly broadcastable (already sent by withdraw,
2108+
# but verify it could be re-sent by checking it was accepted)
2109+
assert decoded['txid'] == out['txid']
2110+
2111+
2112+
@pytest.mark.xfail(strict=True)
2113+
@unittest.skipIf(TEST_NETWORK != 'regtest', "Uss p2tr")
2114+
def test_withdraw_close_output_signed(node_factory, bitcoind):
2115+
"""
2116+
Test that withdraw correctly signs close outputs (anchor/P2WSH).
2117+
2118+
Regression test for https://github.com/ElementsProject/lightning/issues/8701
2119+
The original issue involved spending channel close outputs (with
2120+
option_anchors CSV=1) alongside regular wallet UTXOs.
2121+
"""
2122+
l1, l2 = node_factory.line_graph(2, fundchannel=True, wait_for_announce=True)
2123+
2124+
# Close the channel so l1 gets a close output
2125+
l1.rpc.close(l2.info['id'])
2126+
bitcoind.generate_block(1, wait_for_mempool=1)
2127+
2128+
# Wait for CSV lock (1 block for anchors) and the close output to mature
2129+
bitcoind.generate_block(100)
2130+
sync_blockheight(bitcoind, [l1])
2131+
2132+
wait_for(lambda: all(o['status'] == 'confirmed' for o in l1.rpc.listfunds()['outputs']))
2133+
2134+
# Withdraw all funds - this spends both regular and close outputs
2135+
waddr = l1.bitcoin.rpc.getnewaddress()
2136+
out = l1.rpc.withdraw(waddr, 'all')
2137+
2138+
decoded = bitcoind.rpc.decoderawtransaction(out['tx'])
2139+
2140+
# Every input must have witness data
2141+
for i, vin in enumerate(decoded['vin']):
2142+
assert 'txinwitness' in vin, \
2143+
f"Input {i} has no witness data - tx is unsigned! (issue #8701)"
2144+
assert len(vin['txinwitness']) > 0, \
2145+
f"Input {i} has empty witness stack"
2146+
2147+
assert decoded['txid'] == out['txid']
2148+
2149+
20752150
def test_withdraw_nlocktime(node_factory):
20762151
"""
20772152
Test that we don't set the nLockTime to 0 for withdrawal and

0 commit comments

Comments
 (0)