|
7 | 7 | from pyln.testing.utils import FUNDAMOUNT |
8 | 8 |
|
9 | 9 | from pathlib import Path |
| 10 | +import os |
10 | 11 | import pytest |
11 | 12 | import re |
| 13 | +import threading |
12 | 14 | import unittest |
13 | 15 | import time |
14 | 16 |
|
@@ -172,6 +174,59 @@ def test_v2_open_sigs_reconnect_2(node_factory, bitcoind): |
172 | 174 | l2.daemon.wait_for_log(r'to CHANNELD_NORMAL') |
173 | 175 |
|
174 | 176 |
|
| 177 | +@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') |
| 178 | +@unittest.skipIf(os.getenv('TEST_DB_PROVIDER', 'sqlite3') != 'sqlite3', "sqlite3-specific DB manipulation") |
| 179 | +@pytest.mark.openchannel('v2') |
| 180 | +def test_v2_open_reconnect_next_funding_mismatch(node_factory, bitcoind): |
| 181 | + """Both nodes set next_funding on reconnect but disagree on txid: error sent, channel fails.""" |
| 182 | + # l2 always sends tx_signatures first. Disconnecting l2 just before it |
| 183 | + # sends tx_signatures means neither node ever receives remote tx_sigs, so |
| 184 | + # both end up in DUALOPEND_OPEN_COMMITTED with remote_funding_sigs_rcvd=False |
| 185 | + # and will set next_funding in channel_reestablish. |
| 186 | + broken = r'dualopend daemon died before signed PSBT returned|Owning subdaemon dualopend died' |
| 187 | + l1, l2 = node_factory.get_nodes(2, opts=[ |
| 188 | + {'may_reconnect': True, 'dev-no-reconnect': None, 'broken_log': broken}, |
| 189 | + {'disconnect': ['-WIRE_TX_SIGNATURES'], 'may_reconnect': True, |
| 190 | + 'dev-no-reconnect': None, 'broken_log': broken} |
| 191 | + ]) |
| 192 | + |
| 193 | + l1.rpc.connect(l2.info['id'], 'localhost', l2.port) |
| 194 | + amount = 2**24 |
| 195 | + bitcoind.rpc.sendtoaddress(l1.rpc.newaddr()['p2tr'], amount / 10**8 + 0.01) |
| 196 | + bitcoind.generate_block(1) |
| 197 | + wait_for(lambda: len(l1.rpc.listfunds()['outputs']) > 0) |
| 198 | + |
| 199 | + # -WIRE_TX_SIGNATURES causes fundchannel to block (it waits for reconnect |
| 200 | + # that will never come due to dev-no-reconnect); run it in a daemon thread |
| 201 | + # so the test can proceed. l2.stop() below unblocks it via peer death. |
| 202 | + def _fund(): |
| 203 | + try: |
| 204 | + l1.rpc.fundchannel(l2.info['id'], 100000) |
| 205 | + except Exception: |
| 206 | + pass |
| 207 | + |
| 208 | + threading.Thread(target=_fund, daemon=True).start() |
| 209 | + |
| 210 | + # Both have exchanged commitment_signed (inflight in DB) but no tx_sigs yet. |
| 211 | + # Use any() because the channel record may not exist yet when the thread starts. |
| 212 | + wait_for(lambda: any(c['state'] == 'DUALOPEND_OPEN_COMMITTED' |
| 213 | + for c in l1.rpc.listpeerchannels()['channels'])) |
| 214 | + wait_for(lambda: any(c['state'] == 'DUALOPEND_OPEN_COMMITTED' |
| 215 | + for c in l2.rpc.listpeerchannels()['channels'])) |
| 216 | + |
| 217 | + # Corrupt l2's stored funding txid so it disagrees with l1's on reconnect. |
| 218 | + l2.stop() |
| 219 | + l2.db_manip("UPDATE channel_funding_inflights SET funding_tx_id = X'{}'".format('01' * 32)) |
| 220 | + l2.start() |
| 221 | + |
| 222 | + # Reconnect: both will set next_funding in channel_reestablish but with |
| 223 | + # different txids, triggering open_err_fatal on each side per BOLT #2. |
| 224 | + l1.rpc.connect(l2.info['id'], 'localhost', l2.port) |
| 225 | + |
| 226 | + l1.daemon.wait_for_log(r"next_funding_txid .* doesn't match ours") |
| 227 | + l2.daemon.wait_for_log(r"next_funding_txid .* doesn't match ours") |
| 228 | + |
| 229 | + |
175 | 230 | @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') |
176 | 231 | @pytest.mark.openchannel('v2') |
177 | 232 | def test_v2_open_sigs_reconnect_1(node_factory, bitcoind): |
|
0 commit comments