From a4ef31fd5a4db3e7a6b2b18f17d9137d12318f0f Mon Sep 17 00:00:00 2001 From: Dusty Daemon Date: Mon, 5 Jan 2026 16:45:41 -0500 Subject: [PATCH 1/4] splice: Fix log message Update log message to match the behavior of the check its logging about. Changelog-None --- channeld/channeld.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/channeld/channeld.c b/channeld/channeld.c index bef107c66f4e..35fc2c3af328 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -3015,13 +3015,17 @@ static const u8 *peer_expect_msg_three(const tal_t *ctx, msg = peer_read(ctx, peer->pps); type = fromwire_peektype(msg); + if (type == WIRE_ERROR) + abort(); if (type != expect_type && type != second_allowed_type && type != third_allowed_type) peer_failed_warn(peer->pps, &peer->channel_id, "Got incorrect message from peer: %s" - " (should be %s) [%s]", + " (should be %s or %s or %s) [%s]", peer_wire_name(type), peer_wire_name(expect_type), + peer_wire_name(second_allowed_type), + peer_wire_name(third_allowed_type), sanitize_error(tmpctx, msg, &peer->channel_id)); return msg; @@ -5705,6 +5709,8 @@ static void peer_reconnect(struct peer *peer, do { clean_tmpctx(); msg = peer_read(tmpctx, peer->pps); + check_tx_abort(peer, msg, + inflight ? &inflight->outpoint.txid : NULL); } while (handle_peer_error_or_warning(peer->pps, msg) || capture_premature_msg(&premature_msgs, msg)); From 5f34bedfb4422403958e31f025b9559448d8a50d Mon Sep 17 00:00:00 2001 From: Dusty Daemon Date: Wed, 7 Jan 2026 17:32:15 -0500 Subject: [PATCH 2/4] splice: Handle CHANNEL_READY during splice resume In some situations Eclair sends CHANNEL_READY before resuming the splice in reestablish. This update allows one of these messages to be processed if it comes in during the splice resume process. Changelog-None --- channeld/channeld.c | 61 ++++++++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/channeld/channeld.c b/channeld/channeld.c index 35fc2c3af328..9573780d2af1 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -3004,11 +3004,12 @@ static struct wally_psbt *next_splice_step(const tal_t *ctx, return ictx->desired_psbt; } -static const u8 *peer_expect_msg_three(const tal_t *ctx, +static const u8 *peer_expect_msg_four(const tal_t *ctx, struct peer *peer, enum peer_wire expect_type, enum peer_wire second_allowed_type, - enum peer_wire third_allowed_type) + enum peer_wire third_allowed_type, + enum peer_wire fourth_allowed_type) { u8 *msg; enum peer_wire type; @@ -3017,20 +3018,28 @@ static const u8 *peer_expect_msg_three(const tal_t *ctx, type = fromwire_peektype(msg); if (type == WIRE_ERROR) abort(); - if (type != expect_type && type != second_allowed_type - && type != third_allowed_type) + if (type != expect_type + && type != second_allowed_type + && type != third_allowed_type + && type != fourth_allowed_type) peer_failed_warn(peer->pps, &peer->channel_id, "Got incorrect message from peer: %s" - " (should be %s or %s or %s) [%s]", + " (should be %s or %s or %s of %s) [%s]", peer_wire_name(type), peer_wire_name(expect_type), peer_wire_name(second_allowed_type), peer_wire_name(third_allowed_type), + peer_wire_name(fourth_allowed_type), sanitize_error(tmpctx, msg, &peer->channel_id)); return msg; } +/* In some circumstances Eclair send CHANNEL_READY after CHANNEL_REESTABLISH but + * before resuming splice negotiation, so we need a way to process it in this + * order. */ +static void peer_in(struct peer *peer, const u8 *msg); + /* The question of "who signs splice commitments first" is the same order as the * splice `tx_signature`s are. This function handles sending & receiving the * required commitments as part of the splicing process. @@ -3042,7 +3051,8 @@ static struct commitsig *interactive_send_commitments(struct peer *peer, size_t inflight_index, bool send_commitments, bool recv_commitments, - const u8 **msg_received) + const u8 **msg_received, + int allowed_premature_msg) { struct commitsig_info *result; const u8 *msg; @@ -3084,10 +3094,21 @@ static struct commitsig *interactive_send_commitments(struct peer *peer, result = NULL; if (recv_commitments) { - msg = peer_expect_msg_three(tmpctx, peer, - WIRE_COMMITMENT_SIGNED, - WIRE_TX_SIGNATURES, - WIRE_TX_ABORT); + msg = peer_expect_msg_four(tmpctx, peer, + WIRE_COMMITMENT_SIGNED, + WIRE_TX_SIGNATURES, + WIRE_TX_ABORT, + allowed_premature_msg); + + if (allowed_premature_msg + && fromwire_peektype(msg) == allowed_premature_msg) { + peer_in(peer, msg); + msg = peer_expect_msg_four(tmpctx, peer, + WIRE_COMMITMENT_SIGNED, + WIRE_TX_SIGNATURES, + WIRE_TX_ABORT, + 0); + } check_tx_abort(peer, msg, &inflight->outpoint.txid); @@ -3637,7 +3658,8 @@ static void resume_splice_negotiation(struct peer *peer, bool send_commitments, bool recv_commitments, bool send_signature, - bool recv_signature) + bool recv_signature, + int allowed_premature_msg) { struct inflight *inflight = last_inflight(peer); enum tx_role our_role = inflight->i_am_initiator @@ -3696,7 +3718,8 @@ static void resume_splice_negotiation(struct peer *peer, last_inflight_index(peer), send_commitments, recv_commitments, - &msg_received); + &msg_received, + allowed_premature_msg); check_tx_abort(peer, msg_received, &inflight->outpoint.txid); @@ -4254,7 +4277,7 @@ static void splice_accepter(struct peer *peer, const u8 *inmsg) peer->splice_state->count++; - resume_splice_negotiation(peer, true, true, true, true); + resume_splice_negotiation(peer, true, true, true, true, 0); } /* splice_initiator runs when splice_ack is received by the other side. It @@ -4560,7 +4583,7 @@ static void splice_initiator_user_finalized(struct peer *peer) their_commit = interactive_send_commitments(peer, new_inflight->psbt, our_role, last_inflight_index(peer), - true, true, NULL); + true, true, NULL, 0); new_inflight->last_tx = tal_steal(new_inflight, their_commit->tx); new_inflight->last_sig = their_commit->commit_signature; @@ -4576,7 +4599,7 @@ static void splice_initiator_user_finalized(struct peer *peer) peer->splicing->force_sign_first); if (!sign_first) - resume_splice_negotiation(peer, false, false, false, true); + resume_splice_negotiation(peer, false, false, false, true, 0); outmsg = towire_channeld_splice_confirmed_update(NULL, new_inflight->psbt, @@ -4777,7 +4800,7 @@ static void splice_initiator_user_signed(struct peer *peer, const u8 *inmsg) audit_psbt(inflight->psbt, inflight->psbt); assert(tal_parent(inflight->psbt) != tmpctx); - resume_splice_negotiation(peer, false, false, true, sign_first); + resume_splice_negotiation(peer, false, false, true, sign_first, 0); audit_psbt(inflight->psbt, inflight->psbt); assert(tal_parent(inflight->psbt) != tmpctx); @@ -5763,7 +5786,8 @@ static void peer_reconnect(struct peer *peer, false, !inflight->last_tx, false, - true); + true, + WIRE_CHANNEL_READY); } else if (bitcoin_txid_eq(&remote_next_funding->next_funding_txid, &inflight->outpoint.txid)) { /* Don't send sigs unless we have theirs */ @@ -5779,7 +5803,8 @@ static void peer_reconnect(struct peer *peer, : false, local_next_funding && !inflight->last_tx, true, - local_next_funding); + local_next_funding, + WIRE_CHANNEL_READY); } else if (bitcoin_txid_eq(&remote_next_funding->next_funding_txid, &peer->channel->funding.txid)) { peer_failed_err(peer->pps, From 8402b0a4d295985893ea6d53fc2ce1f9099c95de Mon Sep 17 00:00:00 2001 From: Dusty Daemon Date: Thu, 8 Jan 2026 14:12:05 -0500 Subject: [PATCH 3/4] splice: RPC error handling fix Splice commands can fail because of external reasons, for example our peer rejecting the splice. These external reasons eat the active `splice_command` so we need to stop storing the `splice_command` in callbacks and instead pull it from the `splice_commands` list which automatically removes the object when its consumed. This corrects a lightningd crash under certain RPC circumstances. Changelog-None --- lightningd/channel_control.c | 44 ++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/lightningd/channel_control.c b/lightningd/channel_control.c index 627e43604cbc..917f8af2a5cc 100644 --- a/lightningd/channel_control.c +++ b/lightningd/channel_control.c @@ -482,7 +482,6 @@ static void handle_splice_lookup_tx(struct lightningd *ld, /* Extra splice data we want to store for bitcoin send tx interface */ struct send_splice_info { - struct splice_command *cc; struct channel *channel; const struct bitcoin_tx *final_tx; u32 output_index; @@ -497,6 +496,7 @@ static void handle_tx_broadcast(struct send_splice_info *info) struct bitcoin_txid txid; u8 *tx_bytes; int num_utxos; + struct splice_command *cc; tx_bytes = linearize_tx(tmpctx, info->final_tx); bitcoin_txid(info->final_tx, &txid); @@ -508,15 +508,17 @@ static void handle_tx_broadcast(struct send_splice_info *info) if (num_utxos) wallet_transaction_add(ld->wallet, info->final_tx->wtx, 0, 0); - if (info->cc) { - response = json_stream_success(info->cc->cmd); + cc = splice_command_for_chan(ld, info->channel); + + if (cc) { + response = json_stream_success(cc->cmd); json_add_hex(response, "tx", tx_bytes, tal_bytelen(tx_bytes)); json_add_txid(response, "txid", &txid); json_add_u32(response, "outnum", info->output_index); json_add_psbt(response, "psbt", info->psbt); - was_pending(command_success(info->cc->cmd, response)); + was_pending(command_success(cc->cmd, response)); } } @@ -527,25 +529,32 @@ static void check_utxo_block(struct bitcoind *bitcoind UNUSED, void *arg) { struct send_splice_info *info = arg; + struct lightningd *ld = info->channel->peer->ld; + struct splice_command *cc; if(!txout) { - if (info->cc) - was_pending(command_fail(info->cc->cmd, + cc = splice_command_for_chan(ld, info->channel); + if (cc) + was_pending(command_fail(cc->cmd, SPLICE_BROADCAST_FAIL, - "Error broadcasting splice " - "tx: %s. Unsent tx discarded " - "%s.", + "Error broadcasting splice" + " %s. Unsent tx discarded" + " %s.", info->err_msg, - fmt_wally_tx(tmpctx, - info->final_tx->wtx))); + info->final_tx && info->final_tx->wtx ? + fmt_wally_tx(tmpctx, + info->final_tx->wtx) + : "NULL")); log_unusual(info->channel->log, - "Error broadcasting splice " - "tx: %s. Unsent tx discarded " - "%s.", + "Error broadcasting splice" + " %s. Unsent tx discarded" + " %s.", info->err_msg, - fmt_wally_tx(tmpctx, - info->final_tx->wtx)); + info->final_tx && info->final_tx->wtx ? + fmt_wally_tx(tmpctx, + info->final_tx->wtx) + : "NULL"); } else handle_tx_broadcast(info); @@ -558,8 +567,6 @@ static void send_splice_tx_done(struct bitcoind *bitcoind UNUSED, bool success, const char *msg, struct send_splice_info *info) { - /* A NULL value of `info->cc` means we got here without user intiation. - * This means we are the ACCEPTER side of the splice */ struct lightningd *ld = info->channel->peer->ld; struct bitcoin_outpoint outpoint; @@ -593,7 +600,6 @@ static void send_splice_tx(struct channel *channel, struct send_splice_info *info = tal(NULL, struct send_splice_info); - info->cc = tal_steal(info, cc); info->channel = channel; info->final_tx = tal_steal(info, tx); info->output_index = output_index; From bdfbdf091bf44c719e781a4f978ca08409b52e97 Mon Sep 17 00:00:00 2001 From: Dusty Daemon Date: Mon, 5 Jan 2026 16:27:17 -0500 Subject: [PATCH 4/4] reestablish: Send announcement sigs when asked A new TLV in `channel_reestablish` lets our peer request announcement signatures. This updates CLN to (re)send them when requested. As per splice spec PR https://github.com/lightning/bolts/pull/1160/changes#diff-ed04ca2c673fd6aabde69389511fa9ee60cb44d6b2ef6c88b549ffaa753d6afeR3303-R3308 Changelog-None --- channeld/channeld.c | 21 ++++++++++++++++- channeld/channeld_wire.csv | 1 + lightningd/channel_control.c | 15 ++++++++++-- lightningd/channel_gossip.c | 39 ++++++++++++++++++++------------ lightningd/channel_gossip.h | 3 ++- tests/plugins/channeld_fakenet.c | 2 +- wallet/test/run-wallet.c | 3 +++ 7 files changed, 65 insertions(+), 19 deletions(-) diff --git a/channeld/channeld.c b/channeld/channeld.c index 9573780d2af1..524bd16901a1 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -5526,6 +5526,7 @@ static void peer_reconnect(struct peer *peer, const struct secret *last_remote_per_commit_secret) { struct channel_id channel_id; + bool announcement_sigs_requested; /* Note: BOLT #2 uses these names! */ u64 next_commitment_number, next_revocation_number; bool retransmit_revoke_and_ack, retransmit_commitment_signed; @@ -6020,6 +6021,24 @@ static void peer_reconnect(struct peer *peer, if (retransmit_revoke_and_ack && peer->last_was_revoke) resend_revoke(peer); + /* BOLT-??? #2 + * 1. type: 5 (`my_current_funding_locked`) + * 2. data: + * * [`sha256`:`my_current_funding_locked_txid`] + * * [`byte`:`retransmit_flags`] + * + * The `retransmit_flags` bitfield is used to let our peer know which messages + * we expect them to retransmit after the reconnection: + * + * | Bit Position | Name | + * | ------------- | --------------------------| + * | 0 | `announcement_signatures` | + */ + announcement_sigs_requested = false; + if (recv_tlvs && recv_tlvs->my_current_funding_locked + && recv_tlvs->my_current_funding_locked->retransmit_flags & 1) + announcement_sigs_requested = true; + /* BOLT #2: * * - upon reconnection: @@ -6032,7 +6051,7 @@ static void peer_reconnect(struct peer *peer, tal_free(send_tlvs); /* We've reestablished! */ - wire_sync_write(MASTER_FD, take(towire_channeld_reestablished(NULL))); + wire_sync_write(MASTER_FD, take(towire_channeld_reestablished(NULL, announcement_sigs_requested))); /* Corner case: we didn't send shutdown before because update_add_htlc * pending, but now they're cleared by restart, and we're actually diff --git a/channeld/channeld_wire.csv b/channeld/channeld_wire.csv index be5a5c6dee36..3e9a7170edc1 100644 --- a/channeld/channeld_wire.csv +++ b/channeld/channeld_wire.csv @@ -78,6 +78,7 @@ msgdata,channeld_init,scid_alias,short_channel_id, # channeld->lightningd: successfully negotated reestablishment. msgtype,channeld_reestablished,1101 +msgdata,channeld_reestablished,announcement_sigs_requested,bool, # master->channeld funding hit new depth(funding locked if >= lock depth) # short_channel_id != NULL once we have 3+ confirmations diff --git a/lightningd/channel_control.c b/lightningd/channel_control.c index 917f8af2a5cc..2e7799e56d6c 100644 --- a/lightningd/channel_control.c +++ b/lightningd/channel_control.c @@ -1311,6 +1311,17 @@ static void peer_got_shutdown(struct channel *channel, const u8 *msg) wallet_channel_save(ld->wallet, channel); } +static void peer_channeld_reestablished(struct channel *channel, const u8* msg) +{ + bool announcement_sigs_requested; + if (!fromwire_channeld_reestablished(msg, &announcement_sigs_requested)) + channel_internal_error(channel, + "bad channeld_reestablished %s", + tal_hex(channel, msg)); + + channel_gossip_channel_reestablished(channel, announcement_sigs_requested); +} + void channel_fallen_behind(struct channel *channel) { channel->has_future_per_commitment_point = true; @@ -1565,7 +1576,7 @@ static unsigned channel_msg(struct subd *sd, const u8 *msg, const int *fds) peer_got_shutdown(sd->channel, msg); break; case WIRE_CHANNELD_REESTABLISHED: - channel_gossip_channel_reestablished(sd->channel); + peer_channeld_reestablished(sd->channel, msg); break; case WIRE_CHANNELD_SHUTDOWN_COMPLETE: /* We expect 1 fd. */ @@ -1923,7 +1934,7 @@ bool peer_start_channeld(struct channel *channel, /* "Reestablished" if we've just opened. */ if (!reconnected) - channel_gossip_channel_reestablished(channel); + channel_gossip_channel_reestablished(channel, false); /* FIXME: DTODO: Use a pointer to a txid instead of zero'ing one out. */ memset(&txid, 0, sizeof(txid)); diff --git a/lightningd/channel_gossip.c b/lightningd/channel_gossip.c index 01207818a5d5..fcd31c7dc2d7 100644 --- a/lightningd/channel_gossip.c +++ b/lightningd/channel_gossip.c @@ -687,25 +687,22 @@ static void stash_remote_announce_sigs(struct channel *channel, * - MUST NOT send the `announcement_signatures` message. */ -static void send_channel_announce_sigs(struct channel *channel) +static bool send_channel_announce_sigs(struct channel *channel) { /* First 2 + 256 byte are the signatures and msg type, skip them */ const size_t offset = 258; struct lightningd *ld = channel->peer->ld; struct sha256_double hash; - secp256k1_ecdsa_signature local_node_sig, local_bitcoin_sig; struct channel_gossip *cg = channel->channel_gossip; + secp256k1_ecdsa_signature local_node_sig, local_bitcoin_sig; const u8 *ca, *msg; /* Wait until we've exchanged reestablish messages */ if (!channel->reestablished) { log_debug(channel->log, "channel_gossip: not sending channel_announcement_sigs until reestablished"); - return; + return false; } - if (cg->sent_sigs) - return; - ca = create_channel_announcement(tmpctx, channel, *channel->scid, NULL, NULL, NULL, NULL); @@ -722,20 +719,30 @@ static void send_channel_announce_sigs(struct channel *channel) if (!check_signed_hash(&hash, &local_node_sig, &ld->our_pubkey)) { channel_internal_error(channel, "HSM returned an invalid node signature"); - return; + return false; } if (!check_signed_hash(&hash, &local_bitcoin_sig, &channel->local_funding_pubkey)) { channel_internal_error(channel, "HSM returned an invalid bitcoin signature"); - return; + return false; } msg = towire_announcement_signatures(NULL, &channel->cid, *channel->scid, &local_node_sig, &local_bitcoin_sig); msg_to_peer(channel->peer, take(msg)); - cg->sent_sigs = true; + return cg->sent_sigs = true; +} + +static bool send_channel_announce_sigs_once(struct channel *channel) +{ + struct channel_gossip *cg = channel->channel_gossip; + + if (cg->sent_sigs) + return false; + + return send_channel_announce_sigs(channel); } /* Sends channel_announcement */ @@ -829,7 +836,7 @@ static void set_gossip_state(struct channel *channel, case CGOSSIP_ANNOUNCED: /* In case this snuck up on us (fast confirmations), * make sure we sent sigs */ - send_channel_announce_sigs(channel); + send_channel_announce_sigs_once(channel); /* BOLT #7: * A recipient node: @@ -897,7 +904,7 @@ static void update_gossip_state(struct channel *channel) return; case CGOSSIP_WAITING_FOR_MATCHING_PEER_SIGS: case CGOSSIP_WAITING_FOR_ANNOUNCE_DEPTH: - send_channel_announce_sigs(channel); + send_channel_announce_sigs_once(channel); /* fall thru */ case CGOSSIP_WAITING_FOR_SCID: case CGOSSIP_PRIVATE: @@ -1005,7 +1012,7 @@ void channel_gossip_got_announcement_sigs(struct channel *channel, send_our_sigs: /* This only works once, so we won't spam them. */ - send_channel_announce_sigs(channel); + send_channel_announce_sigs_once(channel); } /* Short channel id changed (splice, or reorg). */ @@ -1198,7 +1205,8 @@ static void channel_reestablished_stable(struct channel *channel) } /* Peer has connected and successfully reestablished channel. */ -void channel_gossip_channel_reestablished(struct channel *channel) +void channel_gossip_channel_reestablished(struct channel *channel, + bool announcement_sigs_requested) { channel->reestablished = true; tal_free(channel->stable_conn_timer); @@ -1216,6 +1224,9 @@ void channel_gossip_channel_reestablished(struct channel *channel) /* We can re-xmit sigs once per reconnect */ channel->channel_gossip->sent_sigs = false; + if (announcement_sigs_requested) + send_channel_announce_sigs(channel); + /* BOLT #7: * - Upon reconnection (once the above timing requirements have * been met): @@ -1236,7 +1247,7 @@ void channel_gossip_channel_reestablished(struct channel *channel) check_channel_gossip(channel); return; case CGOSSIP_WAITING_FOR_MATCHING_PEER_SIGS: - send_channel_announce_sigs(channel); + send_channel_announce_sigs_once(channel); /* fall thru */ case CGOSSIP_PRIVATE: case CGOSSIP_WAITING_FOR_ANNOUNCE_DEPTH: diff --git a/lightningd/channel_gossip.h b/lightningd/channel_gossip.h index 6b8e4e203262..b6fae02a204e 100644 --- a/lightningd/channel_gossip.h +++ b/lightningd/channel_gossip.h @@ -43,7 +43,8 @@ void channel_gossip_update_from_gossipd(struct channel *channel, void channel_gossip_init_done(struct lightningd *ld); /* Peer has connected and successfully reestablished channel. */ -void channel_gossip_channel_reestablished(struct channel *channel); +void channel_gossip_channel_reestablished(struct channel *channel, + bool announcement_sigs_requested); /* Peer has disconnected */ void channel_gossip_channel_disconnect(struct channel *channel); diff --git a/tests/plugins/channeld_fakenet.c b/tests/plugins/channeld_fakenet.c index c8281671b85d..7334fb93c540 100644 --- a/tests/plugins/channeld_fakenet.c +++ b/tests/plugins/channeld_fakenet.c @@ -1167,7 +1167,7 @@ static struct channel *handle_init(struct info *info, const u8 *init_msg) channel->htlcs = htlc_map; status_debug("Created channel"); - daemon_conn_send(info->dc, take(towire_channeld_reestablished(NULL))); + daemon_conn_send(info->dc, take(towire_channeld_reestablished(NULL, false))); return channel; } diff --git a/wallet/test/run-wallet.c b/wallet/test/run-wallet.c index 86962e528f65..a2e97a825277 100644 --- a/wallet/test/run-wallet.c +++ b/wallet/test/run-wallet.c @@ -380,6 +380,9 @@ bool fromwire_onchaind_dev_memleak_reply(const void *p UNNEEDED, bool *leak UNNE bool fromwire_openingd_dev_memleak_reply(const void *p UNNEEDED, bool *leak UNNEEDED) { fprintf(stderr, "fromwire_openingd_dev_memleak_reply called!\n"); abort(); } /* Generated stub for get_network_blockheight */ +bool fromwire_channeld_reestablished(const void *p UNNEEDED, bool *leak UNNEEDED) +{ fprintf(stderr, "fromwire_channeld_reestablished called!\n"); abort(); } +/* Generated stub for get_network_blockheight */ u32 get_network_blockheight(const struct chain_topology *topo UNNEEDED) { fprintf(stderr, "get_network_blockheight called!\n"); abort(); } /* Generated stub for hash_cid */