channeld: split the feerate we'll pay from the one we'll accept
What changed, and why it matters
This commit fixes a bug in Core Lightning where a channel peer could trick or accidentally push the node into signing a Bitcoin transaction with an absurdly high transaction fee during a 'splice' operation. Before the fix, there was no upper limit on the fee rate a peer could propose for a splice, and an internal balance check did not catch it because the accepting side contributed no funds. The patch adds two separate fee ceilings: a sanity ceiling on what any peer can propose, and a stricter self-imposed limit on fees the node itself will pay for its own transactions. It also removes unsafe 'no limit' sentinel values that could overflow later calculations.
Treat this as a security fix and include it in the next maintenance release. Operators running nodes that accept inbound channel opens or splice requests should upgrade, especially if they use default fee settings. Review any custom max_fee_multiplier values, as the patch now clamps the result to FEERATE_CEILING. No immediate on-chain action is required.
Security signals we found
Fixes missing upper bound on remote-proposed splice feerate
Adds explicit sanity ceiling (FEERATE_CEILING) and stricter self-payment cap (MAX_OUR_FEERATE_PER_KW)
Removes UINT_MAX sentinel that could overflow 25/24 RBF calculation
Propagates ignore_fee_limits as explicit boolean instead of overwriting bounds with 0xFFFFFFFF
Holds own side of splice to stricter limit in check_balances
Applies force_feerate override only to policy limit, not safety ceiling
Updates documentation to correct prior 'any fee they want' claim
Evidence from the diff
The patch splits the single feerate_max bound into two: FEERATE_CEILING (1,000,000 perkw / 4000 sat/vB) for the most a peer can drive the node to, and MAX_OUR_FEERATE_PER_KW (100,000 perkw / 400 sat/vB) for the most the node will pay from its own funds. It wires both values plus an ignore_fee_limits flag into channeld and openingd. splice_accepter now rejects remote funding_feerate_perkw below accepted_feerate_min and above accepted_feerate_max. handle_splice_init applies proposed_feerate_max to locally initiated splices, unless splice_force_feerate is set. check_balances now uses the stricter self-limit for whichever side is paying. opening_feerate, splice_feerate, default_feerate, and dual-fund RBF now use our_feerate_max. The UINT_MAX sentinel for unknown feerates is replaced with FEERATE_CEILING to avoid downstream overflow in RBF calculations. Documentation is updated to reflect that ignore-fee-limits no longer means ‘any fee’.
Changed components
channeld/channeld.cchanneld/channeld_wire.csvopeningd/openingd.copeningd/openingd_wire.csvlightningd/chaintopology.clightningd/channel_control.clightningd/dual_open_control.clightningd/opening_control.cbitcoin/feerate.hJSON-RPC feerates outputsetchannel ignorefeelimits behaviorignore-fee-limits configuration optionInspect captured patch +259 / −54
### bitcoin/feerate.h
@@ -43,6 +43,19 @@
*/
#define FEERATE_CEILING 1000000
+/*
+ * The most we are ever willing to pay ourselves (sat/kw).
+ *
+ * Unlike FEERATE_CEILING this *is* a policy limit, and the two are
+ * deliberately an order of magnitude apart because they answer different
+ * questions. FEERATE_CEILING bounds what we let a peer drive us to: their
+ * estimator being broken is not by itself worth dropping a channel over, so
+ * it only has to exclude the absurd. This one bounds what we propose with
+ * our own money, where we can simply decline: 400 sat/vB is around 0.011 BTC
+ * for a bare anchor commitment, which we would rather not spend by accident.
+ */
+#define MAX_OUR_FEERATE_PER_KW 100000
+
enum feerate_style {
FEERATE_PER_KSIPA,
FEERATE_PER_KBYTE
### channeld/channeld.c
@@ -11,6 +11,7 @@
* limits, unlikely as that is.
*/
#include "config.h"
+#include <bitcoin/feerate.h>
#include <bitcoin/script.h>
#include <ccan/asort/asort.h>
#include <ccan/cast/cast.h>
@@ -82,6 +83,14 @@ struct peer {
/* Tolerable amounts for feerate (only relevant for fundee). */
u32 feerate_min, feerate_max;
+ /* The most we're prepared to pay ourselves: stricter than
+ * feerate_max, which is what we'll tolerate from them. */
+ u32 our_feerate_max;
+
+ /* Set by --ignore-fee-limits or dev-ignore-fee-limits: drop the
+ * policy bounds above (but never the sanity ceiling). */
+ bool ignore_fee_limits;
+
/* Feerate to be used when creating penalty transactions. */
u32 feerate_penalty;
@@ -690,6 +699,32 @@ static void handle_peer_add_htlc(struct peer *peer, const u8 *msg)
channel_add_err_name(add_err));
}
+/* Ignoring the fee limits drops the policy bounds, but never the sanity
+ * ceiling: a feerate above that means a broken fee source, and whatever we
+ * accept here we go on to store. */
+static u32 accepted_feerate_min(const struct peer *peer)
+{
+ if (peer->ignore_fee_limits)
+ return 1;
+ return peer->feerate_min;
+}
+
+static u32 accepted_feerate_max(const struct peer *peer)
+{
+ if (peer->ignore_fee_limits)
+ return FEERATE_CEILING;
+ return peer->feerate_max;
+}
+
+/* The most we'll pay ourselves, as opposed to what we'll put up with
+ * from them. */
+static u32 proposed_feerate_max(const struct peer *peer)
+{
+ if (peer->ignore_fee_limits)
+ return FEERATE_CEILING;
+ return peer->our_feerate_max;
+}
+
/* We don't get upset if they're outside the range, as long as they're
* improving (or at least, not getting worse!). */
static bool feerate_same_or_better(const struct channel *channel,
@@ -728,7 +763,8 @@ static void handle_peer_feechange(struct peer *peer, const u8 *msg)
"update_fee from non-opener?");
status_debug("update_fee %u, range %u-%u",
- feerate, peer->feerate_min, peer->feerate_max);
+ feerate, accepted_feerate_min(peer),
+ accepted_feerate_max(peer));
/* BOLT #2:
*
@@ -739,12 +775,14 @@ static void handle_peer_feechange(struct peer *peer, const u8 *msg)
* `error` and fail the channel.
*/
if (!feerate_same_or_better(peer->channel, feerate,
- peer->feerate_min, peer->feerate_max))
+ accepted_feerate_min(peer),
+ accepted_feerate_max(peer)))
peer_failed_warn(peer->pps, &peer->channel_id,
"update_fee %u outside range %u-%u"
" (currently %u)",
feerate,
- peer->feerate_min, peer->feerate_max,
+ accepted_feerate_min(peer),
+ accepted_feerate_max(peer),
channel_feerate(peer->channel, LOCAL));
/* BOLT #2:
@@ -1928,8 +1966,10 @@ static void check_tx_abort(struct peer *peer, const u8 *msg, struct bitcoin_txid
exit(0);
}
-static void splice_abort(struct peer *peer, struct inflight *inflight,
- const char *fmt, ...)
+/* Sends tx_abort, waits for their ack, tells master, and exits: callers rely
+ * on this not returning (check_balances falls through to further checks). */
+static NORETURN void splice_abort(struct peer *peer, struct inflight *inflight,
+ const char *fmt, ...)
{
struct bitcoin_outpoint *outpoint;
u8 *msg;
@@ -3599,10 +3639,18 @@ static struct amount_sat check_balances(struct peer *peer,
/* As a safeguard max feerate is checked (only) locally, if it's
* particularly high we fail and tell the user but allow them to
- * override with `splice_force_feerate` */
- max_accepter_fee = amount_tx_fee(peer->feerate_max,
+ * override with `splice_force_feerate`.
+ *
+ * Whichever side is ours is held to what we're prepared to pay; the
+ * other side is their money, so it only has to clear the looser
+ * bound we apply to anything they propose. */
+ max_accepter_fee = amount_tx_fee(opener
+ ? accepted_feerate_max(peer)
+ : proposed_feerate_max(peer),
calc_weight(TX_ACCEPTER, psbt, false));
- max_initiator_fee = amount_tx_fee(peer->feerate_max,
+ max_initiator_fee = amount_tx_fee(opener
+ ? proposed_feerate_max(peer)
+ : accepted_feerate_max(peer),
calc_weight(TX_INITIATOR, psbt, opener));
if (opener) {
@@ -4277,9 +4325,27 @@ static void splice_accepter(struct peer *peer, const u8 *inmsg)
&peer->channel->funding_pubkey[REMOTE]))
status_info("Splice peer is rotating funding pubkey");
- if (funding_feerate_perkw < peer->feerate_min)
+ /* They initiated, so it's their fee: the looser bound applies.
+ *
+ * We disconnect rather than tx_abort here. A tx_abort has to be
+ * acked, and splice_abort() blocks reading until it is: a peer that
+ * proposes a nonsense feerate and then goes silent would leave us
+ * parked in that read with the channel quiesced in STFU. Since the
+ * bound is FEERATE_CEILING, a peer reaching it is not disagreeing
+ * with us about the mempool, they are broken. */
+ if (funding_feerate_perkw < accepted_feerate_min(peer))
+ peer_failed_warn(peer->pps, &peer->channel_id,
+ "Splice feerate_perkw %u is below our"
+ " minimum %u",
+ funding_feerate_perkw,
+ accepted_feerate_min(peer));
+
+ if (funding_feerate_perkw > accepted_feerate_max(peer))
peer_failed_warn(peer->pps, &peer->channel_id,
- "Splice feerate_perkw is too low");
+ "Splice feerate_perkw %u is above our"
+ " maximum %u",
+ funding_feerate_perkw,
+ accepted_feerate_max(peer));
/* TODO: Add plugin hook for user to adjust accepter amount */
peer->splicing->accepter_relative = 0;
@@ -5014,14 +5080,30 @@ static void handle_splice_init(struct peer *peer, const u8 *inmsg)
wire_sync_write(MASTER_FD, take(msg));
return;
}
- if (peer->splicing->feerate_per_kw < peer->feerate_min) {
+ if (peer->splicing->feerate_per_kw < accepted_feerate_min(peer)) {
msg = towire_channeld_splice_state_error(NULL, tal_fmt(tmpctx,
"Feerate %u is too"
" low. Lower than"
" channel feerate_min"
" %u",
peer->splicing->feerate_per_kw,
- peer->feerate_min));
+ accepted_feerate_min(peer)));
+ wire_sync_write(MASTER_FD, take(msg));
+ return;
+ }
+ /* We initiated, so this is our money: hold it to what we're
+ * prepared to pay, not to what we'd tolerate from them. Like the
+ * fee check in check_balances, `force_feerate` is the user saying
+ * they meant it: this is a policy limit, not a safety one. */
+ if (!peer->splicing->force_feerate
+ && peer->splicing->feerate_per_kw > proposed_feerate_max(peer)) {
+ msg = towire_channeld_splice_state_error(NULL, tal_fmt(tmpctx,
+ "Feerate %u is too"
+ " high. Higher than the most"
+ " we'll pay ourselves"
+ " %u",
+ peer->splicing->feerate_per_kw,
+ proposed_feerate_max(peer)));
wire_sync_write(MASTER_FD, take(msg));
return;
}
@@ -6492,6 +6574,8 @@ static void handle_feerates(struct peer *peer, const u8 *inmsg)
&feerate,
&peer->feerate_min,
&peer->feerate_max,
+ &peer->our_feerate_max,
+ &peer->ignore_fee_limits,
&peer->feerate_penalty,
&peer->feerate_opening,
&peer->feerate_splice))
@@ -6865,6 +6949,8 @@ static void init_channel(struct peer *peer)
&peer->feerate_splice,
&peer->feerate_min,
&peer->feerate_max,
+ &peer->our_feerate_max,
+ &peer->ignore_fee_limits,
&peer->feerate_penalty,
&peer->feerate_opening,
&peer->their_commit_sig,
@@ -6956,7 +7042,7 @@ static void init_channel(struct peer *peer)
peer->next_index[LOCAL], peer->next_index[REMOTE],
peer->revocations_received,
fmt_fee_states(tmpctx, fee_states),
- peer->feerate_min, peer->feerate_max,
+ accepted_feerate_min(peer), accepted_feerate_max(peer),
fmt_height_states(tmpctx, blockheight_states),
peer->our_blockheight);
### channeld/channeld_wire.csv
@@ -31,6 +31,8 @@ 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,our_feerate_max,u32,
+msgdata,channeld_init,ignore_fee_limits,bool,
msgdata,channeld_init,feerate_penalty,u32,
msgdata,channeld_init,feerate_opening,u32,
msgdata,channeld_init,first_commit_sig,bitcoin_signature,
@@ -331,6 +333,8 @@ msgtype,channeld_feerates,1027
msgdata,channeld_feerates,feerate,u32,
msgdata,channeld_feerates,min_feerate,u32,
msgdata,channeld_feerates,max_feerate,u32,
+msgdata,channeld_feerates,our_max_feerate,u32,
+msgdata,channeld_feerates,ignore_fee_limits,bool,
msgdata,channeld_feerates,penalty_feerate,u32,
msgdata,channeld_feerates,opening_feerate,u32,
msgdata,channeld_feerates,feerate_splice,u32,
### contrib/msggen/msggen/schema.json
@@ -35956,7 +35956,7 @@
"added": "v23.08",
"type": "boolean",
"description": [
- "If set to True means to allow the peer to set the commitment transaction fees (or closing transaction fees) to any value they want. This is dangerous: they could set an exorbitant fee (so HTLCs are unenforcable), or a tiny fee (so that commitment transactions cannot be relayed), but avoids channel breakage in case of feerate disagreements. (Note: the global `ignore_fee_limits` setting overrides this)."
+ "If set to True means to allow the peer to set the commitment transaction fees (or closing transaction fees) to any value they want, short of a sanity ceiling of 1000000perkw (4000 sat/vB) which we refuse regardless: a feerate above that means a broken fee estimator rather than a busy mempool. This is dangerous: they could set an exorbitant fee (so HTLCs are unenforcable), or a tiny fee (so that commitment transactions cannot be relayed), but avoids channel breakage in case of feerate disagreements. (Note: the global `ignore_fee_limits` setting overrides this)."
]
}
}
### doc/getting-started/getting-started/configuration.md
@@ -268,7 +268,7 @@ The [`listconfigs`](ref:listconfigs) command will output a valid configuration f
- **ignore-fee-limits**=_BOOL_
- Allow nodes which establish channels to us to set any fee they want. This may result in a channel which cannot be closed, should fees increase, but make channels far more reliable since we never close it due to unreasonable fees.
+ Allow nodes which establish channels to us to set any fee they want, short of a sanity ceiling of 1000000perkw (4000 sat/vB) which we refuse regardless: a feerate above that means a broken fee estimator rather than a busy mempool. This may result in a channel which cannot be closed, should fees increase, but make channels far more reliable since we never close it due to unreasonable fees.
- **commit-time**=_MILLISECONDS_
### doc/lightningd-config.5.md
@@ -381,7 +381,10 @@ falls below this.
* **ignore-fee-limits**=*BOOL*
- Allow nodes which establish channels to us to set any fee they want.
+ Allow nodes which establish channels to us to set any fee they want,
+short of a sanity ceiling of 1000000perkw (4000 sat/vB) which we refuse
+regardless: a feerate above that means a broken fee estimator rather
+than a busy mempool.
This may result in a channel which cannot be closed, should fees
increase, but make channels far more reliable since we never close it
due to unreasonable fees. Note that this can be set on a per-channel
### doc/schemas/setchannel.json
@@ -57,7 +57,7 @@
"added": "v23.08",
"type": "boolean",
"description": [
- "If set to True means to allow the peer to set the commitment transaction fees (or closing transaction fees) to any value they want. This is dangerous: they could set an exorbitant fee (so HTLCs are unenforcable), or a tiny fee (so that commitment transactions cannot be relayed), but avoids channel breakage in case of feerate disagreements. (Note: the global `ignore_fee_limits` setting overrides this)."
+ "If set to True means to allow the peer to set the commitment transaction fees (or closing transaction fees) to any value they want, short of a sanity ceiling of 1000000perkw (4000 sat/vB) which we refuse regardless: a feerate above that means a broken fee estimator rather than a busy mempool. This is dangerous: they could set an exorbitant fee (so HTLCs are unenforcable), or a tiny fee (so that commitment transactions cannot be relayed), but avoids channel breakage in case of feerate disagreements. (Note: the global `ignore_fee_limits` setting overrides this)."
]
}
}
### lightningd/chaintopology.c
@@ -616,10 +616,19 @@ static struct rate_conversion conversions[] = {
u32 opening_feerate(struct chain_topology *topo)
{
+ u32 rate;
+
+ /* An explicitly forced feerate is the operator saying they meant
+ * it, so we don't second-guess it. */
if (topo->ld->force_feerates)
return topo->ld->force_feerates[FEERATE_OPENING];
- return feerate_for_deadline(topo,
+
+ rate = feerate_for_deadline(topo,
conversions[FEERATE_OPENING].blockcount);
+ /* We fund the opening tx, so this is our money. */
+ if (rate > our_feerate_max(topo->ld, NULL))
+ rate = our_feerate_max(topo->ld, NULL);
+ return rate;
}
u32 splice_feerate(struct chain_topology *topo, struct lightningd *ld)
@@ -628,8 +637,9 @@ u32 splice_feerate(struct chain_topology *topo, struct lightningd *ld)
if (!rate)
return 0;
rate += ld->config.feerate_offset;
- if (rate > feerate_max(ld, NULL))
- rate = feerate_max(ld, NULL);
+ /* We pay for the splice we initiate. */
+ if (rate > our_feerate_max(ld, NULL))
+ rate = our_feerate_max(ld, NULL);
return rate;
}
@@ -1184,6 +1194,15 @@ u32 feerate_min(struct lightningd *ld, bool *unknown)
/* FIXME: This is what bcli used to do: halve the slow feerate! */
min /= 2;
+ /* Never demand more than we would ever propose ourselves. We cap what
+ * we offer at MAX_OUR_FEERATE_PER_KW (see our_feerate_max), so anything
+ * above that would have us refuse a peer the very feerate we would have
+ * sent them, which costs us the channel for nothing. A broken fee
+ * source clamped to FEERATE_CEILING puts this at FEERATE_CEILING/2,
+ * five times that cap. */
+ if (min > MAX_OUR_FEERATE_PER_KW)
+ min = MAX_OUR_FEERATE_PER_KW;
+
/* We can't allow less than feerate_floor, since that won't relay */
if (min < get_feerate_floor(topo))
return get_feerate_floor(topo);
@@ -1194,6 +1213,7 @@ u32 feerate_max(struct lightningd *ld, bool *unknown)
{
const struct chain_topology *topo = ld->topology;
u32 max = 0;
+ u64 scaled;
if (unknown)
*unknown = false;
@@ -1208,9 +1228,35 @@ u32 feerate_max(struct lightningd *ld, bool *unknown)
if (!max) {
if (unknown)
*unknown = true;
- return UINT_MAX;
+ /* No estimates: fall back to the ceiling, exactly as
+ * feerate_min falls back to the floor. Returning UINT_MAX
+ * here would be a sentinel meaning "no bound at all", and
+ * that bound is what a peer's proposal gets measured
+ * against and what we then store: a value that large
+ * overflows the 25/24 RBF calculation. */
+ return FEERATE_CEILING;
}
- return max * topo->ld->config.max_fee_multiplier;
+ /* Estimates are clamped to FEERATE_CEILING on the way in, but the
+ * multiplier is settable, so widen before multiplying. */
+ scaled = (u64)max * topo->ld->config.max_fee_multiplier;
+
+ /* Don't let the multiplier carry us past the sanity ceiling: above
+ * that a peer is not congested, their fee source is broken. */
+ if (scaled > FEERATE_CEILING)
+ return FEERATE_CEILING;
+ return scaled;
+}
+
+u32 our_feerate_max(struct lightningd *ld, bool *unknown)
+{
+ u32 max = feerate_max(ld, unknown);
+
+ /* We are stricter with ourselves than with a peer: this is our
+ * money, and declining to propose a feerate costs us nothing, where
+ * refusing a peer's costs us the channel. */
+ if (max > MAX_OUR_FEERATE_PER_KW)
+ return MAX_OUR_FEERATE_PER_KW;
+ return max;
}
u32 default_locktime(const struct chain_topology *topo)
### lightningd/chaintopology.h
@@ -194,6 +194,11 @@ bool unknown_feerates(const struct chain_topology *topo);
u32 feerate_min(struct lightningd *ld, bool *unknown);
u32 feerate_max(struct lightningd *ld, bool *unknown);
+/* Same, but the most we're willing to *pay*, which is stricter than what
+ * we'll tolerate from a peer (see MAX_OUR_FEERATE_PER_KW). Use this
+ * wherever the feerate comes out of our own funds. */
+u32 our_feerate_max(struct lightningd *ld, bool *unknown);
+
/* These return 0 if unknown */
u32 opening_feerate(struct chain_topology *topo);
u32 splice_feerate(struct chain_topology *topo, struct lightningd *ld);
### lightningd/channel_control.c
@@ -65,7 +65,8 @@ static u32 default_feerate(struct lightningd *ld, const struct channel *channel,
if (!feerate)
return 0;
- max_feerate = feerate_max(ld, NULL);
+ /* We only clamp the feerate we propose here, and the opener pays it. */
+ max_feerate = our_feerate_max(ld, NULL);
/* The channel opener should use a slightly higher than minimal feerate
* in order to avoid excessive feerate disagreements */
@@ -81,7 +82,8 @@ static u32 default_feerate(struct lightningd *ld, const struct channel *channel,
void channel_update_feerates(struct lightningd *ld, const struct channel *channel)
{
u8 *msg;
- u32 min_feerate, max_feerate;
+ u32 min_feerate, max_feerate, our_max_feerate;
+ bool ignore_fee_limits;
bool anchors = channel_type_has_anchors(channel->type);
u32 feerate = default_feerate(ld, channel, (channel->opener == LOCAL));
u32 feerate_splice = splice_feerate(ld->topology, ld);
@@ -95,26 +97,30 @@ void channel_update_feerates(struct lightningd *ld, const struct channel *channe
min_feerate = get_feerate_floor(ld->topology);
else
min_feerate = feerate_min(ld, NULL);
+ /* max_feerate is what we'll tolerate from them, our_max_feerate what
+ * we're prepared to pay ourselves. */
max_feerate = feerate_max(ld, NULL);
-
- if (channel->ignore_fee_limits || ld->config.ignore_fee_limits) {
- min_feerate = 1;
- max_feerate = 0xFFFFFFFF;
- }
+ our_max_feerate = our_feerate_max(ld, NULL);
+ ignore_fee_limits = channel->ignore_fee_limits
+ || ld->config.ignore_fee_limits;
log_debug(ld->log,
- "update_feerates: feerate = %u, min=%u, max=%u, penalty=%u,"
- " opening=%u, splicing: %u",
+ "update_feerates: feerate = %u, min=%u, max=%u, our_max=%u,"
+ " penalty=%u, opening=%u, splicing: %u%s",
feerate,
min_feerate,
- feerate_max(ld, NULL),
+ max_feerate,
+ our_max_feerate,
penalty_feerate(ld->topology),
opening_feerate(ld->topology),
- feerate_splice);
+ feerate_splice,
+ ignore_fee_limits ? " (limits ignored)" : "");
msg = towire_channeld_feerates(NULL, feerate,
min_feerate,
max_feerate,
+ our_max_feerate,
+ ignore_fee_limits,
penalty_feerate(ld->topology),
opening_feerate(ld->topology),
feerate_splice);
@@ -1759,7 +1765,9 @@ 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 feerate_splice, min_feerate, max_feerate, curr_blockheight;
+ u32 feerate_splice, min_feerate, max_feerate, our_max_feerate;
+ u32 curr_blockheight;
+ bool ignore_fee_limits;
struct channel_inflight *inflight;
struct inflight **inflights;
struct bitcoin_txid txid;
@@ -1861,12 +1869,14 @@ bool peer_start_channeld(struct channel *channel,
min_feerate = get_feerate_floor(ld->topology);
else
min_feerate = feerate_min(ld, NULL);
+ /* max_feerate is what we'll tolerate from them, our_max_feerate what
+ * we're prepared to pay ourselves. */
max_feerate = feerate_max(ld, NULL);
+ our_max_feerate = our_feerate_max(ld, NULL);
- if (channel->ignore_fee_limits || ld->config.ignore_fee_limits) {
- min_feerate = 1;
- max_feerate = 0xFFFFFFFF;
- }
+ /* channeld applies this: the bounds above stay honest on the wire. */
+ ignore_fee_limits = channel->ignore_fee_limits
+ || ld->config.ignore_fee_limits;
/* Make sure we don't go backsards on blockheights */
curr_blockheight = get_block_height(ld->topology);
@@ -1939,6 +1949,8 @@ bool peer_start_channeld(struct channel *channel,
feerate_splice,
min_feerate,
max_feerate,
+ our_max_feerate,
+ ignore_fee_limits,
penalty_feerate(ld->topology),
opening_feerate(ld->topology),
&channel->last_sig,
@@ -2737,6 +2749,9 @@ static struct command_result *json_dev_feerate(struct command *cmd,
msg = towire_channeld_feerates(NULL, *feerate,
feerate_min(cmd->ld, NULL),
feerate_max(cmd->ld, NULL),
+ our_feerate_max(cmd->ld, NULL),
+ channel->ignore_fee_limits
+ || cmd->ld->config.ignore_fee_limits,
penalty_feerate(cmd->ld->topology),
opening_feerate(cmd->ld->topology),
splice_feerate(cmd->ld->topology, cmd->ld));
### lightningd/dual_open_control.c
@@ -2608,6 +2608,16 @@ json_openchannel_bump(struct command *cmd,
next_feerate_min,
*info->feerate_per_kw_funding);
+ /* We fund this, so it's our money: don't let the 25/24 escalation
+ * (or an ambitious caller) carry us past the most we'll pay. */
+ if (*info->feerate_per_kw_funding > our_feerate_max(cmd->ld, NULL))
+ return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
+ "Feerate %u is above the most we'll pay"
+ " (%u); the last attempt was at %u",
+ *info->feerate_per_kw_funding,
+ our_feerate_max(cmd->ld, NULL),
+ last_feerate_perkw);
+
/* BOLT #2:
* - if both nodes advertised `option_support_large_channel`:
* - MAY set `funding_satoshis` greater than or equal to 2^24 satoshi.
### lightningd/opening_control.c
@@ -1001,13 +1001,9 @@ bool peer_start_openingd(struct peer *peer, struct peer_fd *peer_fd)
&max_to_self_delay,
&min_effective_htlc_capacity);
- if (peer->ld->config.ignore_fee_limits) {
- minrate = 1;
- maxrate = 0xFFFFFFFF;
- } else {
- minrate = feerate_min(peer->ld, NULL);
- maxrate = feerate_max(peer->ld, NULL);
- }
+ /* openingd applies ignore_fee_limits itself, so these stay honest. */
+ minrate = feerate_min(peer->ld, NULL);
+ maxrate = feerate_max(peer->ld, NULL);
msg = towire_openingd_init(NULL,
chainparams,
@@ -1020,6 +1016,7 @@ bool peer_start_openingd(struct peer *peer, struct peer_fd *peer_fd)
&uc->local_funding_pubkey,
uc->minimum_depth,
minrate, maxrate,
+ peer->ld->config.ignore_fee_limits,
peer->ld->dev_force_tmp_channel_id,
peer->ld->config.allowdustreserve,
peer->ld->dev_any_channel_type);
### openingd/openingd.c
@@ -8,6 +8,7 @@
* commit to the database once openingd succeeds.
*/
#include "config.h"
+#include <bitcoin/feerate.h>
#include <bitcoin/script.h>
#include <ccan/array_size/array_size.h>
#include <ccan/breakpoint/breakpoint.h>
@@ -49,6 +50,8 @@ struct state {
/* Constraints on a channel they open. */
u32 minimum_depth;
u32 min_feerate, max_feerate;
+ /* Drop the policy bounds above (never the sanity ceiling). */
+ bool ignore_fee_limits;
struct amount_msat min_effective_htlc_capacity;
/* Limits on what remote config we accept. */
@@ -845,6 +848,7 @@ static u8 *fundee_channel(struct state *state, const u8 *open_channel_msg)
struct tlv_accept_channel_tlvs *accept_tlvs;
struct tlv_open_channel_tlvs *open_tlvs;
struct amount_sat *reserve;
+ u32 min_feerate, max_feerate;
/* BOLT #2:
*
@@ -955,17 +959,24 @@ static u8 *fundee_channel(struct state *state, const u8 *open_channel_msg)
* - it considers `feerate_per_kw` too small for timely processing or
* unreasonably large.
*/
- if (state->feerate_per_kw < state->min_feerate) {
+ /* Even ignoring the limits we refuse 0: that is not a feerate any
+ * commitment could be relayed at, and it is what we would store. */
+ min_feerate = state->ignore_fee_limits ? 1 : state->min_feerate;
+ if (state->feerate_per_kw < min_feerate) {
negotiation_failed(state,
"feerate_per_kw %u below minimum %u",
- state->feerate_per_kw, state->min_feerate);
+ state->feerate_per_kw, min_feerate);
return NULL;
}
- if (state->feerate_per_kw > state->max_feerate) {
+ /* Even ignoring the limits we refuse the absurd: this is the feerate
+ * we would go on to store. */
+ max_feerate = state->ignore_fee_limits
+ ? FEERATE_CEILING : state->max_feerate;
+ if (state->feerate_per_kw > max_feerate) {
negotiation_failed(state,
"feerate_per_kw %u above maximum %u",
- state->feerate_per_kw, state->max_feerate);
+ state->feerate_per_kw, max_feerate);
return NULL;
}
@@ -1452,6 +1463,7 @@ int main(int argc, char *argv[])
&state->our_funding_pubkey,
&state->minimum_depth,
&state->min_feerate, &state->max_feerate,
+ &state->ignore_fee_limits,
&state->dev_force_tmp_channel_id,
&state->allowdustreserve,
&state->dev_accept_any_channel_type))
### openingd/openingd_wire.csv
@@ -24,6 +24,7 @@ msgdata,openingd_init,our_funding_pubkey,pubkey,
msgdata,openingd_init,minimum_depth,u32,
msgdata,openingd_init,min_feerate,u32,
msgdata,openingd_init,max_feerate,u32,
+msgdata,openingd_init,ignore_fee_limits,bool,
msgdata,openingd_init,dev_temporary_channel_id,?byte,32
# Do we allow `fundchannel` or the `openchannel` hook to set sub-dust
# reserves? This is explicitly required by the spec for safety
### tests/fuzz/fuzz-open_channel.c
@@ -347,6 +347,9 @@ static struct state *fromwire_new_state(const tal_t *ctx)
state->minimum_depth = fromwire_u32(cursor, max);
state->min_feerate = fromwire_u32(cursor, max);
state->max_feerate = fromwire_u32(cursor, max);
+ /* Take this from the input too, so we explore both the bounded and the
+ * ignore-fee-limits path through fundee_channel(). */
+ state->ignore_fee_limits = fromwire_bool(cursor, max);
state->our_funding_pubkey = dummy_pubkey;
/* Set developer options to false. */
### tests/plugins/channeld_fakenet.c
@@ -959,10 +959,12 @@ static void handle_offer_htlc(struct info *info, const u8 *inmsg)
static void handle_feerates(struct info *info, const u8 *inmsg)
{
- u32 feerate, min, max, penalty, opening, splicing;
+ u32 feerate, min, max, our_max, penalty, opening, splicing;
+ bool ignore_fee_limits;
if (!fromwire_channeld_feerates(inmsg, &feerate,
- &min, &max, &penalty, &opening,
+ &min, &max, &our_max,
+ &ignore_fee_limits, &penalty, &opening,
&splicing))
master_badmsg(WIRE_CHANNELD_FEERATES, inmsg);
@@ -1056,6 +1058,8 @@ static struct channel *handle_init(struct info *info, const u8 *init_msg)
struct penalty_base *pbases;
struct channel_type *channel_type;
u32 feerate_splice, feerate_min, feerate_max, feerate_penalty, feerate_opening;
+ u32 our_feerate_max;
+ bool ignore_fee_limits;
struct pubkey remote_per_commit;
struct pubkey old_remote_per_commit;
u32 commit_msec;
@@ -1098,6 +1102,8 @@ static struct channel *handle_init(struct info *info, const u8 *init_msg)
&feerate_splice,
&feerate_min,
&feerate_max,
+ &our_feerate_max,
+ &ignore_fee_limits,
&feerate_penalty,
&feerate_opening,
&their_commit_sig,
### tests/test_misc.py
@@ -1884,7 +1884,8 @@ def test_feerates(node_factory, anchors):
feerates = l1.rpc.feerates('perkw')
assert feerates['warning_missing_feerates'] == 'Some fee estimates unavailable: bitcoind startup?'
assert 'perkb' not in feerates
- assert feerates['perkw']['max_acceptable'] == 2**32 - 1
+ # No estimates: falls back to the ceiling, as min falls back to the floor.
+ assert feerates['perkw']['max_acceptable'] == 1000000
assert feerates['perkw']['min_acceptable'] == 253
assert feerates['perkw']['min_acceptable'] == 253
assert feerates['perkw']['floor'] == 253
@@ -1895,7 +1896,7 @@ def test_feerates(node_factory, anchors):
feerates = l1.rpc.feerates('perkb')
assert feerates['warning_missing_feerates'] == 'Some fee estimates unavailable: bitcoind startup?'
assert 'perkw' not in feerates
- assert feerates['perkb']['max_acceptable'] == (2**32 - 1)
+ assert feerates['perkb']['max_acceptable'] == 1000000 * 4
assert feerates['perkb']['min_acceptable'] == 253 * 4
# Note: This is floored at the FEERATE_FLOOR constant (253)
assert feerates['perkb']['floor'] == 1012
@@ -4986,8 +4987,11 @@ def test_set_feerate_offset(node_factory, bitcoind):
else:
feerate = 11100
min_feerate = 1875
+ # our_max is what we're willing to pay ourselves (MAX_OUR_FEERATE_PER_KW),
+ # as opposed to max, which is what we'll tolerate from the peer.
l1.daemon.wait_for_log(f'lightningd: update_feerates: feerate = {feerate}, '
- f'min={min_feerate}, max=150000, penalty=7500')
+ f'min={min_feerate}, max=150000, our_max=100000, '
+ f'penalty=7500')
l2.daemon.wait_for_log(f'peer updated fee to {feerate}')
l2.pay(l1, 100000000)
Why this scored 72/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.