lightningd: use scriptpubkey and blockdepth watches instead of tx watches for dual funding.
What changed, and why it matters
This change refactors how Core Lightning watches for dual-funded channel opening transactions. Instead of watching for a specific transaction ID, it now watches for any transaction that spends to a specific 2-of-2 multisig script (the funding script). This is a robustness improvement that helps handle cases where the funding transaction might be replaced or malleated before confirmation, but the commit message frames it as a design change rather than a security fix.
Treat as a normal code-quality/robustness patch. Review whether the old txid-only watch could have caused operational issues (e.g., a mutated or RBF-replaced dual-funding tx not being tracked), but no urgent security action is indicated by the supplied materials. If a security advisory or CVE is later published, reassess.
Security signals we found
Replaces txid watch with scriptpubkey watch, reducing reliance on a specific funding txid surviving malleability/replacement
Separates reorg handling into distinct opening_reorged_cb callback
Adds explicit candidate-funding validity check before depth watch
No explicit security framing in commit message or diff
Evidence from the diff
The patch in lightningd/dual_open_control.c replaces a txid-based watch (watch_txid) with a scriptPubKey-based watch (watch_scriptpubkey) plus a block-depth watch (watch_blockdepth) for dual-funded channel openings. The new flow: (1) watch for any confirmed output matching the 2-of-2 P2WSH funding script; (2) on finding it, locate the block and update the short channel ID; (3) then watch that block depth for lock-in and reorg handling. The old callback mixed tx arrival, SCID derivation, depth checks, and reorg handling; the new code separates these concerns. The commit message gives no security rationale; it reads as a correctness/robustness refactor for dual-funding transaction tracking.
Changed components
lightningd/dual_open_control.cDual-funded channel opening flowTransaction/reorg watching subsystemInspect captured patch +35 / −19
diff --git a/lightningd/dual_open_control.c b/lightningd/dual_open_control.c
index ed7f732a..f5f63b7b 100644
--- a/lightningd/dual_open_control.c
+++ b/lightningd/dual_open_control.c
@@ -1003,44 +1003,60 @@ static void dualopend_tell_depth(struct channel *channel,
}
static enum watch_result opening_depth_cb(struct lightningd *ld,
- const struct bitcoin_txid *txid,
- const struct bitcoin_tx *tx,
unsigned int depth,
struct channel_inflight *inflight)
{
- struct txlocator *loc;
-
/* Usually, we're here because we're awaiting a lockin, but
* we could also mutual shutdown */
if (inflight->channel->state != DUALOPEND_AWAITING_LOCKIN)
return DELETE_WATCH;
+ if (depth >= inflight->channel->minimum_depth)
+ update_channel_from_inflight(ld, inflight->channel, inflight,
+ false);
+
+ dualopend_tell_depth(inflight->channel, &inflight->funding->outpoint.txid, depth);
+ return KEEP_WATCHING;
+}
+
+static enum watch_result opening_reorged_cb(struct lightningd *ld, struct channel_inflight *inflight)
+{
/* Reorged out? OK, we're not committed yet. */
- if (depth == 0)
- return KEEP_WATCHING;
+ log_info(inflight->channel->log, "Candidate funding tx was in a block, now reorged out");
+ return DELETE_WATCH;
+}
- /* FIXME: Don't do this until we're actually locked in! */
- loc = wallet_transaction_locate(tmpctx, ld->wallet, txid);
+static void dual_funding_found(struct lightningd *ld,
+ const struct bitcoin_tx *tx,
+ u32 outnum,
+ const struct txlocator *loc,
+ struct channel_inflight *inflight)
+{
+ /* Kill it if the channel funding isn't a valid scid */
if (!depthcb_update_scid(inflight->channel,
&inflight->funding->outpoint,
loc))
- return DELETE_WATCH;
-
- if (depth >= inflight->channel->minimum_depth)
- update_channel_from_inflight(ld, inflight->channel, inflight,
- false);
-
- dualopend_tell_depth(inflight->channel, txid, depth);
+ return;
- return KEEP_WATCHING;
+ /* Otherwise, watch for block depth increases (we'll immediately expect one) */
+ watch_blockdepth(inflight, ld->topology, loc->blkheight,
+ opening_depth_cb,
+ opening_reorged_cb,
+ inflight);
}
void watch_opening_inflight(struct lightningd *ld,
struct channel_inflight *inflight)
{
- watch_txid(inflight, ld->topology,
- &inflight->funding->outpoint.txid,
- opening_depth_cb, inflight);
+ const u8 *funding_wscript = bitcoin_redeem_2of2(tmpctx,
+ &inflight->channel->local_funding_pubkey,
+ &inflight->channel->channel_info.remote_fundingkey);
+ watch_scriptpubkey(inflight, ld->topology,
+ take(scriptpubkey_p2wsh(NULL, funding_wscript)),
+ &inflight->funding->outpoint,
+ inflight->funding->total_funds,
+ dual_funding_found,
+ inflight);
}
static void
Why this scored 43/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.