Skip to content

Commit 089d3af

Browse files
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 376468f commit 089d3af

1 file changed

Lines changed: 71 additions & 0 deletions

File tree

tests/test_wallet.py

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

20742074

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

0 commit comments

Comments
 (0)