Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions doc/lightningd-config.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -415,9 +415,23 @@ RPC call lightning-setchannel(7).

* **htlc-maximum-msat**=*MILLISATOSHI*

Default: unset (no limit). Sets the maximum allowed HTLC value for newly created
channels. If you want to change the `htlc_maximum_msat` for existing channels,
use the RPC call lightning-setchannel(7).
Sets the maximum allowed HTLC value for newly created channels. If unset
(the default), public channels advertise 25% of the channel capacity: a
maximum well below the publicly-known capacity makes it harder to probe for
where payments are flowing. Private channels, whose capacity is not public,
advertise the full amount they can send. Either way this is capped at what we
can actually send, and an explicit value here overrides the default (up to
that cap). It is also raised to `htlc_minimum_msat` where that is higher,
since the spec requires the maximum to be at least the minimum. If you want
to change the `htlc_maximum_msat` for existing
channels, use the RPC call lightning-setchannel(7), for example:

lightning-cli listpeerchannels | \
jq -r '.channels[] | select(.private == false and .short_channel_id) |
"\(.short_channel_id) \((.total_msat // 0) / 4 | floor)"' | \
while read scid max; do
lightning-cli setchannel "$scid" htlcmax="${max}msat"
done

* **announce-addr-discovered**=*BOOL*

Expand Down
61 changes: 54 additions & 7 deletions lightningd/channel.c
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,57 @@ struct amount_msat htlc_max_possible_send(const struct channel *channel)
return lower_bound_msat;
}

struct amount_msat channel_htlc_maximum_default(const struct channel *channel,
struct amount_msat configured_max)
{
struct amount_msat cap = htlc_max_possible_send(channel);
struct amount_msat deflt;

/* If the operator set --htlc-maximum-msat, honour it (the sentinel
* AMOUNT_MSAT(-1ULL) means "unset"). Otherwise pick a default. */
if (!amount_msat_eq(configured_max, AMOUNT_MSAT(-1ULL)))
deflt = configured_max;
else if (channel->channel_flags & CHANNEL_FLAGS_ANNOUNCE_CHANNEL) {
/* Oakland privacy proposal (Lightning Dev Summit): probing for
* where payments went is much harder if the htlc maximum is
* well below the channel capacity. For public channels the
* capacity is known from the funding output, so default to 25%
* of it. */
if (!amount_sat_to_msat(&deflt, channel->funding_sats))
deflt = cap;
else
deflt = amount_msat_div(deflt, 4);
} else
/* Private channels have no publicly-known capacity to correlate
* against, so we advertise the full amount we can send. */
deflt = cap;

/* BOLT #7:
*
* - MUST set `htlc_maximum_msat` to the maximum value it will send through this channel for a single HTLC.
* - MUST set this to less than or equal to the channel capacity.
* - MUST set this to less than or equal to `max_htlc_value_in_flight_msat` it received from the peer.
* - MUST set this to greater than or equal to `htlc_minimum_msat`.
*/
/* A quarter of the capacity can land below a high htlc_minimum_msat:
* a routable channel beats the stronger privacy margin. */
if (amount_msat_less(deflt, channel->htlc_minimum_msat))
deflt = channel->htlc_minimum_msat;

/* htlc_max_possible_send() covers both upper bounds above. If it is
* itself below htlc_minimum_msat then no value satisfies the spec, so
* say so rather than advertising an unroutable channel silently. */
if (amount_msat_less(cap, channel->htlc_minimum_msat))
log_unusual(channel->log,
"htlc_minimum_msat %s exceeds the most we can send"
" (%s): channel_update will not be routable",
fmt_amount_msat(tmpctx, channel->htlc_minimum_msat),
fmt_amount_msat(tmpctx, cap));

/* Never advertise more than we could actually send. */
return amount_msat_min(deflt, cap);
}

