@@ -66,6 +66,62 @@ def test_splice_disconnect_sig(node_factory, bitcoind):
6666 assert l1 .db_query ("SELECT count(*) as c FROM channeltxs;" )[0 ]['c' ] == 0
6767
6868
69+ @pytest .mark .openchannel ('v1' )
70+ @pytest .mark .openchannel ('v2' )
71+ @unittest .skipIf (TEST_NETWORK != 'regtest' , 'elementsd doesnt yet support PSBT features we need' )
72+ @pytest .mark .xfail (strict = True , reason = "funding_tx_index tracking not yet implemented" )
73+ def test_splice_reconnect_after_lock_no_channel_ready (node_factory , bitcoind ):
74+ # Once a splice locks, channel funding txid is updated to the splice txid.
75+ # On reconnect we must still recognise the peer's `my_current_funding_locked`
76+ # as a splice (funding_tx_index > 0) and NOT retransmit `channel_ready`.
77+ # channeld used to compare the txid against the (already-updated) channel
78+ # funding txid, which is wrong once a splice completes. This drives a full
79+ # splice + restart + reestablish to exercise the funding_tx_index path end
80+ # to end (DB columns, inflight wire, and the reestablish detection).
81+ l1 = node_factory .get_node (may_reconnect = True )
82+ l2 = node_factory .get_node (may_reconnect = True )
83+ l1 .openchannel (l2 , 1000000 )
84+
85+ chan_id = l1 .get_channel_id (l2 )
86+
87+ # add extra sats to pay fee
88+ funds_result = l1 .rpc .fundpsbt ("107527sat" , 0 , 0 , excess_as_change = True )
89+
90+ result = l1 .rpc .splice_init (chan_id , 100000 , funds_result ['psbt' ])
91+ result = l1 .rpc .splice_update (chan_id , result ['psbt' ])
92+ assert (result ['commitments_secured' ] is False )
93+ result = l1 .rpc .splice_update (chan_id , result ['psbt' ])
94+ assert (result ['commitments_secured' ] is True )
95+ result = l1 .rpc .signpsbt (result ['psbt' ])
96+ result = l1 .rpc .splice_signed (chan_id , result ['signed_psbt' ])
97+
98+ # Confirm and lock the splice on both sides.
99+ bitcoind .generate_block (6 , wait_for_mempool = 1 )
100+ l1 .daemon .wait_for_log (r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL' )
101+ l2 .daemon .wait_for_log (r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL' )
102+
103+ # Restart l1 so it reloads channel + inflight state from the DB (exercising
104+ # the new funding_tx_index columns) and reconnects/reestablishes.
105+ l1 .restart ()
106+
107+ l1 .daemon .wait_for_log (r'peer_in WIRE_CHANNEL_REESTABLISH' )
108+ l2 .daemon .wait_for_log (r'peer_in WIRE_CHANNEL_REESTABLISH' )
109+ l1 .daemon .wait_for_log (r'billboard: Channel ready for use.' )
110+ l2 .daemon .wait_for_log (r'billboard: Channel ready for use.' )
111+
112+ # The locked splice must have been persisted with funding_tx_index == 1...
113+ rows = l1 .db_query ("SELECT funding_tx_index FROM channels;" )
114+ assert max (r ['funding_tx_index' ] for r in rows ) == 1
115+
116+ # ...and recognised on reestablish, so channel_ready is NOT retransmitted.
117+ assert not l1 .daemon .is_in_log (r'Retransmitting channel_ready' )
118+ assert not l2 .daemon .is_in_log (r'Retransmitting channel_ready' )
119+
120+ # Channel still works after the splice + reconnect.
121+ inv = l2 .rpc .invoice (10 ** 2 , 'lbl' , 'desc' )
122+ l1 .rpc .xpay (inv ['bolt11' ])
123+
124+
69125@pytest .mark .openchannel ('v1' )
70126@pytest .mark .openchannel ('v2' )
71127@unittest .skipIf (TEST_NETWORK != 'regtest' , 'elementsd doesnt yet support PSBT features we need' )
@@ -114,3 +170,81 @@ def test_splice_disconnect_commit(node_factory, bitcoind, executor):
114170 # Check that the splice doesn't generate a unilateral close transaction
115171 time .sleep (5 )
116172 assert l1 .db_query ("SELECT count(*) as c FROM channeltxs;" )[0 ]['c' ] == 0
173+
174+
175+ @pytest .mark .openchannel ('v1' )
176+ @pytest .mark .openchannel ('v2' )
177+ @unittest .skipIf (TEST_NETWORK != 'regtest' , 'elementsd doesnt yet support PSBT features we need' )
178+ @pytest .mark .xfail (strict = True , reason = "funding_tx_index tracking not yet implemented" )
179+ def test_splice_funding_tx_index_increments (node_factory , bitcoind ):
180+ # funding_tx_index is 0 for the original funding and increments by 1 per
181+ # splice. Two sequential splices must reach index 2 on the persisted
182+ # channel (exercises the parent + 1 assignment).
183+ l1 = node_factory .get_node (may_reconnect = True )
184+ l2 = node_factory .get_node (may_reconnect = True )
185+ l1 .openchannel (l2 , 1000000 )
186+ chan_id = l1 .get_channel_id (l2 )
187+
188+ def do_splice (amount ):
189+ funds = l1 .rpc .fundpsbt ('{}sat' .format (amount + 7527 ), 0 , 0 ,
190+ excess_as_change = True )
191+ result = l1 .rpc .splice_init (chan_id , amount , funds ['psbt' ])
192+ result = l1 .rpc .splice_update (chan_id , result ['psbt' ])
193+ while result ['commitments_secured' ] is False :
194+ result = l1 .rpc .splice_update (chan_id , result ['psbt' ])
195+ result = l1 .rpc .signpsbt (result ['psbt' ])
196+ l1 .rpc .splice_signed (chan_id , result ['signed_psbt' ])
197+ bitcoind .generate_block (6 , wait_for_mempool = 1 )
198+ l1 .daemon .wait_for_log (r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL' )
199+ l2 .daemon .wait_for_log (r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL' )
200+
201+ def channel_funding_tx_index ():
202+ rows = l1 .db_query ("SELECT funding_tx_index FROM channels;" )
203+ return max (r ['funding_tx_index' ] for r in rows )
204+
205+ # First splice: 0 -> 1
206+ do_splice (100000 )
207+ assert channel_funding_tx_index () == 1
208+
209+ # Second splice: 1 -> 2
210+ do_splice (50000 )
211+ assert channel_funding_tx_index () == 2
212+
213+
214+ @pytest .mark .openchannel ('v1' )
215+ @pytest .mark .openchannel ('v2' )
216+ @unittest .skipIf (TEST_NETWORK != 'regtest' , 'elementsd doesnt yet support PSBT features we need' )
217+ @pytest .mark .xfail (strict = True , reason = "funding_tx_index tracking not yet implemented" )
218+ def test_splice_inflight_funding_tx_index (node_factory , bitcoind ):
219+ # A pending (not-yet-locked) splice inflight carries funding_tx_index == 1
220+ # (the open was index 0), and the value must survive a restart so the
221+ # reestablish detection still has it.
222+ l1 = node_factory .get_node (may_reconnect = True )
223+ l2 = node_factory .get_node (may_reconnect = True )
224+ l1 .openchannel (l2 , 1000000 )
225+ chan_id = l1 .get_channel_id (l2 )
226+
227+ funds = l1 .rpc .fundpsbt ("107527sat" , 0 , 0 , excess_as_change = True )
228+ result = l1 .rpc .splice_init (chan_id , 100000 , funds ['psbt' ])
229+ result = l1 .rpc .splice_update (chan_id , result ['psbt' ])
230+ while result ['commitments_secured' ] is False :
231+ result = l1 .rpc .splice_update (chan_id , result ['psbt' ])
232+ result = l1 .rpc .signpsbt (result ['psbt' ])
233+ l1 .rpc .splice_signed (chan_id , result ['signed_psbt' ])
234+
235+ # The pending splice inflight is index 1.
236+ inflights = l1 .db_query ("SELECT funding_tx_index FROM"
237+ " channel_funding_inflights;" )
238+ assert [r ['funding_tx_index' ] for r in inflights ] == [1 ]
239+
240+ # Restart l1: the inflight (and its index) must reload from the DB.
241+ l1 .restart ()
242+ l1 .daemon .wait_for_log (r'peer_in WIRE_CHANNEL_REESTABLISH' )
243+ inflights = l1 .db_query ("SELECT funding_tx_index FROM"
244+ " channel_funding_inflights;" )
245+ assert [r ['funding_tx_index' ] for r in inflights ] == [1 ]
246+
247+ # And the splice still completes after the restart.
248+ bitcoind .generate_block (6 , wait_for_mempool = 1 )
249+ l1 .daemon .wait_for_log (r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL' )
250+ l2 .daemon .wait_for_log (r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL' )
0 commit comments