splice: Update `tx_abort` for mulitple splices
What changed, and why it matters
This change updates how a Bitcoin Lightning node cancels in-progress channel funding changes ('splices'). When several splices are pending at once, the node now tells its peer exactly which splice is being cancelled, instead of always assuming it is the most recent one. The commit message says this prevents the wrong splice candidate from being aborted when multiple are pending.
Treat as a low-to-moderate reliability/correctness fix. Review whether the NULL inflight cases during balance checks can ever race with an existing inflight, and add tests covering multiple concurrent splice candidates and abort paths. No urgent security deployment is indicated by the diff alone.
Security signals we found
Protocol-state correctness fix for multi-splice tx_abort handling
Potential wrong-splice-abort behavior when multiple inflight splice candidates exist
No explicit memory corruption, overflow, or authentication bypass in diff
Evidence from the diff
The patch modifies channeld/channeld.c so that splice_abort() takes an explicit struct inflight pointer rather than always calling last_inflight(peer). Call sites that already know which splice is failing pass that inflight; call sites that abort before an inflight exists pass NULL. The user-initiated abort path in handle_abort_req() now passes last_inflight(peer), and a reconnect-time abort also passes NULL. This is a correctness fix for multi-splice handling, not a memory-safety or cryptographic bug.
Changed components
channeld/channeld.csplice_abort()handle_abort_req()peer_reconnect()check_balances()Inspect captured patch +15 / −13
diff --git a/channeld/channeld.c b/channeld/channeld.c
index 55b38e62..b5855d9a 100644
--- a/channeld/channeld.c
+++ b/channeld/channeld.c
@@ -1913,9 +1913,9 @@ static void check_tx_abort(struct peer *peer, const u8 *msg, struct bitcoin_txid
exit(0);
}
-static void splice_abort(struct peer *peer, const char *fmt, ...)
+static void splice_abort(struct peer *peer, struct inflight *inflight,
+ const char *fmt, ...)
{
- struct inflight *inflight = last_inflight(peer);
struct bitcoin_outpoint *outpoint;
u8 *msg;
char *reason;
@@ -3384,7 +3384,8 @@ static struct amount_sat calc_balance(struct peer *peer)
}
/* Returns the total channel funding output amount if all checks pass.
- * Otherwise, exits via peer_failed_warn. DTODO: Change to `tx_abort`. */
+ * Otherwise, exits via peer_failed_warn.
+ * Note: Should only be called before adding splice to inflights. */
static struct amount_sat check_balances(struct peer *peer,
enum tx_role our_role,
const struct wally_psbt *psbt,
@@ -3477,7 +3478,7 @@ static struct amount_sat check_balances(struct peer *peer,
*/
if (!amount_msat_add_sat_s64(&funding_amount, funding_amount,
peer->splicing->opener_relative))
- splice_abort(peer, "Splice initiator did not provide enough"
+ splice_abort(peer, NULL, "Splice initiator did not provide enough"
" funding, funding_amount: %s, opener_relative:"
" %"PRIu64,
fmt_amount_msat(tmpctx, funding_amount),
@@ -3494,7 +3495,7 @@ static struct amount_sat check_balances(struct peer *peer,
if (!amount_msat_add_sat_s64(&funding_amount, funding_amount,
peer->splicing->accepter_relative))
- splice_abort(peer, "Splice accepter did not provide enough"
+ splice_abort(peer, NULL, "Splice accepter did not provide enough"
" funding");
if (!amount_msat_add_sat_s64(&out[TX_ACCEPTER], out[TX_ACCEPTER],
peer->splicing->accepter_relative))
@@ -3511,7 +3512,7 @@ static struct amount_sat check_balances(struct peer *peer,
out[TX_INITIATOR],
true);
wire_sync_write(MASTER_FD, take(msg));
- splice_abort(peer,
+ splice_abort(peer, NULL,
"Initiator funding is less than commited"
" amount. Initiator contributing %s but they"
" committed to %s. Pending offered HTLC"
@@ -3538,7 +3539,7 @@ static struct amount_sat check_balances(struct peer *peer,
out[TX_INITIATOR],
true);
wire_sync_write(MASTER_FD, take(msg));
- splice_abort(peer,
+ splice_abort(peer, NULL,
"Accepter funding is less than commited"
" amount. Accepter contributing %s but they"
" committed to %s. Pending offered HTLC"
@@ -3581,7 +3582,7 @@ static struct amount_sat check_balances(struct peer *peer,
msg = towire_channeld_splice_feerate_error(NULL, initiator_fee,
false);
wire_sync_write(MASTER_FD, take(msg));
- splice_abort(peer,
+ splice_abort(peer, NULL,
"%s fee (%s) was too low, must be at least %s",
opener ? "Our" : "Your",
fmt_amount_msat(tmpctx, initiator_fee),
@@ -3601,7 +3602,7 @@ static struct amount_sat check_balances(struct peer *peer,
wire_sync_write(MASTER_FD, take(msg));
- splice_abort(peer,
+ splice_abort(peer, NULL,
"Our own fee (%s) is too high to use without"
" forcing. Splice feerate %"PRIu32
" x weight %lu / 1000 = %s (max)",
@@ -3615,7 +3616,7 @@ static struct amount_sat check_balances(struct peer *peer,
msg = towire_channeld_splice_feerate_error(NULL, accepter_fee,
false);
wire_sync_write(MASTER_FD, take(msg));
- splice_abort(peer,
+ splice_abort(peer, NULL,
"%s fee (%s) was too low, must be at least %s"
" weight: %"PRIu64", feerate_max: %"PRIu32,
opener ? "Your" : "Our",
@@ -3629,7 +3630,7 @@ static struct amount_sat check_balances(struct peer *peer,
msg = towire_channeld_splice_feerate_error(NULL, accepter_fee,
true);
wire_sync_write(MASTER_FD, take(msg));
- splice_abort(peer,
+ splice_abort(peer, NULL,
"Our own fee (%s) was too high, max without"
" forcing is %s.",
fmt_amount_msat(tmpctx, accepter_fee),
@@ -5054,7 +5055,7 @@ static void handle_abort_req(struct peer *peer, const u8 *inmsg)
if (!fromwire_channeld_abort(inmsg))
master_badmsg(WIRE_CHANNELD_ABORT, inmsg);
- splice_abort(peer, "requested by user");
+ splice_abort(peer, last_inflight(peer), "requested by user");
}
static void peer_in(struct peer *peer, const u8 *msg)
@@ -5915,7 +5916,8 @@ static void peer_reconnect(struct peer *peer,
" channel, ignoring it: %s",
fmt_bitcoin_outpoint(tmpctx, &peer->channel->funding));
else
- splice_abort(peer, "next_funding_txid not recognized.");
+ splice_abort(peer, NULL,
+ "next_funding_txid not recognized.");
}
/* BOLT #2:
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.