struct channel *new_channel(struct peer *peer, u64 dbid,
/* NULL or stolen */
struct wallet_shachain *their_shachain,
Expand Down Expand Up @@ -567,7 +618,7 @@ struct channel *new_channel(struct peer *peer, u64 dbid,
bool withheld)
{
struct channel *channel = tal(peer->ld, struct channel);
struct amount_msat htlc_min, htlc_max;
struct amount_msat htlc_min;

bool anysegwit = !chainparams->is_elements && feature_negotiated(peer->ld->our_features,
peer->their_features,
Expand Down Expand Up @@ -694,11 +745,8 @@ struct channel *new_channel(struct peer *peer, u64 dbid,
channel->htlc_minimum_msat = htlc_min;
else
channel->htlc_minimum_msat = htlc_minimum_msat;
htlc_max = htlc_max_possible_send(channel);
if (amount_msat_less(htlc_max, htlc_maximum_msat))
channel->htlc_maximum_msat = htlc_max;
else
channel->htlc_maximum_msat = htlc_maximum_msat;
channel->htlc_maximum_msat = channel_htlc_maximum_default(channel,
htlc_maximum_msat);

list_add_tail(&peer->channels, &channel->list);
channel->rr_number = peer->ld->rr_counter++;
Expand Down Expand Up @@ -1350,4 +1398,3 @@ const u8 *channel_update_for_error(const tal_t *ctx,

return channel_gossip_update_for_error(ctx, channel);
}

9 changes: 9 additions & 0 deletions lightningd/channel.h
Original file line number Diff line number Diff line change
Expand Up @@ -977,6 +977,15 @@ const u8 *channel_update_for_error(const tal_t *ctx,

struct amount_msat htlc_max_possible_send(const struct channel *channel);

/* Default htlc_maximum_msat to advertise for a new channel, given the
* configured --htlc-maximum-msat (AMOUNT_MSAT(-1ULL) if unset). Public
* channels default to 25% of capacity for privacy; private channels and an
* explicit setting use the full amount, all capped at what we can send and
* floored at channel->htlc_minimum_msat (which must therefore already be
* set). */
struct amount_msat channel_htlc_maximum_default(const struct channel *channel,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

channel_htlc_maximum_default() doesn't clamp the result against channel->htlc_minimum_msat. With the previous capacity default this was effectively unreachable, but with a 25% ofcapacity default it's now realistic for small public channels combined with a non-default --htlc-minimum-msat, producing a channel_update with htlc_maximum_msat < htlc_minimum_msat (channel becomes unroutable). Should this function also do deflt = amount_msat_max(deflt, channel->htlc_minimum_msat) before the final cap, or at least log a warning when that happens?

struct amount_msat configured_max);

/* Given features, what channel_type do we want? */
struct channel_type *desired_channel_type(const tal_t *ctx,
const struct feature_set *our_features,
Expand Down
6 changes: 4 additions & 2 deletions lightningd/dual_open_control.c
Original file line number Diff line number Diff line change
Expand Up @@ -1255,7 +1255,8 @@ wallet_update_channel(struct lightningd *ld,
channel->msat_to_us_max = our_msat;
channel->lease_expiry = lease_expiry;
channel->htlc_minimum_msat = channel->channel_info.their_config.htlc_minimum;
channel->htlc_maximum_msat = htlc_max_possible_send(channel);
channel->htlc_maximum_msat = channel_htlc_maximum_default(channel,
ld->config.htlc_maximum_msat);

tal_free(channel->lease_commit_sig);
channel->lease_commit_sig = tal_steal(channel, lease_commit_sig);
Expand Down Expand Up @@ -1486,7 +1487,8 @@ wallet_commit_channel(struct lightningd *ld,
channel->lease_chan_max_msat = lease_chan_max_msat;
channel->lease_chan_max_ppt = lease_chan_max_ppt;
channel->htlc_minimum_msat = channel_info->their_config.htlc_minimum;
channel->htlc_maximum_msat = htlc_max_possible_send(channel);
channel->htlc_maximum_msat = channel_htlc_maximum_default(channel,
ld->config.htlc_maximum_msat);
/* Filled in when we have PSBT for inflight */
channel->funding_psbt = NULL;

Expand Down
49 changes: 44 additions & 5 deletions tests/test_pay.py
Original file line number Diff line number Diff line change
Expand Up @@ -2069,8 +2069,8 @@ def test_setchannel_usage(node_factory, bitcoind):
DEF_BASE = 10
DEF_BASE_MSAT = Millisatoshi(DEF_BASE)
DEF_PPM = 100
# Minus reserve
MAX_HTLC = Millisatoshi(int(FUNDAMOUNT * 1000 * 0.99))
# Public channels default htlc_maximum_msat to 25% of capacity.
MAX_HTLC = Millisatoshi(int(FUNDAMOUNT * 1000 * 0.25))

l1, l2, l3 = node_factory.get_nodes(3,
opts={'fee-base': DEF_BASE, 'fee-per-satoshi': DEF_PPM})
Expand All @@ -2089,7 +2089,7 @@ def channel_get_config(scid):
db_fees = l1.db_query('SELECT feerate_base, feerate_ppm, htlc_maximum_msat FROM channels;')
assert(db_fees[0]['feerate_base'] == DEF_BASE)
assert(db_fees[0]['feerate_ppm'] == DEF_PPM)
# This will be the capacity - reserves:
# This will be 25% of the capacity:
assert(db_fees[0]['htlc_maximum_msat'] == MAX_HTLC)
# this is also what listpeers should return
channel = only_one(l1.rpc.listpeerchannels()['channels'])
Expand Down Expand Up @@ -2306,7 +2306,8 @@ def test_setchannel_routing(node_factory, bitcoind):
# - htlc max is honored
DEF_BASE = 1
DEF_PPM = 10
MAX_HTLC = Millisatoshi(int(FUNDAMOUNT * 1000 * 0.99))
# Public channels default htlc_maximum_msat to 25% of capacity.
MAX_HTLC = Millisatoshi(int(FUNDAMOUNT * 1000 * 0.25))
MIN_HTLC = Millisatoshi(0)

l1, l2, l3 = node_factory.line_graph(
Expand Down Expand Up @@ -2418,6 +2419,8 @@ def test_setchannel_zero(node_factory, bitcoind):
# - payment can be done using zero fees
DEF_BASE = 1
DEF_PPM = 10
# This is the cap (capacity minus reserve) that setchannel clamps to,
# not the default: below we try to set htlcmax above it.
MAX_HTLC = Millisatoshi(int(FUNDAMOUNT * 1000 * 0.99))

l1, l2, l3 = node_factory.line_graph(
Expand Down Expand Up @@ -2469,7 +2472,8 @@ def test_setchannel_restart(node_factory, bitcoind):
DEF_BASE = 1
DEF_PPM = 10
MIN_HTLC = Millisatoshi(0)
MAX_HTLC = Millisatoshi(int(FUNDAMOUNT * 1000 * 0.99))
# Public channels default htlc_maximum_msat to 25% of capacity.
MAX_HTLC = Millisatoshi(int(FUNDAMOUNT * 1000 * 0.25))
OPTS = {'may_reconnect': True, 'fee-base': DEF_BASE, 'fee-per-satoshi': DEF_PPM}

l1, l2, l3 = node_factory.line_graph(3, announce_channels=True, wait_for_announce=True, opts=OPTS)
Expand Down Expand Up @@ -2580,6 +2584,41 @@ def test_setchannel_startup_opts(node_factory, bitcoind):
assert result[1]['htlc_maximum_msat'] == Millisatoshi(5)


def test_htlc_maximum_msat_default(node_factory, bitcoind):
"""Public channels default htlc_maximum_msat to 25% of capacity, private
channels to everything we can send"""
# A public channel's capacity is known from the funding output, so we
# advertise well below it to make probing harder.
PUBLIC_MAX = Millisatoshi(int(FUNDAMOUNT * 1000 * 0.25))
l1, l2 = node_factory.line_graph(2, wait_for_announce=True)

scid = only_one(l1.rpc.listpeerchannels()['channels'])['short_channel_id']
assert only_one(l1.rpc.listpeerchannels()['channels'])['maximum_htlc_out_msat'] == PUBLIC_MAX
# Both directions default the same way.
wait_for(lambda: [c['htlc_maximum_msat'] for c in l1.rpc.listchannels(scid)['channels']] == [PUBLIC_MAX, PUBLIC_MAX])

# A private channel has no publicly-known capacity to correlate against,
# so we advertise everything we can send: capacity minus their reserve.
PRIVATE_MAX = Millisatoshi(int(FUNDAMOUNT * 1000 * 0.99))
l3, l4 = node_factory.line_graph(2, announce_channels=False)

assert only_one(l3.rpc.listpeerchannels()['channels'])['maximum_htlc_out_msat'] == PRIVATE_MAX
assert only_one(l4.rpc.listpeerchannels()['channels'])['maximum_htlc_out_msat'] == PRIVATE_MAX


def test_htlc_maximum_msat_not_below_minimum(node_factory, bitcoind):
"""BOLT #7 requires htlc_maximum_msat >= htlc_minimum_msat, so the 25%
public default must not undercut a higher htlc-minimum-msat"""
# 25% of capacity is 250000000msat, so this minimum sits above the default.
HTLC_MIN = Millisatoshi(300000000)
l1, l2 = node_factory.line_graph(2, opts={'htlc-minimum-msat': HTLC_MIN})

for n in (l1, l2):
chan = only_one(n.rpc.listpeerchannels()['channels'])
assert chan['minimum_htlc_out_msat'] == HTLC_MIN
assert chan['maximum_htlc_out_msat'] >= chan['minimum_htlc_out_msat']


@pytest.mark.parametrize("anchors", [False, True])
def test_channel_spendable(node_factory, bitcoind, anchors):
"""Test that spendable_msat is accurate"""
Expand Down
Loading