From 8208b07325235df7e2725136072a4ef459862d55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADckolas=20Goline?= Date: Wed, 24 Jun 2026 17:54:23 -0300 Subject: [PATCH 1/3] splice: add tests for funding_tx_index tracking Add regression tests that each funding transaction carries a funding_tx_index (0 for the original funding, +1 per splice), that it is persisted and reloaded across restart, and that a pending splice inflight carries its index. Changelog-None --- tests/test_splicing_disconnect.py | 134 ++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/tests/test_splicing_disconnect.py b/tests/test_splicing_disconnect.py index a0975b0b4a10..72ba864d4d15 100644 --- a/tests/test_splicing_disconnect.py +++ b/tests/test_splicing_disconnect.py @@ -66,6 +66,62 @@ def test_splice_disconnect_sig(node_factory, bitcoind): assert l1.db_query("SELECT count(*) as c FROM channeltxs;")[0]['c'] == 0 +@pytest.mark.openchannel('v1') +@pytest.mark.openchannel('v2') +@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') +@pytest.mark.xfail(strict=True, reason="funding_tx_index tracking not yet implemented") +def test_splice_reconnect_after_lock_no_channel_ready(node_factory, bitcoind): + # Once a splice locks, channel funding txid is updated to the splice txid. + # On reconnect we must still recognise the peer's `my_current_funding_locked` + # as a splice (funding_tx_index > 0) and NOT retransmit `channel_ready`. + # channeld used to compare the txid against the (already-updated) channel + # funding txid, which is wrong once a splice completes. This drives a full + # splice + restart + reestablish to exercise the funding_tx_index path end + # to end (DB columns, inflight wire, and the reestablish detection). + l1 = node_factory.get_node(may_reconnect=True) + l2 = node_factory.get_node(may_reconnect=True) + l1.openchannel(l2, 1000000) + + chan_id = l1.get_channel_id(l2) + + # add extra sats to pay fee + funds_result = l1.rpc.fundpsbt("107527sat", 0, 0, excess_as_change=True) + + result = l1.rpc.splice_init(chan_id, 100000, funds_result['psbt']) + result = l1.rpc.splice_update(chan_id, result['psbt']) + assert(result['commitments_secured'] is False) + result = l1.rpc.splice_update(chan_id, result['psbt']) + assert(result['commitments_secured'] is True) + result = l1.rpc.signpsbt(result['psbt']) + result = l1.rpc.splice_signed(chan_id, result['signed_psbt']) + + # Confirm and lock the splice on both sides. + bitcoind.generate_block(6, wait_for_mempool=1) + l1.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL') + l2.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL') + + # Restart l1 so it reloads channel + inflight state from the DB (exercising + # the new funding_tx_index columns) and reconnects/reestablishes. + l1.restart() + + l1.daemon.wait_for_log(r'peer_in WIRE_CHANNEL_REESTABLISH') + l2.daemon.wait_for_log(r'peer_in WIRE_CHANNEL_REESTABLISH') + l1.daemon.wait_for_log(r'billboard: Channel ready for use.') + l2.daemon.wait_for_log(r'billboard: Channel ready for use.') + + # The locked splice must have been persisted with funding_tx_index == 1... + rows = l1.db_query("SELECT funding_tx_index FROM channels;") + assert max(r['funding_tx_index'] for r in rows) == 1 + + # ...and recognised on reestablish, so channel_ready is NOT retransmitted. + assert not l1.daemon.is_in_log(r'Retransmitting channel_ready') + assert not l2.daemon.is_in_log(r'Retransmitting channel_ready') + + # Channel still works after the splice + reconnect. + inv = l2.rpc.invoice(10**2, 'lbl', 'desc') + l1.rpc.xpay(inv['bolt11']) + + @pytest.mark.openchannel('v1') @pytest.mark.openchannel('v2') @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): # Check that the splice doesn't generate a unilateral close transaction time.sleep(5) assert l1.db_query("SELECT count(*) as c FROM channeltxs;")[0]['c'] == 0 + + +@pytest.mark.openchannel('v1') +@pytest.mark.openchannel('v2') +@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') +@pytest.mark.xfail(strict=True, reason="funding_tx_index tracking not yet implemented") +def test_splice_funding_tx_index_increments(node_factory, bitcoind): + # funding_tx_index is 0 for the original funding and increments by 1 per + # splice. Two sequential splices must reach index 2 on the persisted + # channel (exercises the parent + 1 assignment). + l1 = node_factory.get_node(may_reconnect=True) + l2 = node_factory.get_node(may_reconnect=True) + l1.openchannel(l2, 1000000) + chan_id = l1.get_channel_id(l2) + + def do_splice(amount): + funds = l1.rpc.fundpsbt('{}sat'.format(amount + 7527), 0, 0, + excess_as_change=True) + result = l1.rpc.splice_init(chan_id, amount, funds['psbt']) + result = l1.rpc.splice_update(chan_id, result['psbt']) + while result['commitments_secured'] is False: + result = l1.rpc.splice_update(chan_id, result['psbt']) + result = l1.rpc.signpsbt(result['psbt']) + l1.rpc.splice_signed(chan_id, result['signed_psbt']) + bitcoind.generate_block(6, wait_for_mempool=1) + l1.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL') + l2.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL') + + def channel_funding_tx_index(): + rows = l1.db_query("SELECT funding_tx_index FROM channels;") + return max(r['funding_tx_index'] for r in rows) + + # First splice: 0 -> 1 + do_splice(100000) + assert channel_funding_tx_index() == 1 + + # Second splice: 1 -> 2 + do_splice(50000) + assert channel_funding_tx_index() == 2 + + +@pytest.mark.openchannel('v1') +@pytest.mark.openchannel('v2') +@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') +@pytest.mark.xfail(strict=True, reason="funding_tx_index tracking not yet implemented") +def test_splice_inflight_funding_tx_index(node_factory, bitcoind): + # A pending (not-yet-locked) splice inflight carries funding_tx_index == 1 + # (the open was index 0), and the value must survive a restart so the + # reestablish detection still has it. + l1 = node_factory.get_node(may_reconnect=True) + l2 = node_factory.get_node(may_reconnect=True) + l1.openchannel(l2, 1000000) + chan_id = l1.get_channel_id(l2) + + funds = l1.rpc.fundpsbt("107527sat", 0, 0, excess_as_change=True) + result = l1.rpc.splice_init(chan_id, 100000, funds['psbt']) + result = l1.rpc.splice_update(chan_id, result['psbt']) + while result['commitments_secured'] is False: + result = l1.rpc.splice_update(chan_id, result['psbt']) + result = l1.rpc.signpsbt(result['psbt']) + l1.rpc.splice_signed(chan_id, result['signed_psbt']) + + # The pending splice inflight is index 1. + inflights = l1.db_query("SELECT funding_tx_index FROM" + " channel_funding_inflights;") + assert [r['funding_tx_index'] for r in inflights] == [1] + + # Restart l1: the inflight (and its index) must reload from the DB. + l1.restart() + l1.daemon.wait_for_log(r'peer_in WIRE_CHANNEL_REESTABLISH') + inflights = l1.db_query("SELECT funding_tx_index FROM" + " channel_funding_inflights;") + assert [r['funding_tx_index'] for r in inflights] == [1] + + # And the splice still completes after the restart. + bitcoind.generate_block(6, wait_for_mempool=1) + l1.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL') + l2.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL') From 2c40355334ef7df977030bb53e4c1685da485809 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADckolas=20Goline?= Date: Wed, 24 Jun 2026 18:13:53 -0300 Subject: [PATCH 2/3] splice: track funding_tx_index on inflights and channel funding Give every funding tx a stable index: 0 for the original funding (including RBF attempts), incrementing by 1 per splice. Threaded through the channeld inflight, the channel, the channeld_init and channeld_add_inflight wire messages, set on splice creation and carried onto the channel funding when a splice locks, and persisted in the channel_funding_inflights and channels tables. --- channeld/channeld.c | 10 ++++++++++ channeld/channeld_wire.csv | 2 ++ channeld/full_channel.c | 2 ++ channeld/full_channel.h | 1 + channeld/inflight.c | 2 ++ channeld/inflight.h | 3 +++ channeld/test/run-full_channel.c | 4 ++-- common/initial_channel.c | 4 ++++ common/initial_channel.h | 7 +++++++ devtools/mkcommit.c | 2 +- lightningd/channel.c | 5 ++++- lightningd/channel.h | 8 ++++++++ lightningd/channel_control.c | 5 +++++ lightningd/dual_open_control.c | 2 ++ lightningd/opening_control.c | 2 ++ lightningd/peer_control.c | 1 + openingd/dualopend.c | 4 ++++ openingd/openingd.c | 2 ++ tests/fuzz/fuzz-full_channel.c | 4 ++-- tests/fuzz/fuzz-initial_channel.c | 3 ++- tests/plugins/channeld_fakenet.c | 3 +++ tests/test_splicing_disconnect.py | 4 +--- wallet/migrations.c | 2 ++ wallet/test/run-chain_moves_duplicate-detect.c | 2 ++ wallet/test/run-db.c | 2 ++ .../test/run-migrate_remove_chain_moves_duplicates.c | 2 ++ wallet/test/run-wallet.c | 5 +++-- wallet/wallet.c | 10 +++++++++- 28 files changed, 90 insertions(+), 13 deletions(-) diff --git a/channeld/channeld.c b/channeld/channeld.c index b2e662fdb53f..8476b801dfa0 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -483,6 +483,7 @@ static void check_mutual_splice_locked(struct peer *peer) fmt_channel(tmpctx, peer->channel)); error = channel_update_funding(peer->channel, &inflight->outpoint, + inflight->funding_tx_index, inflight->amnt, inflight->splice_amnt); if (error) @@ -4362,6 +4363,7 @@ static void splice_accepter(struct peer *peer, const u8 *inmsg) &peer->splicing->remote_funding_pubkey, &outpoint.txid, outpoint.n, + peer->channel->funding_tx_index + 1, funding_feerate_perkw, both_amount, peer->splicing->accepter_relative, @@ -4379,6 +4381,8 @@ static void splice_accepter(struct peer *peer, const u8 *inmsg) &new_inflight->outpoint.txid, NULL); new_inflight->remote_funding = peer->splicing->remote_funding_pubkey; new_inflight->outpoint = outpoint; + /* A splice's funding tx is the parent funding's index + 1. */ + new_inflight->funding_tx_index = peer->channel->funding_tx_index + 1; new_inflight->amnt = both_amount; new_inflight->psbt = clone_psbt(new_inflight, ictx->current_psbt); new_inflight->splice_amnt = peer->splicing->accepter_relative; @@ -4657,6 +4661,7 @@ static void splice_initiator_user_finalized(struct peer *peer) &peer->splicing->remote_funding_pubkey, ¤t_psbt_txid, chan_output_index, + peer->channel->funding_tx_index + 1, peer->splicing->feerate_per_kw, amount_sat(new_chan_output->amount), peer->splicing->opener_relative, @@ -4674,6 +4679,8 @@ static void splice_initiator_user_finalized(struct peer *peer) NULL); new_inflight->remote_funding = peer->splicing->remote_funding_pubkey; new_inflight->outpoint.n = chan_output_index; + /* A splice's funding tx is the parent funding's index + 1. */ + new_inflight->funding_tx_index = peer->channel->funding_tx_index + 1; new_inflight->amnt = amount_sat(new_chan_output->amount); new_inflight->splice_amnt = peer->splicing->opener_relative; new_inflight->last_tx = NULL; @@ -6825,6 +6832,7 @@ static void init_channel(struct peer *peer) { struct basepoints points[NUM_SIDES]; struct amount_sat funding_sats; + u32 funding_tx_index; struct amount_msat local_msat; struct pubkey funding_pubkey[NUM_SIDES]; struct channel_config conf[NUM_SIDES]; @@ -6854,6 +6862,7 @@ static void init_channel(struct peer *peer) &peer->channel_id, &funding, &funding_sats, + &funding_tx_index, &minimum_depth, &peer->our_blockheight, &blockheight_states, @@ -6968,6 +6977,7 @@ static void init_channel(struct peer *peer) peer->channel = new_full_channel(peer, &peer->channel_id, &funding, + funding_tx_index, minimum_depth, take(blockheight_states), lease_expiry, diff --git a/channeld/channeld_wire.csv b/channeld/channeld_wire.csv index 208da3038edb..b98423ca4d04 100644 --- a/channeld/channeld_wire.csv +++ b/channeld/channeld_wire.csv @@ -21,6 +21,7 @@ msgdata,channeld_init,hsm_capabilities,u32,num_hsm_capabilities msgdata,channeld_init,channel_id,channel_id, msgdata,channeld_init,funding,bitcoin_outpoint, msgdata,channeld_init,funding_satoshi,amount_sat, +msgdata,channeld_init,funding_tx_index,u32, msgdata,channeld_init,minimum_depth,u32, msgdata,channeld_init,our_blockheight,u32, msgdata,channeld_init,blockheight_states,height_states, @@ -261,6 +262,7 @@ msgtype,channeld_add_inflight,7216 msgdata,channeld_add_inflight,remote_funding,pubkey, msgdata,channeld_add_inflight,tx_id,bitcoin_txid, msgdata,channeld_add_inflight,tx_outnum,u32, +msgdata,channeld_add_inflight,funding_tx_index,u32, msgdata,channeld_add_inflight,feerate,u32, msgdata,channeld_add_inflight,satoshis,amount_sat, msgdata,channeld_add_inflight,splice_amount,s64, diff --git a/channeld/full_channel.c b/channeld/full_channel.c index 5e6586250caf..0244714d67b4 100644 --- a/channeld/full_channel.c +++ b/channeld/full_channel.c @@ -74,6 +74,7 @@ static bool balance_ok(const struct balance *balance, struct channel *new_full_channel(const tal_t *ctx, const struct channel_id *cid, const struct bitcoin_outpoint *funding, + u32 funding_tx_index, u32 minimum_depth, const struct height_states *blockheight_states, u32 lease_expiry, @@ -93,6 +94,7 @@ struct channel *new_full_channel(const tal_t *ctx, struct channel *channel = new_initial_channel(ctx, cid, funding, + funding_tx_index, minimum_depth, blockheight_states, lease_expiry, diff --git a/channeld/full_channel.h b/channeld/full_channel.h index d1cb84c3ffe3..47d09f5aeeca 100644 --- a/channeld/full_channel.h +++ b/channeld/full_channel.h @@ -36,6 +36,7 @@ struct existing_htlc; struct channel *new_full_channel(const tal_t *ctx, const struct channel_id *cid, const struct bitcoin_outpoint *funding, + u32 funding_tx_index, u32 minimum_depth, const struct height_states *blockheight_states, u32 lease_expiry, diff --git a/channeld/inflight.c b/channeld/inflight.c index 475e6698fdec..838ebd2e6c89 100644 --- a/channeld/inflight.c +++ b/channeld/inflight.c @@ -9,6 +9,7 @@ struct inflight *fromwire_inflight(const tal_t *ctx, const u8 **cursor, size_t * struct inflight *inflight = tal(ctx, struct inflight); fromwire_bitcoin_outpoint(cursor, max, &inflight->outpoint); + inflight->funding_tx_index = fromwire_u32(cursor, max); fromwire_pubkey(cursor, max, &inflight->remote_funding); inflight->amnt = fromwire_amount_sat(cursor, max); inflight->remote_tx_sigs = fromwire_bool(cursor, max); @@ -41,6 +42,7 @@ struct inflight *fromwire_inflight(const tal_t *ctx, const u8 **cursor, size_t * void towire_inflight(u8 **pptr, const struct inflight *inflight) { towire_bitcoin_outpoint(pptr, &inflight->outpoint); + towire_u32(pptr, inflight->funding_tx_index); towire_pubkey(pptr, &inflight->remote_funding); towire_amount_sat(pptr, inflight->amnt); towire_bool(pptr, inflight->remote_tx_sigs); diff --git a/channeld/inflight.h b/channeld/inflight.h index 025dc450240d..b34fe785f00a 100644 --- a/channeld/inflight.h +++ b/channeld/inflight.h @@ -10,6 +10,9 @@ struct inflight { /* The new channel outpoint */ struct bitcoin_outpoint outpoint; + /* Which funding tx this is: 0 for the original funding (incl. RBF + * attempts), incrementing by 1 for each splice. */ + u32 funding_tx_index; struct pubkey remote_funding; struct amount_sat amnt; bool remote_tx_sigs; diff --git a/channeld/test/run-full_channel.c b/channeld/test/run-full_channel.c index 1f8b10ba92a5..7446c7ad1863 100644 --- a/channeld/test/run-full_channel.c +++ b/channeld/test/run-full_channel.c @@ -492,7 +492,7 @@ int main(int argc, const char *argv[]) feerate_per_kw[LOCAL] = feerate_per_kw[REMOTE] = 15000; derive_channel_id(&cid, &funding); lchannel = new_full_channel(tmpctx, &cid, - &funding, 0, + &funding, 0, 0, take(new_height_states(NULL, LOCAL, &blockheight)), 0, /* No channel lease */ funding_amount, to_local, @@ -505,7 +505,7 @@ int main(int argc, const char *argv[]) &remote_funding_pubkey, take(channel_type_static_remotekey(NULL)), false, LOCAL); rchannel = new_full_channel(tmpctx, &cid, - &funding, 0, + &funding, 0, 0, take(new_height_states(NULL, REMOTE, &blockheight)), 0, /* No channel lease */ funding_amount, to_remote, diff --git a/common/initial_channel.c b/common/initial_channel.c index 417b766cc861..ffcb9a771069 100644 --- a/common/initial_channel.c +++ b/common/initial_channel.c @@ -12,6 +12,7 @@ struct channel *new_initial_channel(const tal_t *ctx, const struct channel_id *cid, const struct bitcoin_outpoint *funding, + u32 funding_tx_index, u32 minimum_depth, const struct height_states *height_states TAKES, u32 lease_expiry, @@ -46,6 +47,7 @@ struct channel *new_initial_channel(const tal_t *ctx, channel->cid = *cid; channel->funding = *funding; + channel->funding_tx_index = funding_tx_index; channel->funding_sats = funding_sats; channel->minimum_depth = minimum_depth; channel->lease_expiry = lease_expiry; @@ -156,6 +158,7 @@ struct bitcoin_tx *initial_channel_tx(const tal_t *ctx, const char *channel_update_funding(struct channel *channel, const struct bitcoin_outpoint *funding, + u32 funding_tx_index, struct amount_sat funding_sats, s64 splice_amnt) { @@ -164,6 +167,7 @@ const char *channel_update_funding(struct channel *channel, channel->funding = *funding; channel->funding_sats = funding_sats; + channel->funding_tx_index = funding_tx_index; if (splice_amnt * 1000 + channel->view[LOCAL].owed[LOCAL].millisatoshis < 0) /* Raw: splicing */ return tal_fmt(tmpctx, "Channel funding update would make local" diff --git a/common/initial_channel.h b/common/initial_channel.h index 5204efe22945..f3c5312c7d7b 100644 --- a/common/initial_channel.h +++ b/common/initial_channel.h @@ -39,6 +39,10 @@ struct channel { /* Funding txid and output. */ struct bitcoin_outpoint funding; + /* Which funding tx this is: 0 for the original funding (incl. RBF + * attempts), incrementing by 1 for each splice. */ + u32 funding_tx_index; + /* Keys used to spend funding tx. */ struct pubkey funding_pubkey[NUM_SIDES]; @@ -88,6 +92,7 @@ struct channel { * @ctx: tal context to allocate return value from. * @cid: The channel's id. * @funding: The commitment transaction id/outnum + * @funding_tx_index: 0 for the original funding, +1 for each splice. * @minimum_depth: The minimum confirmations needed for funding transaction. * @height_states: The blockheight update states. * @lease_expiry: Block the lease expires. @@ -109,6 +114,7 @@ struct channel { struct channel *new_initial_channel(const tal_t *ctx, const struct channel_id *cid, const struct bitcoin_outpoint *funding, + u32 funding_tx_index, u32 minimum_depth, const struct height_states *height_states TAKES, u32 lease_expiry, @@ -154,6 +160,7 @@ struct bitcoin_tx *initial_channel_tx(const tal_t *ctx, */ const char *channel_update_funding(struct channel *channel, const struct bitcoin_outpoint *funding, + u32 funding_tx_index, struct amount_sat funding_sats, s64 splice_amnt); diff --git a/devtools/mkcommit.c b/devtools/mkcommit.c index 48494ac79819..dfa558cc89d9 100644 --- a/devtools/mkcommit.c +++ b/devtools/mkcommit.c @@ -399,7 +399,7 @@ int main(int argc, char *argv[]) channel = new_full_channel(NULL, &cid, - &funding, 1, + &funding, 0, 1, take(new_height_states(NULL, fee_payer, &blockheight)), 0, /* Defaults to no lease */ diff --git a/lightningd/channel.c b/lightningd/channel.c index 1cd40871ed2d..8a704f45a889 100644 --- a/lightningd/channel.c +++ b/lightningd/channel.c @@ -171,6 +171,7 @@ struct channel_inflight * new_inflight(struct channel *channel, struct pubkey *remote_funding, const struct bitcoin_outpoint *funding_outpoint, + u32 funding_tx_index, u32 funding_feerate, struct amount_sat total_funds, struct amount_sat our_funds, @@ -199,6 +200,7 @@ new_inflight(struct channel *channel, funding->splice_remote_funding = tal_steal(funding, remote_funding); inflight->funding = funding; + inflight->funding_tx_index = funding_tx_index; inflight->channel = channel; inflight->remote_tx_sigs = false; inflight->funding_psbt = tal_steal(inflight, psbt); @@ -506,6 +508,7 @@ struct channel *new_channel(struct peer *peer, u64 dbid, u64 next_index_remote, u64 next_htlc_id, const struct bitcoin_outpoint *funding, + u32 funding_tx_index, struct amount_sat funding_sats, struct amount_msat push, struct amount_sat our_funds, @@ -612,6 +615,7 @@ struct channel *new_channel(struct peer *peer, u64 dbid, channel->next_index[REMOTE] = next_index_remote; channel->next_htlc_id = next_htlc_id; channel->funding = *funding; + channel->funding_tx_index = funding_tx_index; channel->funding_sats = funding_sats; channel->funding_spend_watch = NULL; channel->push = push; @@ -1338,4 +1342,3 @@ const u8 *channel_update_for_error(const tal_t *ctx, return channel_gossip_update_for_error(ctx, channel); } - diff --git a/lightningd/channel.h b/lightningd/channel.h index 46add63f51a4..11f791dea2ab 100644 --- a/lightningd/channel.h +++ b/lightningd/channel.h @@ -58,6 +58,9 @@ struct channel_inflight { /* Funding info */ const struct funding_info *funding; + /* Which funding tx this is: 0 for the original funding (incl. RBF + * attempts), incrementing by 1 for each splice. */ + u32 funding_tx_index; struct wally_psbt *funding_psbt; bool remote_tx_sigs; bool tx_broadcast; @@ -203,6 +206,9 @@ struct channel { /* Funding outpoint and amount */ struct bitcoin_outpoint funding; + /* Which funding tx this is: 0 for the original funding (incl. RBF + * attempts), incrementing by 1 for each splice. */ + u32 funding_tx_index; struct amount_sat funding_sats; /* Watch we have on funding output. */ @@ -394,6 +400,7 @@ struct channel *new_channel(struct peer *peer, u64 dbid, u64 next_index_remote, u64 next_htlc_id, const struct bitcoin_outpoint *funding, + u32 funding_tx_index, struct amount_sat funding_sats, struct amount_msat push, struct amount_sat our_funds, @@ -458,6 +465,7 @@ struct channel *new_channel(struct peer *peer, u64 dbid, struct channel_inflight *new_inflight(struct channel *channel, struct pubkey *remote_funding STEALS, const struct bitcoin_outpoint *funding_outpoint, + u32 funding_tx_index, u32 funding_feerate, struct amount_sat funding_sat, struct amount_sat our_funds, diff --git a/lightningd/channel_control.c b/lightningd/channel_control.c index ee707bca1a9d..18cdb5fb0f1c 100644 --- a/lightningd/channel_control.c +++ b/lightningd/channel_control.c @@ -891,6 +891,7 @@ static void handle_add_inflight(struct lightningd *ld, { struct pubkey *remote_funding = tal(tmpctx, struct pubkey); struct bitcoin_outpoint outpoint; + u32 funding_tx_index; u32 feerate; struct amount_sat satoshis; s64 splice_amnt; @@ -903,6 +904,7 @@ static void handle_add_inflight(struct lightningd *ld, remote_funding, &outpoint.txid, &outpoint.n, + &funding_tx_index, &feerate, &satoshis, &splice_amnt, @@ -919,6 +921,7 @@ static void handle_add_inflight(struct lightningd *ld, inflight = new_inflight(channel, remote_funding, &outpoint, + funding_tx_index, feerate, satoshis, channel->our_funds, @@ -1883,6 +1886,7 @@ bool peer_start_channeld(struct channel *channel, infcopy->remote_funding = *inflight->funding->splice_remote_funding; infcopy->outpoint = inflight->funding->outpoint; + infcopy->funding_tx_index = inflight->funding_tx_index; infcopy->amnt = inflight->funding->total_funds; infcopy->remote_tx_sigs = inflight->remote_tx_sigs; infcopy->splice_amnt = inflight->funding->splice_amnt; @@ -1910,6 +1914,7 @@ bool peer_start_channeld(struct channel *channel, &channel->cid, &channel->funding, channel->funding_sats, + channel->funding_tx_index, channel->minimum_depth, curr_blockheight, channel->blockheight_states, diff --git a/lightningd/dual_open_control.c b/lightningd/dual_open_control.c index 7c440e59098b..3fac4050eb5f 100644 --- a/lightningd/dual_open_control.c +++ b/lightningd/dual_open_control.c @@ -1274,6 +1274,7 @@ wallet_update_channel(struct lightningd *ld, inflight = new_inflight(channel, NULL, &channel->funding, + 0, funding_feerate, channel->funding_sats, channel->our_funds, @@ -1500,6 +1501,7 @@ wallet_commit_channel(struct lightningd *ld, inflight = new_inflight(channel, NULL, &channel->funding, + 0, funding_feerate, channel->funding_sats, channel->our_funds, diff --git a/lightningd/opening_control.c b/lightningd/opening_control.c index 74a3277d051e..e4623f641d60 100644 --- a/lightningd/opening_control.c +++ b/lightningd/opening_control.c @@ -182,6 +182,7 @@ wallet_commit_channel(struct lightningd *ld, uc->minimum_depth, 1, 1, 0, funding, + 0, funding_sats, push, local_funding, @@ -1618,6 +1619,7 @@ static struct channel *stub_chan(struct command *cmd, 0, 1, 1, 1, &funding, + 0, funding_sats, AMOUNT_MSAT(0), AMOUNT_SAT(0), diff --git a/lightningd/peer_control.c b/lightningd/peer_control.c index 8ec1cb792433..814e1484b13e 100644 --- a/lightningd/peer_control.c +++ b/lightningd/peer_control.c @@ -2296,6 +2296,7 @@ void update_channel_from_inflight(struct lightningd *ld, bool is_splice) { channel->funding = inflight->funding->outpoint; + channel->funding_tx_index = inflight->funding_tx_index; channel->funding_sats = inflight->funding->total_funds; channel->our_funds = inflight->funding->our_funds; diff --git a/openingd/dualopend.c b/openingd/dualopend.c index e1940a44e516..b13c4848912d 100644 --- a/openingd/dualopend.c +++ b/openingd/dualopend.c @@ -2130,6 +2130,7 @@ static void revert_channel_state(struct state *state) state->channel = new_initial_channel(state, &state->channel_id, &tx_state->funding, + 0, state->minimum_depth, take(new_height_states(NULL, opener, &tx_state->blockheight)), @@ -2204,6 +2205,7 @@ static u8 *accepter_commits(struct state *state, state->channel = new_initial_channel(state, &state->channel_id, &tx_state->funding, + 0, state->minimum_depth, take(new_height_states(NULL, REMOTE, &tx_state->blockheight)), @@ -2849,6 +2851,7 @@ static u8 *opener_commits(struct state *state, state->channel = new_initial_channel(state, &state->channel_id, &tx_state->funding, + 0, state->minimum_depth, take(new_height_states(NULL, LOCAL, &state->tx_state->blockheight)), @@ -4456,6 +4459,7 @@ int main(int argc, char *argv[]) state->channel = new_initial_channel(state, &state->channel_id, &state->tx_state->funding, + 0, state->minimum_depth, take(new_height_states(NULL, opener, &state->tx_state->blockheight)), diff --git a/openingd/openingd.c b/openingd/openingd.c index 52e50572e2e0..60e1564d443c 100644 --- a/openingd/openingd.c +++ b/openingd/openingd.c @@ -590,6 +590,7 @@ static bool funder_finalize_channel_setup(struct state *state, state->channel = new_initial_channel(state, &cid, &state->funding, + 0, state->minimum_depth, NULL, 0, /* No channel lease */ state->funding_sats, @@ -1156,6 +1157,7 @@ static u8 *fundee_channel(struct state *state, const u8 *open_channel_msg) state->channel = new_initial_channel(state, &state->channel_id, &state->funding, + 0, state->minimum_depth, NULL, 0, /* No channel lease */ state->funding_sats, diff --git a/tests/fuzz/fuzz-full_channel.c b/tests/fuzz/fuzz-full_channel.c index 977b7367b697..4930ad9c2d3f 100644 --- a/tests/fuzz/fuzz-full_channel.c +++ b/tests/fuzz/fuzz-full_channel.c @@ -208,7 +208,7 @@ static void init_channels(const tal_t *ctx, const u8 **cursor, size_t *max, derive_channel_id(&cid, funding); - *lchannel = new_full_channel(ctx, &cid, funding, 0, + *lchannel = new_full_channel(ctx, &cid, funding, 0, 0, take(new_height_states(NULL, LOCAL, blockheight)), 0, *funding_amount, to_local, take(new_fee_states(NULL, LOCAL, &feerate_per_kw[LOCAL])), @@ -216,7 +216,7 @@ static void init_channels(const tal_t *ctx, const u8 **cursor, size_t *max, &localbase, &remotebase, &local_funding_pubkey, &remote_funding_pubkey, take(channel_type_static_remotekey(NULL)), false, LOCAL); - *rchannel = new_full_channel(ctx, &cid, funding, 0, + *rchannel = new_full_channel(ctx, &cid, funding, 0, 0, take(new_height_states(NULL, REMOTE, blockheight)), 0, *funding_amount, to_remote, take(new_fee_states(NULL, REMOTE, &feerate_per_kw[REMOTE])), diff --git a/tests/fuzz/fuzz-initial_channel.c b/tests/fuzz/fuzz-initial_channel.c index 52eca85fa3f5..e21b7151ec95 100644 --- a/tests/fuzz/fuzz-initial_channel.c +++ b/tests/fuzz/fuzz-initial_channel.c @@ -46,7 +46,7 @@ static void test_channel_update_funding(struct channel *channel, const u8 **curs funding_sats.satoshis %= MAX_SATS; /* Raw: fuzzing */ splice_amnt = fromwire_s64(cursor, max) % MAX_SATS; - channel_update_funding(channel, &funding, funding_sats, splice_amnt); + channel_update_funding(channel, &funding, 0, funding_sats, splice_amnt); } void run(const uint8_t *data, size_t size) @@ -98,6 +98,7 @@ void run(const uint8_t *data, size_t size) for (enum side opener = 0; opener < NUM_SIDES; opener++) { channel = new_initial_channel(tmpctx, &cid, &funding, + 0, minimum_depth, take(new_height_states(NULL, opener, &blockheight)), diff --git a/tests/plugins/channeld_fakenet.c b/tests/plugins/channeld_fakenet.c index f43384fa371c..131c869e3a3f 100644 --- a/tests/plugins/channeld_fakenet.c +++ b/tests/plugins/channeld_fakenet.c @@ -1052,6 +1052,7 @@ static struct channel *handle_init(struct info *info, const u8 *init_msg) struct ext_key final_ext_key; u8 *fwd_msg; u32 minimum_depth, lease_expiry; + u32 funding_tx_index; struct secret last_remote_per_commit_secret; struct penalty_base *pbases; struct channel_type *channel_type; @@ -1089,6 +1090,7 @@ static struct channel *handle_init(struct info *info, const u8 *init_msg) &channel_id, &funding, &funding_sats, + &funding_tx_index, &minimum_depth, &info->current_block_height, &info->blockheight_states, @@ -1141,6 +1143,7 @@ static struct channel *handle_init(struct info *info, const u8 *init_msg) status_debug("Parsed init..."); channel = new_full_channel(info, &channel_id, &funding, + funding_tx_index, minimum_depth, info->blockheight_states, lease_expiry, diff --git a/tests/test_splicing_disconnect.py b/tests/test_splicing_disconnect.py index 72ba864d4d15..4baa848e5a70 100644 --- a/tests/test_splicing_disconnect.py +++ b/tests/test_splicing_disconnect.py @@ -69,7 +69,7 @@ def test_splice_disconnect_sig(node_factory, bitcoind): @pytest.mark.openchannel('v1') @pytest.mark.openchannel('v2') @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') -@pytest.mark.xfail(strict=True, reason="funding_tx_index tracking not yet implemented") +@pytest.mark.xfail(strict=True, reason="channel_ready wrongly retransmitted after splice until funding_tx_index detection") def test_splice_reconnect_after_lock_no_channel_ready(node_factory, bitcoind): # Once a splice locks, channel funding txid is updated to the splice txid. # On reconnect we must still recognise the peer's `my_current_funding_locked` @@ -175,7 +175,6 @@ def test_splice_disconnect_commit(node_factory, bitcoind, executor): @pytest.mark.openchannel('v1') @pytest.mark.openchannel('v2') @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') -@pytest.mark.xfail(strict=True, reason="funding_tx_index tracking not yet implemented") def test_splice_funding_tx_index_increments(node_factory, bitcoind): # funding_tx_index is 0 for the original funding and increments by 1 per # splice. Two sequential splices must reach index 2 on the persisted @@ -214,7 +213,6 @@ def channel_funding_tx_index(): @pytest.mark.openchannel('v1') @pytest.mark.openchannel('v2') @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') -@pytest.mark.xfail(strict=True, reason="funding_tx_index tracking not yet implemented") def test_splice_inflight_funding_tx_index(node_factory, bitcoind): # A pending (not-yet-locked) splice inflight carries funding_tx_index == 1 # (the open was index 0), and the value must survive a restart so the diff --git a/wallet/migrations.c b/wallet/migrations.c index 1f5d3b51c0f2..2dc97a50dbf3 100644 --- a/wallet/migrations.c +++ b/wallet/migrations.c @@ -1085,6 +1085,8 @@ static const struct db_migration dbmigrations[] = { {SQL("ALTER TABLE offers ADD COLUMN force_paths INTEGER DEFAULT 0;"), NULL, SQL("ALTER TABLE offers DROP COLUMN force_paths"), NULL}, /* ^v26.04 */ + {SQL("ALTER TABLE channel_funding_inflights ADD funding_tx_index INTEGER DEFAULT 0"), NULL}, + {SQL("ALTER TABLE channels ADD funding_tx_index INTEGER DEFAULT 0"), NULL}, }; const struct db_migration *get_db_migrations(size_t *num) diff --git a/wallet/test/run-chain_moves_duplicate-detect.c b/wallet/test/run-chain_moves_duplicate-detect.c index 491ccc1da265..6902ae576f53 100644 --- a/wallet/test/run-chain_moves_duplicate-detect.c +++ b/wallet/test/run-chain_moves_duplicate-detect.c @@ -180,6 +180,7 @@ struct channel *new_channel(struct peer *peer UNNEEDED, u64 dbid UNNEEDED, u64 next_index_remote UNNEEDED, u64 next_htlc_id UNNEEDED, const struct bitcoin_outpoint *funding UNNEEDED, + u32 funding_tx_index UNNEEDED, struct amount_sat funding_sats UNNEEDED, struct amount_msat push UNNEEDED, struct amount_sat our_funds UNNEEDED, @@ -252,6 +253,7 @@ struct channel_state_change *new_channel_state_change(const tal_t *ctx UNNEEDED, struct channel_inflight *new_inflight(struct channel *channel UNNEEDED, struct pubkey *remote_funding STEALS UNNEEDED, const struct bitcoin_outpoint *funding_outpoint UNNEEDED, + u32 funding_tx_index UNNEEDED, u32 funding_feerate UNNEEDED, struct amount_sat funding_sat UNNEEDED, struct amount_sat our_funds UNNEEDED, diff --git a/wallet/test/run-db.c b/wallet/test/run-db.c index 3ac23b20f636..7a0c9a1650f8 100644 --- a/wallet/test/run-db.c +++ b/wallet/test/run-db.c @@ -188,6 +188,7 @@ struct channel *new_channel(struct peer *peer UNNEEDED, u64 dbid UNNEEDED, u64 next_index_remote UNNEEDED, u64 next_htlc_id UNNEEDED, const struct bitcoin_outpoint *funding UNNEEDED, + u32 funding_tx_index UNNEEDED, struct amount_sat funding_sats UNNEEDED, struct amount_msat push UNNEEDED, struct amount_sat our_funds UNNEEDED, @@ -260,6 +261,7 @@ struct channel_state_change *new_channel_state_change(const tal_t *ctx UNNEEDED, struct channel_inflight *new_inflight(struct channel *channel UNNEEDED, struct pubkey *remote_funding STEALS UNNEEDED, const struct bitcoin_outpoint *funding_outpoint UNNEEDED, + u32 funding_tx_index UNNEEDED, u32 funding_feerate UNNEEDED, struct amount_sat funding_sat UNNEEDED, struct amount_sat our_funds UNNEEDED, diff --git a/wallet/test/run-migrate_remove_chain_moves_duplicates.c b/wallet/test/run-migrate_remove_chain_moves_duplicates.c index d2f25f8f6cdf..023186984ffa 100644 --- a/wallet/test/run-migrate_remove_chain_moves_duplicates.c +++ b/wallet/test/run-migrate_remove_chain_moves_duplicates.c @@ -221,6 +221,7 @@ struct channel *new_channel(struct peer *peer UNNEEDED, u64 dbid UNNEEDED, u64 next_index_remote UNNEEDED, u64 next_htlc_id UNNEEDED, const struct bitcoin_outpoint *funding UNNEEDED, + u32 funding_tx_index UNNEEDED, struct amount_sat funding_sats UNNEEDED, struct amount_msat push UNNEEDED, struct amount_sat our_funds UNNEEDED, @@ -293,6 +294,7 @@ struct channel_state_change *new_channel_state_change(const tal_t *ctx UNNEEDED, struct channel_inflight *new_inflight(struct channel *channel UNNEEDED, struct pubkey *remote_funding STEALS UNNEEDED, const struct bitcoin_outpoint *funding_outpoint UNNEEDED, + u32 funding_tx_index UNNEEDED, u32 funding_feerate UNNEEDED, struct amount_sat funding_sat UNNEEDED, struct amount_sat our_funds UNNEEDED, diff --git a/wallet/test/run-wallet.c b/wallet/test/run-wallet.c index 0cef1f6c9ab2..6304cb8df973 100644 --- a/wallet/test/run-wallet.c +++ b/wallet/test/run-wallet.c @@ -1612,6 +1612,7 @@ static bool test_channel_inflight_crud(struct lightningd *ld, const tal_t *ctx, 8, false, false, &our_config, 101, 1, 1, 1, &outpoint, + 0, funding_sats, AMOUNT_MSAT(0), our_sats, 0, NULL, @@ -1662,7 +1663,7 @@ static bool test_channel_inflight_crud(struct lightningd *ld, const tal_t *ctx, memset(&outpoint, 1, sizeof(outpoint)); mempat(&sig.s, sizeof(sig.s)); - inflight = new_inflight(chan, NULL, &outpoint, 253, + inflight = new_inflight(chan, NULL, &outpoint, 0, 253, funding_sats, our_sats, funding_psbt, @@ -1698,7 +1699,7 @@ static bool test_channel_inflight_crud(struct lightningd *ld, const tal_t *ctx, our_sats = AMOUNT_SAT(555555); memset(&outpoint, 2, sizeof(outpoint)); mempat(&sig.s, sizeof(sig.s)); - inflight = new_inflight(chan, NULL, &outpoint, 300, + inflight = new_inflight(chan, NULL, &outpoint, 0, 300, funding_sats, our_sats, funding_psbt, diff --git a/wallet/wallet.c b/wallet/wallet.c index 91eb5079ff6b..879a0857baef 100644 --- a/wallet/wallet.c +++ b/wallet/wallet.c @@ -1494,6 +1494,7 @@ void wallet_inflight_add(struct wallet *w, struct channel_inflight *inflight) " channel_id" ", funding_tx_id" ", funding_tx_outnum" + ", funding_tx_index" ", funding_feerate" ", funding_satoshi" ", our_funding_satoshi" @@ -1515,11 +1516,12 @@ void wallet_inflight_add(struct wallet *w, struct channel_inflight *inflight) ", locked_scid" ", i_sent_sigs" ") VALUES (" - "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);")); + "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);")); db_bind_u64(stmt, inflight->channel->dbid); db_bind_txid(stmt, &inflight->funding->outpoint.txid); db_bind_int(stmt, inflight->funding->outpoint.n); + db_bind_int(stmt, inflight->funding_tx_index); db_bind_int(stmt, inflight->funding->feerate); db_bind_amount_sat(stmt, inflight->funding->total_funds); db_bind_amount_sat(stmt, inflight->funding->our_funds); @@ -1714,6 +1716,7 @@ wallet_stmt2inflight(struct wallet *w, struct db_stmt *stmt, i_sent_sigs = db_col_int(stmt, "i_sent_sigs"); inflight = new_inflight(chan, remote_funding, &funding, + db_col_int(stmt, "funding_tx_index"), db_col_int(stmt, "funding_feerate"), funding_sat, our_funding_sat, @@ -1765,6 +1768,7 @@ static bool wallet_channel_load_inflights(struct wallet *w, stmt = db_prepare_v2(w->db, SQL("SELECT" " funding_tx_id" ", funding_tx_outnum" + ", funding_tx_index" ", funding_feerate" ", funding_satoshi" ", our_funding_satoshi" @@ -2132,6 +2136,7 @@ static struct channel *wallet_stmt2channel(struct wallet *w, struct db_stmt *stm db_col_u64(stmt, "next_index_remote"), db_col_u64(stmt, "next_htlc_id"), &funding, + db_col_int(stmt, "funding_tx_index"), funding_sat, push_msat, our_funding_sat, @@ -2379,6 +2384,7 @@ static bool wallet_channels_load_active(struct wallet *w) ", next_htlc_id" ", funding_tx_id" ", funding_tx_outnum" + ", funding_tx_index" ", funding_satoshi" ", our_funding_satoshi" ", funding_locked_remote" @@ -2680,6 +2686,7 @@ void wallet_channel_save(struct wallet *w, struct channel *chan) " next_htlc_id=?," " funding_tx_id=?," " funding_tx_outnum=?," + " funding_tx_index=?," " funding_satoshi=?," " our_funding_satoshi=?," " funding_locked_remote=?," @@ -2745,6 +2752,7 @@ void wallet_channel_save(struct wallet *w, struct channel *chan) db_bind_sha256d(stmt, &chan->funding.txid.shad); db_bind_int(stmt, chan->funding.n); + db_bind_int(stmt, chan->funding_tx_index); db_bind_amount_sat(stmt, chan->funding_sats); db_bind_amount_sat(stmt, chan->our_funds); db_bind_int(stmt, chan->remote_channel_ready); From a762d4d278a4bc0a5ed8372f8a683af27d9299e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADckolas=20Goline?= Date: Wed, 24 Jun 2026 18:40:12 -0300 Subject: [PATCH 3/3] channeld: detect splice on reestablish via funding_tx_index, not txid is_splice_active compared the peer's my_current_funding_locked txid against peer->channel->funding.txid, which is wrong: once a splice locks, funding.txid becomes the splice txid, both sides agree on it, the check falls through, and channel_ready is incorrectly retransmitted on reconnect. Resolve the txid to its funding_tx_index (the current channel funding or a pending splice inflight) and treat > 0 as a splice. Changelog-Fixed: channeld: correctly detect splice transactions when deciding whether to retransmit `channel_ready` on reconnect. --- channeld/channeld.c | 22 +++++++++++++++++++--- tests/test_splicing_disconnect.py | 4 +++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/channeld/channeld.c b/channeld/channeld.c index 8476b801dfa0..02f888d19630 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -5645,6 +5645,23 @@ static bool capture_premature_msg(const u8 ***shit_lnd_says, const u8 *msg) return true; } +/* Returns the funding_tx_index of the funding tx with this txid: the current + * channel funding, or a pending splice inflight. Returns 0 (the original + * funding) if unknown. + */ +static u32 funding_tx_index_for_txid(const struct peer *peer, + const struct bitcoin_txid *txid) +{ + if (bitcoin_txid_eq(txid, &peer->channel->funding.txid)) + return peer->channel->funding_tx_index; + for (size_t i = 0; i < tal_count(peer->splice_state->inflights); i++) { + const struct inflight *inf = peer->splice_state->inflights[i]; + if (bitcoin_txid_eq(txid, &inf->outpoint.txid)) + return inf->funding_tx_index; + } + return 0; +} + static void peer_reconnect(struct peer *peer, const struct secret *last_remote_per_commit_secret) { @@ -5974,9 +5991,8 @@ static void peer_reconnect(struct peer *peer, || remote_next_funding || (recv_tlvs && recv_tlvs->my_current_funding_locked - && !bitcoin_txid_eq( - &recv_tlvs->my_current_funding_locked->my_current_funding_locked_txid, - &peer->channel->funding.txid)); + && funding_tx_index_for_txid(peer, + &recv_tlvs->my_current_funding_locked->my_current_funding_locked_txid) > 0); /* BOLT #2: * diff --git a/tests/test_splicing_disconnect.py b/tests/test_splicing_disconnect.py index 4baa848e5a70..fa8e2bc65ccd 100644 --- a/tests/test_splicing_disconnect.py +++ b/tests/test_splicing_disconnect.py @@ -69,7 +69,6 @@ def test_splice_disconnect_sig(node_factory, bitcoind): @pytest.mark.openchannel('v1') @pytest.mark.openchannel('v2') @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') -@pytest.mark.xfail(strict=True, reason="channel_ready wrongly retransmitted after splice until funding_tx_index detection") def test_splice_reconnect_after_lock_no_channel_ready(node_factory, bitcoind): # Once a splice locks, channel funding txid is updated to the splice txid. # On reconnect we must still recognise the peer's `my_current_funding_locked` @@ -104,6 +103,9 @@ def test_splice_reconnect_after_lock_no_channel_ready(node_factory, bitcoind): # the new funding_tx_index columns) and reconnects/reestablishes. l1.restart() + # Force the reconnect rather than waiting on auto-reconnect backoff. + l1.rpc.connect(l2.info['id'], 'localhost', l2.port) + l1.daemon.wait_for_log(r'peer_in WIRE_CHANNEL_REESTABLISH') l2.daemon.wait_for_log(r'peer_in WIRE_CHANNEL_REESTABLISH') l1.daemon.wait_for_log(r'billboard: Channel ready for use.')