channeld: Add new feerate type ‘splice’
What changed, and why it matters
This commit changes how Core Lightning sets transaction fees for 'splice' operations, which are a way to resize a Lightning channel while it is still open. Previously, splicing used the same fee rate as opening a new channel. The patch introduces a separate 'splice' fee rate and, for the party that opened the channel, adds a small configurable buffer to reduce disagreements with the peer. The change is a refinement of fee policy rather than a clear-cut security fix; it could reduce the chance of a splice transaction getting stuck or rejected, but it does not by itself fix a known exploitable bug.
Treat as a normal feature/refinement commit. If this patch is being backported, verify whether it is part of a larger fix for a disclosed splice-fee issue and look for an accompanying advisory or CVE. No immediate hardening is required based on the diff alone.
Security signals we found
New fee-rate parameter introduced for splice operations
Channel opener now gets a configurable feerate offset for splice transactions
Log lines expanded to include opening and splice feerates
Test fakenet updated to parse new wire field
Evidence from the diff
The patch adds a new feerate_splice field to channeld’s peer state and to the channeld_init and channeld_feerates wire messages. A helper default_feerate() computes a feerate from unilateral_feerate() and, when the local node is the channel opener, adds ld->config.feerate_offset while capping at max_feerate. channel_update_feerates() now sends both feerate_opening and feerate_splice. json_splice_init() uses default_feerate(…, true) instead of opening_feerate() when no feerate is supplied. The change gives more careful control of splice feerates and avoids using the opening feerate for splices, but the diff alone does not demonstrate a vulnerability or a complete fix for one.
Changed components
channeld/channeld.cchanneld/channeld_wire.csvlightningd/channel_control.ctests/plugins/channeld_fakenet.cInspect captured patch +63 / −25
diff --git a/channeld/channeld.c b/channeld/channeld.c
index 113e7a4b..bd256476 100644
--- a/channeld/channeld.c
+++ b/channeld/channeld.c
@@ -76,13 +76,16 @@ struct peer {
/* What (additional) messages the HSM accepts */
u32 *hsm_capabilities;
+ /* The feerate to initiate a splice */
+ u32 feerate_splice;
+
/* Tolerable amounts for feerate (only relevant for fundee). */
u32 feerate_min, feerate_max;
/* Feerate to be used when creating penalty transactions. */
u32 feerate_penalty;
- /* Feerate to be used when opening (or splicing) a channel. */
+ /* Feerate to be used when opening a channel. */
u32 feerate_opening;
/* Local next per-commit point. */
@@ -6374,11 +6377,13 @@ static void handle_feerates(struct peer *peer, const u8 *inmsg)
{
u32 feerate;
- if (!fromwire_channeld_feerates(inmsg, &feerate,
- &peer->feerate_min,
- &peer->feerate_max,
- &peer->feerate_penalty,
- &peer->feerate_opening))
+ if (!fromwire_channeld_feerates(inmsg,
+ &feerate,
+ &peer->feerate_min,
+ &peer->feerate_max,
+ &peer->feerate_penalty,
+ &peer->feerate_opening,
+ &peer->feerate_splice))
master_badmsg(WIRE_CHANNELD_FEERATES, inmsg);
/* BOLT #2:
@@ -6746,6 +6751,7 @@ static void init_channel(struct peer *peer)
&lease_expiry,
&conf[LOCAL], &conf[REMOTE],
&fee_states,
+ &peer->feerate_splice,
&peer->feerate_min,
&peer->feerate_max,
&peer->feerate_penalty,
diff --git a/channeld/channeld_wire.csv b/channeld/channeld_wire.csv
index 0baa87a6..208da303 100644
--- a/channeld/channeld_wire.csv
+++ b/channeld/channeld_wire.csv
@@ -28,6 +28,7 @@ msgdata,channeld_init,lease_expiry,u32,
msgdata,channeld_init,our_config,channel_config,
msgdata,channeld_init,their_config,channel_config,
msgdata,channeld_init,fee_states,fee_states,
+msgdata,channeld_init,feerate_splice,u32,
msgdata,channeld_init,feerate_min,u32,
msgdata,channeld_init,feerate_max,u32,
msgdata,channeld_init,feerate_penalty,u32,
@@ -332,6 +333,7 @@ msgdata,channeld_feerates,min_feerate,u32,
msgdata,channeld_feerates,max_feerate,u32,
msgdata,channeld_feerates,penalty_feerate,u32,
msgdata,channeld_feerates,opening_feerate,u32,
+msgdata,channeld_feerates,feerate_splice,u32,
# master -> channeld: do you have a memleak?
msgtype,channeld_dev_memleak,1033
diff --git a/lightningd/channel_control.c b/lightningd/channel_control.c
index c92f463a..aac597e3 100644
--- a/lightningd/channel_control.c
+++ b/lightningd/channel_control.c
@@ -46,12 +46,37 @@ struct splice_command {
u32 user_psbt_ver;
};
+static u32 default_feerate(struct lightningd *ld, const struct channel *channel,
+ bool add_offset)
+{
+ u32 max_feerate;
+ bool anchors = channel_type_has_anchors(channel->type);
+ u32 feerate = unilateral_feerate(ld->topology, anchors);
+
+ /* Nothing to do if we don't know feerate. */
+ if (!feerate)
+ return 0;
+
+ max_feerate = feerate_max(ld, NULL);
+
+ /* The channel opener should use a slightly higher than minimal feerate
+ * in order to avoid excessive feerate disagreements */
+ if (channel->opener == LOCAL) {
+ feerate += ld->config.feerate_offset;
+ if (feerate > max_feerate)
+ feerate = max_feerate;
+ }
+
+ return feerate;
+}
+
void channel_update_feerates(struct lightningd *ld, const struct channel *channel)
{
u8 *msg;
u32 min_feerate, max_feerate;
bool anchors = channel_type_has_anchors(channel->type);
- u32 feerate = unilateral_feerate(ld->topology, anchors);
+ u32 feerate = default_feerate(ld, channel, (channel->opener == LOCAL));
+ u32 feerate_splice = default_feerate(ld, channel, true);
/* Nothing to do if we don't know feerate. */
if (!feerate)
@@ -63,13 +88,6 @@ void channel_update_feerates(struct lightningd *ld, const struct channel *channe
else
min_feerate = feerate_min(ld, NULL);
max_feerate = feerate_max(ld, NULL);
- /* The channel opener should use a slightly higher than minimal feerate
- * in order to avoid excessive feerate disagreements */
- if (channel->opener == LOCAL) {
- feerate += ld->config.feerate_offset;
- if (feerate > max_feerate)
- feerate = max_feerate;
- }
if (channel->ignore_fee_limits || ld->config.ignore_fee_limits) {
min_feerate = 1;
@@ -77,17 +95,21 @@ void channel_update_feerates(struct lightningd *ld, const struct channel *channe
}
log_debug(ld->log,
- "update_feerates: feerate = %u, min=%u, max=%u, penalty=%u",
+ "update_feerates: feerate = %u, min=%u, max=%u, penalty=%u,"
+ " opening=%u, splicing: %u",
feerate,
min_feerate,
feerate_max(ld, NULL),
- penalty_feerate(ld->topology));
+ penalty_feerate(ld->topology),
+ opening_feerate(ld->topology),
+ feerate_splice);
msg = towire_channeld_feerates(NULL, feerate,
min_feerate,
max_feerate,
penalty_feerate(ld->topology),
- opening_feerate(ld->topology));
+ opening_feerate(ld->topology),
+ feerate_splice);
subd_send_msg(channel->owner, take(msg));
}
@@ -1703,7 +1725,7 @@ bool peer_start_channeld(struct channel *channel,
const struct config *cfg = &ld->config;
struct secret last_remote_per_commit_secret;
struct penalty_base *pbases;
- u32 min_feerate, max_feerate, curr_blockheight;
+ u32 feerate_splice, min_feerate, max_feerate, curr_blockheight;
struct channel_inflight *inflight;
struct inflight **inflights;
struct bitcoin_txid txid;
@@ -1863,6 +1885,8 @@ bool peer_start_channeld(struct channel *channel,
tal_arr_expand(&inflights, infcopy);
}
+ feerate_splice = default_feerate(ld, channel, true);
+
initmsg = towire_channeld_init(tmpctx,
chainparams,
ld->our_features,
@@ -1878,6 +1902,7 @@ bool peer_start_channeld(struct channel *channel,
&channel->our_config,
&channel->channel_info.their_config,
channel->fee_states,
+ feerate_splice,
min_feerate,
max_feerate,
penalty_feerate(ld->topology),
@@ -2307,7 +2332,7 @@ static struct command_result *json_splice_init(struct command *cmd,
if (!feerate_per_kw) {
feerate_per_kw = tal(cmd, u32);
- *feerate_per_kw = opening_feerate(cmd->ld->topology);
+ *feerate_per_kw = default_feerate(cmd->ld, channel, true);
}
if (!initialpsbt)
@@ -2317,8 +2342,10 @@ static struct command_result *json_splice_init(struct command *cmd,
SPLICE_INPUT_ERROR,
"PSBT failed to validate.");
- log_debug(cmd->ld->log, "splice_init input PSBT version %d",
- initialpsbt->version);
+ log_debug(cmd->ld->log, "splice_init input PSBT version %d,"
+ " feerate: %u",
+ initialpsbt->version,
+ *feerate_per_kw);
cc = tal(cmd, struct splice_command);
@@ -2679,7 +2706,8 @@ static struct command_result *json_dev_feerate(struct command *cmd,
feerate_min(cmd->ld, NULL),
feerate_max(cmd->ld, NULL),
penalty_feerate(cmd->ld->topology),
- opening_feerate(cmd->ld->topology));
+ opening_feerate(cmd->ld->topology),
+ default_feerate(cmd->ld, channel, true));
subd_send_msg(channel->owner, take(msg));
response = json_stream_success(cmd);
diff --git a/tests/plugins/channeld_fakenet.c b/tests/plugins/channeld_fakenet.c
index b31b1352..f43384fa 100644
--- a/tests/plugins/channeld_fakenet.c
+++ b/tests/plugins/channeld_fakenet.c
@@ -959,10 +959,11 @@ failed:
static void handle_feerates(struct info *info, const u8 *inmsg)
{
- u32 feerate, min, max, penalty, opening;
+ u32 feerate, min, max, penalty, opening, splicing;
if (!fromwire_channeld_feerates(inmsg, &feerate,
- &min, &max, &penalty, &opening))
+ &min, &max, &penalty, &opening,
+ &splicing))
master_badmsg(WIRE_CHANNELD_FEERATES, inmsg);
/* BOLT #2:
@@ -1054,7 +1055,7 @@ static struct channel *handle_init(struct info *info, const u8 *init_msg)
struct secret last_remote_per_commit_secret;
struct penalty_base *pbases;
struct channel_type *channel_type;
- u32 feerate_min, feerate_max, feerate_penalty, feerate_opening;
+ u32 feerate_splice, feerate_min, feerate_max, feerate_penalty, feerate_opening;
struct pubkey remote_per_commit;
struct pubkey old_remote_per_commit;
u32 commit_msec;
@@ -1094,6 +1095,7 @@ static struct channel *handle_init(struct info *info, const u8 *init_msg)
&lease_expiry,
&conf[LOCAL], &conf[REMOTE],
&info->fee_states,
+ &feerate_splice,
&feerate_min,
&feerate_max,
&feerate_penalty,
Why this scored 25/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.