dualopend: handle ANNOUNCEMENT_SIGNATURES from peer.
What changed, and why it matters
This patch fixes a bug where Core Lightning's dual-funding channel opener (dualopend) would crash with a 'BROKEN' error if a peer sent ANNOUNCEMENT_SIGNATURES messages before the channel had fully transitioned to the normal channel daemon. The fix simply forwards those messages to the main lightningd process, just as the regular channel daemon already does. Without the patch, a peer could cause transient connection failures and daemon restarts during channel setup.
Apply the patch. It is a straightforward correctness fix that prevents a peer from causing dualopend to die during channel establishment. No immediate incident response is required beyond normal patching, but operators running dual-funded channels should update to avoid transient connection failures.
Security signals we found
daemon crash / subdaemon death triggered by a peer message
peer-induced transient failure in channel setup
unexpected-message handling gap between dualopend and channeld
BROKEN log indicates a code path the developers considered impossible
Evidence from the diff
In the dual-funding flow, the peer can send WIRE_ANNOUNCEMENT_SIGNATURES while dualopend still owns the channel (before handoff to channeld). Previously, dualopend treated this as an unexpected message, logged a BROKEN error, and dropped the connection. The commit adds handling for WIRE_ANNOUNCEMENT_SIGNATURES in openingd/dualopend.c, parsing the message and relaying it via a new internal wire type WIRE_DUALOPEND_GOT_ANNOUNCEMENT to lightningd/dual_open_control.c, which calls channel_gossip_got_announcement_sigs(). This mirrors channeld’s existing behavior and prevents an unnecessary daemon death and peer transient failure.
Changed components
openingd/dualopend.clightningd/dual_open_control.copeningd/dualopend_wire.csvdual-funding channel establishment flowInspect captured patch +54 / −1
diff --git a/lightningd/dual_open_control.c b/lightningd/dual_open_control.c
index 3ad5cfb..1e46c06 100644
--- a/lightningd/dual_open_control.c
+++ b/lightningd/dual_open_control.c
@@ -3696,6 +3696,28 @@ static void handle_commit_received(struct subd *dualopend,
abort();
}
+static void handle_dualopend_got_announcement(struct subd *dualopend, const u8 *msg)
+{
+ struct channel *channel = dualopend->channel;
+ secp256k1_ecdsa_signature remote_ann_node_sig;
+ secp256k1_ecdsa_signature remote_ann_bitcoin_sig;
+ struct short_channel_id scid;
+
+ if (!fromwire_dualopend_got_announcement(msg,
+ &scid,
+ &remote_ann_node_sig,
+ &remote_ann_bitcoin_sig)) {
+ channel_internal_error(channel,
+ "bad dualopend_got_announcement %s",
+ tal_hex(tmpctx, msg));
+ return;
+ }
+
+ channel_gossip_got_announcement_sigs(channel, scid,
+ &remote_ann_node_sig,
+ &remote_ann_bitcoin_sig);
+}
+
static unsigned int dual_opend_msg(struct subd *dualopend,
const u8 *msg, const int *fds)
{
@@ -3758,6 +3780,9 @@ static unsigned int dual_opend_msg(struct subd *dualopend,
case WIRE_DUALOPEND_UPDATE_REQUIRE_CONFIRMED:
handle_update_require_confirmed(dualopend, msg);
return 0;
+ case WIRE_DUALOPEND_GOT_ANNOUNCEMENT:
+ handle_dualopend_got_announcement(dualopend, msg);
+ return 0;
/* Messages we send */
case WIRE_DUALOPEND_INIT:
case WIRE_DUALOPEND_REINIT:
diff --git a/openingd/dualopend.c b/openingd/dualopend.c
index d140d37..8e902fa 100644
--- a/openingd/dualopend.c
+++ b/openingd/dualopend.c
@@ -4169,12 +4169,33 @@ static u8 *handle_master_in(struct state *state)
case WIRE_DUALOPEND_VALIDATE_LEASE:
case WIRE_DUALOPEND_VALIDATE_INPUTS:
case WIRE_DUALOPEND_UPDATE_REQUIRE_CONFIRMED:
+ case WIRE_DUALOPEND_GOT_ANNOUNCEMENT:
break;
}
status_failed(STATUS_FAIL_MASTER_IO,
"Unknown msg %s", tal_hex(tmpctx, msg));
}
+static void handle_announcement_signatures(struct state *state, const u8 *msg)
+{
+ struct channel_id chanid;
+ struct short_channel_id remote_scid;
+ secp256k1_ecdsa_signature remote_node_sig, remote_bitcoin_sig;
+
+ if (!fromwire_announcement_signatures(msg,
+ &chanid,
+ &remote_scid,
+ &remote_node_sig,
+ &remote_bitcoin_sig))
+ open_err_fatal(state, "Bad announcement_signatures %s", tal_hex(msg, msg));
+
+ wire_sync_write(REQ_FD,
+ take(towire_dualopend_got_announcement(NULL,
+ remote_scid,
+ &remote_node_sig,
+ &remote_bitcoin_sig)));
+}
+
/*~ Standard "peer sent a message, handle it" demuxer. Though it really only
* handles a few messages, we use the standard form as principle of least
* surprise. */
@@ -4221,6 +4242,9 @@ static u8 *handle_peer_in(struct state *state)
case WIRE_TX_ABORT:
handle_tx_abort(state, msg);
return NULL;
+ case WIRE_ANNOUNCEMENT_SIGNATURES:
+ handle_announcement_signatures(state, msg);
+ return NULL;
/* Otherwise we fall through */
case WIRE_INIT:
case WIRE_ERROR:
@@ -4239,7 +4263,6 @@ static u8 *handle_peer_in(struct state *state)
case WIRE_UPDATE_FEE:
case WIRE_UPDATE_BLOCKHEIGHT:
case WIRE_CHANNEL_REESTABLISH:
- case WIRE_ANNOUNCEMENT_SIGNATURES:
case WIRE_GOSSIP_TIMESTAMP_FILTER:
case WIRE_ONION_MESSAGE:
case WIRE_ACCEPT_CHANNEL2:
diff --git a/openingd/dualopend_wire.csv b/openingd/dualopend_wire.csv
index 1c2589a..3484139 100644
--- a/openingd/dualopend_wire.csv
+++ b/openingd/dualopend_wire.csv
@@ -288,3 +288,8 @@ msgdata,dualopend_validate_lease,their_pubkey,pubkey,
msgtype,dualopend_validate_lease_reply,7127
msgdata,dualopend_validate_lease_reply,err_msg,?wirestring,
+
+msgtype,dualopend_got_announcement,7031
+msgdata,dualopend_got_announcement,scid,short_channel_id,
+msgdata,dualopend_got_announcement,remote_ann_node_sig,secp256k1_ecdsa_signature,
+msgdata,dualopend_got_announcement,remote_ann_bitcoin_sig,secp256k1_ecdsa_signature,
Why this scored 34/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.