diff --git a/channeld/channeld.c b/channeld/channeld.c index 262cb387f..d1b898754 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -124,7 +124,9 @@ struct peer { * the channel_updates. */ u32 fee_base; u32 fee_per_satoshi; - struct amount_msat htlc_maximum_msat; + /* Note: the real min constraint is channel->config[REMOTE].htlc_minimum: + * they could kill the channel if we violate that! */ + struct amount_msat htlc_minimum_msat, htlc_maximum_msat; /* The scriptpubkey to use for shutting down. */ u32 *final_index; @@ -361,7 +363,7 @@ static void send_channel_update(struct peer *peer, int disable_flag) disable_flag == ROUTING_FLAGS_DISABLED, peer->cltv_delta, - peer->channel->config[REMOTE].htlc_minimum, + peer->htlc_minimum_msat, peer->fee_base, peer->fee_per_satoshi, peer->htlc_maximum_msat); @@ -3429,10 +3431,13 @@ static void handle_blockheight(struct peer *peer, const u8 *inmsg) static void handle_config_channel(struct peer *peer, const u8 *inmsg) { u32 *base, *ppm; - struct amount_msat *htlc_max; + struct amount_msat *htlc_min, *htlc_max; bool changed; - if (!fromwire_channeld_config_channel(inmsg, inmsg, &base, &ppm, &htlc_max)) + if (!fromwire_channeld_config_channel(inmsg, inmsg, + &base, &ppm, + &htlc_min, + &htlc_max)) master_badmsg(WIRE_CHANNELD_CONFIG_CHANNEL, inmsg); /* only send channel updates if values actually changed */ @@ -3445,6 +3450,10 @@ static void handle_config_channel(struct peer *peer, const u8 *inmsg) peer->fee_per_satoshi = *ppm; changed = true; } + if (htlc_min && !amount_msat_eq(*htlc_min, peer->htlc_minimum_msat)) { + peer->htlc_minimum_msat = *htlc_min; + changed = true; + } if (htlc_max && !amount_msat_eq(*htlc_max, peer->htlc_maximum_msat)) { peer->htlc_maximum_msat = *htlc_max; changed = true; @@ -3755,6 +3764,7 @@ static void init_channel(struct peer *peer) &opener, &peer->fee_base, &peer->fee_per_satoshi, + &peer->htlc_minimum_msat, &peer->htlc_maximum_msat, &local_msat, &points[LOCAL], diff --git a/channeld/channeld_wire.csv b/channeld/channeld_wire.csv index df54a26b7..7100e5c56 100644 --- a/channeld/channeld_wire.csv +++ b/channeld/channeld_wire.csv @@ -35,6 +35,7 @@ msgdata,channeld_init,old_remote_per_commit,pubkey, msgdata,channeld_init,opener,enum side, msgdata,channeld_init,fee_base,u32, msgdata,channeld_init,fee_proportional,u32, +msgdata,channeld_init,htlc_minimum_msat,amount_msat, msgdata,channeld_init,htlc_maximum_msat,amount_msat, msgdata,channeld_init,local_msatoshi,amount_msat, msgdata,channeld_init,our_basepoints,basepoints, @@ -220,6 +221,7 @@ msgdata,channeld_fail_fallen_behind,remote_per_commitment_point,?pubkey, msgtype,channeld_config_channel,1029 msgdata,channeld_config_channel,feerate_base,?u32, msgdata,channeld_config_channel,feerate_ppm,?u32, +msgdata,channeld_config_channel,htlc_minimum,?amount_msat, msgdata,channeld_config_channel,htlc_maximum,?amount_msat, # When we receive announcement_signatures for channel announce diff --git a/contrib/pyln-client/pyln/client/lightning.py b/contrib/pyln-client/pyln/client/lightning.py index 92185ec8a..1c17b1c5a 100644 --- a/contrib/pyln-client/pyln/client/lightning.py +++ b/contrib/pyln-client/pyln/client/lightning.py @@ -1176,7 +1176,7 @@ class LightningRpc(UnixDomainSocketRpc): } return self.call("setchannelfee", payload) - def setchannel(self, id, feebase=None, feeppm=None, htlcmax=None, enforcedelay=None): + def setchannel(self, id, feebase=None, feeppm=None, htlcmin=None, htlcmax=None, enforcedelay=None): """Set configuration a channel/peer {id} (or 'all'). {feebase} is a value in millisatoshi that is added as base fee @@ -1185,6 +1185,9 @@ class LightningRpc(UnixDomainSocketRpc): {feeppm} is a value added proportionally per-millionths to any routed payment volume in satoshi. + {htlcmin} is the minimum (outgoing) htlc amount to allow and + advertize. + {htlcmax} is the maximum (outgoing) htlc amount to allow and advertize. @@ -1196,6 +1199,7 @@ class LightningRpc(UnixDomainSocketRpc): "id": id, "feebase": feebase, "feeppm": feeppm, + "htlcmin": htlcmin, "htlcmax": htlcmax, "enforcedelay": enforcedelay, } diff --git a/doc/lightning-listpeers.7.md b/doc/lightning-listpeers.7.md index 9835e0e25..88905de2c 100644 --- a/doc/lightning-listpeers.7.md +++ b/doc/lightning-listpeers.7.md @@ -87,6 +87,7 @@ On success, an object containing **peers** is returned. It is an array of objec - **spendable_msat** (msat, optional): total we could send through channel - **receivable_msat** (msat, optional): total peer could send through channel - **minimum_htlc_in_msat** (msat, optional): the minimum amount HTLC we accept + - **minimum_htlc_out_msat** (msat, optional): the minimum amount HTLC we will send - **maximum_htlc_out_msat** (msat, optional): the maximum amount HTLC we will send - **their_to_self_delay** (u32, optional): the number of blocks before they can take their funds if they unilateral close - **our_to_self_delay** (u32, optional): the number of blocks before we can take our funds if we unilateral close @@ -379,4 +380,4 @@ Main web site: Lightning RFC site (BOLT \#9): -[comment]: # ( SHA256STAMP:8e30caf48aed46acc7c053a355867dc8b8624035dba4ea7668d30d86b8d827cd) +[comment]: # ( SHA256STAMP:147b7008c8f4acb031df625e0731614339a75ee5861cb9f40cd542b1017e3660) diff --git a/doc/lightning-setchannel.7.md b/doc/lightning-setchannel.7.md index 658511a18..f915b7968 100644 --- a/doc/lightning-setchannel.7.md +++ b/doc/lightning-setchannel.7.md @@ -1,20 +1,24 @@ -lightning-setchannel -- Command for configuring fees / maximum htlc on a lightning channel +lightning-setchannel -- Command for configuring fees / htlc range advertized for a channel =========================================================================================== SYNOPSIS -------- -**setchannel** *id* [*feebase*] [*feeppm*] [*htlcmax*] [*enforcedelay*] +**setchannel** *id* [*feebase*] [*feeppm*] [*htlcmin*] [*htlcmax*] [*enforcedelay*] DESCRIPTION ----------- The **setchannel** RPC command sets channel specific routing fees, and -`htlc_maximum_msat` as defined in BOLT \#7. The channel has to be in +`htlc_minimum_msat` or `htlc_maximum_msat` as defined in BOLT \#7. The channel has to be in normal or awaiting state. This can be checked by **listpeers** reporting a *state* of CHANNELD\_NORMAL or CHANNELD\_AWAITING\_LOCKIN for the channel. +These changes (for a public channel) will be broadcast to the rest of +the network (though many nodes limit the rate of such changes they +will accept: we allow 2 a day, with a few extra occasionally). + *id* is required and should contain a scid (short channel ID), channel id or peerid (pubkey) of the channel to be modified. If *id* is set to "all", the updates are applied to all channels in states @@ -31,6 +35,13 @@ to any routed payment volume in satoshi. For example, if ppm is 1,000 and 1,000,000 satoshi is being routed through the channel, an proportional fee of 1,000 satoshi is added, resulting in a 0.1% fee. +*htlcmin* is an optional value that limits how small an HTLC we will +send: if omitted, it is unchanged (the default is no lower limit). It +can be a whole number, or a whole number ending in *msat* or *sat*, or +a number with three decimal places ending in *sat*, or a number with 1 +to 11 decimal places ending in *btc*. The peer also enforces a +minimum for the channel: setting it below will be ignored. + *htlcmax* is an optional value that limits how large an HTLC we will send: if omitted, it is unchanged (the default is no effective limit). It can be a whole number, or a whole number ending in *msat* @@ -55,8 +66,11 @@ On success, an object containing **channels** is returned. It is an array of ob - **channel_id** (hex): The channel_id of the channel (always 64 characters) - **fee_base_msat** (msat): The resulting feebase (this is the BOLT #7 name) - **fee_proportional_millionths** (u32): The resulting feeppm (this is the BOLT #7 name) +- **minimum_htlc_out_msat** (msat): The resulting htlcmin we will advertize (the BOLT #7 name is htlc_minimum_msat) - **maximum_htlc_out_msat** (msat): The resulting htlcmax we will advertize (the BOLT #7 name is htlc_maximum_msat) - **short_channel_id** (short_channel_id, optional): the short_channel_id (if locked in) +- the following warnings are possible: + - **warning_htlcmin_too_low**: The requested htlcmin was too low for this peer, so we set it to the minimum they will allow [comment]: # (GENERATE-FROM-SCHEMA-END) @@ -86,4 +100,4 @@ RESOURCES Main web site: -[comment]: # ( SHA256STAMP:25c6733af784e8a21a8eed4bcb0f12767ae49d16fe623187ae5313b5bb5cdd80) +[comment]: # ( SHA256STAMP:0f153e7dddce61bc921b3743472f11316c5984b9b1459cac1b201d6f51ec1be1) diff --git a/doc/schemas/listpeers.schema.json b/doc/schemas/listpeers.schema.json index cada820f1..f68d55809 100644 --- a/doc/schemas/listpeers.schema.json +++ b/doc/schemas/listpeers.schema.json @@ -418,6 +418,10 @@ "type": "msat", "description": "the minimum amount HTLC we accept" }, + "minimum_htlc_out_msat": { + "type": "msat", + "description": "the minimum amount HTLC we will send" + }, "maximum_htlc_out_msat": { "type": "msat", "description": "the maximum amount HTLC we will send" @@ -776,6 +780,7 @@ "spendable_msat": {}, "receivable_msat": {}, "minimum_htlc_in_msat": {}, + "minimum_htlc_out_msat": {}, "maximum_htlc_out_msat": {}, "spendable_msatoshi": {}, "receivable_msatoshi": {}, @@ -864,6 +869,7 @@ "spendable_msat": {}, "receivable_msat": {}, "minimum_htlc_in_msat": {}, + "minimum_htlc_out_msat": {}, "maximum_htlc_out_msat": {}, "spendable_msatoshi": {}, "receivable_msatoshi": {}, @@ -953,6 +959,7 @@ "spendable_msat": {}, "receivable_msat": {}, "minimum_htlc_in_msat": {}, + "minimum_htlc_out_msat": {}, "maximum_htlc_out_msat": {}, "spendable_msatoshi": {}, "receivable_msatoshi": {}, @@ -1041,6 +1048,7 @@ "spendable_msat": {}, "receivable_msat": {}, "minimum_htlc_in_msat": {}, + "minimum_htlc_out_msat": {}, "maximum_htlc_out_msat": {}, "spendable_msatoshi": {}, "receivable_msatoshi": {}, diff --git a/doc/schemas/setchannel.schema.json b/doc/schemas/setchannel.schema.json index fcd896a51..512e4dbdf 100644 --- a/doc/schemas/setchannel.schema.json +++ b/doc/schemas/setchannel.schema.json @@ -17,6 +17,7 @@ "channel_id", "fee_base_msat", "fee_proportional_millionths", + "minimum_htlc_out_msat", "maximum_htlc_out_msat" ], "properties": { @@ -42,6 +43,14 @@ "type": "u32", "description": "The resulting feeppm (this is the BOLT #7 name)" }, + "minimum_htlc_out_msat": { + "type": "msat", + "description": "The resulting htlcmin we will advertize (the BOLT #7 name is htlc_minimum_msat)" + }, + "warning_htlcmin_too_low": { + "type": "string", + "description": "The requested htlcmin was too low for this peer, so we set it to the minimum they will allow" + }, "maximum_htlc_out_msat": { "type": "msat", "description": "The resulting htlcmax we will advertize (the BOLT #7 name is htlc_maximum_msat)" diff --git a/lightningd/channel.c b/lightningd/channel.c index 3bb5ad027..a6555fc76 100644 --- a/lightningd/channel.c +++ b/lightningd/channel.c @@ -410,10 +410,11 @@ struct channel *new_channel(struct peer *peer, u64 dbid, secp256k1_ecdsa_signature *lease_commit_sig STEALS, u32 lease_chan_max_msat, u16 lease_chan_max_ppt, + struct amount_msat htlc_minimum_msat, struct amount_msat htlc_maximum_msat) { struct channel *channel = tal(peer->ld, struct channel); - struct amount_msat htlc_max; + struct amount_msat htlc_min, htlc_max; assert(dbid != 0); channel->peer = peer; @@ -509,8 +510,12 @@ struct channel *new_channel(struct peer *peer, u64 dbid, channel->blockheight_states = dup_height_states(channel, height_states); channel->channel_update = NULL; - /* DB migration, for example, sets this to bignum; correct - * here */ + /* DB migration, for example, sets min to 0, max to large: fixup */ + htlc_min = channel->channel_info.their_config.htlc_minimum; + if (amount_msat_greater(htlc_min, htlc_minimum_msat)) + 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; diff --git a/lightningd/channel.h b/lightningd/channel.h index 5390ad74a..cd0113317 100644 --- a/lightningd/channel.h +++ b/lightningd/channel.h @@ -196,8 +196,8 @@ struct channel { * peer via option_data_loss_protect? */ const struct pubkey *future_per_commitment_point; - /* Max htlc amount allowed in channel. */ - struct amount_msat htlc_maximum_msat; + /* Min/max htlc amount allowed in channel. */ + struct amount_msat htlc_minimum_msat, htlc_maximum_msat; /* Feerate per channel */ u32 feerate_base, feerate_ppm; @@ -205,7 +205,7 @@ struct channel { /* But allow these feerates/htlcs up until this time. */ struct timeabs old_feerate_timeout; u32 old_feerate_base, old_feerate_ppm; - struct amount_msat old_htlc_maximum_msat; + struct amount_msat old_htlc_minimum_msat, old_htlc_maximum_msat; /* If they used option_upfront_shutdown_script. */ const u8 *remote_upfront_shutdown_script; @@ -315,6 +315,7 @@ struct channel *new_channel(struct peer *peer, u64 dbid, secp256k1_ecdsa_signature *lease_commit_sig STEALS, u32 lease_chan_max_msat, u16 lease_chan_max_ppt, + struct amount_msat htlc_minimum_msat, struct amount_msat htlc_maximum_msat); /* new_inflight - Create a new channel_inflight for a channel */ diff --git a/lightningd/channel_control.c b/lightningd/channel_control.c index 2b21d5241..53b433967 100644 --- a/lightningd/channel_control.c +++ b/lightningd/channel_control.c @@ -702,6 +702,7 @@ void peer_start_channeld(struct channel *channel, channel->opener, channel->feerate_base, channel->feerate_ppm, + channel->htlc_minimum_msat, channel->htlc_maximum_msat, channel->our_msat, &channel->local_basepoints, diff --git a/lightningd/dual_open_control.c b/lightningd/dual_open_control.c index d2fa86f4a..f10fffe9b 100644 --- a/lightningd/dual_open_control.c +++ b/lightningd/dual_open_control.c @@ -1110,6 +1110,7 @@ wallet_update_channel(struct lightningd *ld, channel->msat_to_us_min = our_msat; 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); tal_free(channel->lease_commit_sig); @@ -1253,6 +1254,7 @@ 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); /* Now we finally put it in the database. */ diff --git a/lightningd/opening_control.c b/lightningd/opening_control.c index be576e75a..ed23a5ae0 100644 --- a/lightningd/opening_control.c +++ b/lightningd/opening_control.c @@ -210,6 +210,7 @@ wallet_commit_channel(struct lightningd *ld, take(new_height_states(NULL, uc->fc ? LOCAL : REMOTE, &lease_start_blockheight)), 0, NULL, 0, 0, /* No leases on v1s */ + AMOUNT_MSAT(0), /* No htlc_minimum_msat */ AMOUNT_MSAT(-1ULL)); /* No htlc_maximum_msat */ /* Now we finally put it in the database. */ diff --git a/lightningd/peer_control.c b/lightningd/peer_control.c index f25dc0006..39c6b4a31 100644 --- a/lightningd/peer_control.c +++ b/lightningd/peer_control.c @@ -861,6 +861,9 @@ static void json_add_channel(struct lightningd *ld, channel->our_config.htlc_minimum, "htlc_minimum_msat", "minimum_htlc_in_msat"); + json_add_amount_msat_only(response, + "minimum_htlc_out_msat", + channel->htlc_minimum_msat); json_add_amount_msat_only(response, "maximum_htlc_out_msat", channel->htlc_maximum_msat); @@ -2010,21 +2013,27 @@ static struct command_result *param_msat_u32(struct command *cmd, static void set_channel_config(struct command *cmd, struct channel *channel, u32 *base, u32 *ppm, + struct amount_msat *htlc_min, struct amount_msat *htlc_max, u32 delaysecs, struct json_stream *response, bool add_details) { + bool warn_cannot_set_min = false; + /* We only need to defer values if we *increase* fees (or drop - * max); we always allow users to overpay fees. */ + * max, increase min); we always allow users to overpay fees. */ if ((base && *base > channel->feerate_base) || (ppm && *ppm > channel->feerate_ppm) + || (htlc_min + && amount_msat_greater(*htlc_min, channel->htlc_minimum_msat)) || (htlc_max && amount_msat_less(*htlc_max, channel->htlc_maximum_msat))) { channel->old_feerate_timeout = timeabs_add(time_now(), time_from_sec(delaysecs)); channel->old_feerate_base = channel->feerate_base; channel->old_feerate_ppm = channel->feerate_ppm; + channel->old_htlc_minimum_msat = channel->htlc_minimum_msat; channel->old_htlc_maximum_msat = channel->htlc_maximum_msat; } @@ -2033,6 +2042,17 @@ static void set_channel_config(struct command *cmd, struct channel *channel, channel->feerate_base = *base; if (ppm) channel->feerate_ppm = *ppm; + if (htlc_min) { + struct amount_msat actual_min; + + /* We can't send something they'll refuse: check that here. */ + actual_min = channel->channel_info.their_config.htlc_minimum; + if (amount_msat_less(*htlc_min, actual_min)) { + warn_cannot_set_min = true; + channel->htlc_minimum_msat = actual_min; + } else + channel->htlc_minimum_msat = *htlc_min; + } if (htlc_max) channel->htlc_maximum_msat = *htlc_max; @@ -2040,7 +2060,7 @@ static void set_channel_config(struct command *cmd, struct channel *channel, if (channel->owner && streq(channel->owner->name, "channeld")) subd_send_msg(channel->owner, take(towire_channeld_config_channel(NULL, base, ppm, - htlc_max))); + htlc_min, htlc_max))); /* save values to database */ wallet_channel_save(cmd->ld->wallet, channel); @@ -2059,6 +2079,12 @@ static void set_channel_config(struct command *cmd, struct channel *channel, amount_msat(channel->feerate_base)); json_add_u32(response, "fee_proportional_millionths", channel->feerate_ppm); + json_add_amount_msat_only(response, + "minimum_htlc_out_msat", + channel->htlc_minimum_msat); + if (warn_cannot_set_min) + json_add_string(response, "warning_htlcmin_too_low", + "Set minimum_htlc_out_msat to minimum allowed by peer"); json_add_amount_msat_only(response, "maximum_htlc_out_msat", channel->htlc_maximum_msat); @@ -2110,13 +2136,13 @@ static struct command_result *json_setchannelfee(struct command *cmd, channel->state != CHANNELD_AWAITING_LOCKIN && channel->state != DUALOPEND_AWAITING_LOCKIN) continue; - set_channel_config(cmd, channel, base, ppm, NULL, + set_channel_config(cmd, channel, base, ppm, NULL, NULL, *delaysecs, response, false); } /* single channel should be updated */ } else { - set_channel_config(cmd, channel, base, ppm, NULL, + set_channel_config(cmd, channel, base, ppm, NULL, NULL, *delaysecs, response, false); } @@ -2149,13 +2175,14 @@ static struct command_result *json_setchannel(struct command *cmd, struct peer *peer; struct channel *channel; u32 *base, *ppm, *delaysecs; - struct amount_msat *htlc_max; + struct amount_msat *htlc_min, *htlc_max; /* Parse the JSON command */ if (!param(cmd, buffer, params, p_req("id", param_channel_or_all, &channel), p_opt("feebase", param_msat_u32, &base), p_opt("feeppm", param_number, &ppm), + p_opt("htlcmin", param_msat, &htlc_min), p_opt("htlcmax", param_msat, &htlc_max), p_opt_def("enforcedelay", param_number, &delaysecs, 600), NULL)) @@ -2182,13 +2209,15 @@ static struct command_result *json_setchannel(struct command *cmd, channel->state != CHANNELD_AWAITING_LOCKIN && channel->state != DUALOPEND_AWAITING_LOCKIN) continue; - set_channel_config(cmd, channel, base, ppm, htlc_max, + set_channel_config(cmd, channel, base, ppm, + htlc_min, htlc_max, *delaysecs, response, true); } /* single channel should be updated */ } else { - set_channel_config(cmd, channel, base, ppm, htlc_max, + set_channel_config(cmd, channel, base, ppm, + htlc_min, htlc_max, *delaysecs, response, true); } diff --git a/lightningd/peer_htlcs.c b/lightningd/peer_htlcs.c index fc55f2b27..e0d902c71 100644 --- a/lightningd/peer_htlcs.c +++ b/lightningd/peer_htlcs.c @@ -656,16 +656,18 @@ static void forward_htlc(struct htlc_in *hin, "Allowing payment using older feerate"); } - if (amount_msat_greater(amt_to_forward, next->htlc_maximum_msat)) { - /* Are we in old-max grace-period? */ + if (amount_msat_greater(amt_to_forward, next->htlc_maximum_msat) + || amount_msat_less(amt_to_forward, next->htlc_minimum_msat)) { + /* Are we in old-range grace-period? */ if (!time_before(time_now(), next->old_feerate_timeout) + || amount_msat_less(amt_to_forward, next->old_htlc_minimum_msat) || amount_msat_greater(amt_to_forward, next->old_htlc_maximum_msat)) { failmsg = towire_temporary_channel_failure(tmpctx, get_channel_update(next)); goto fail; } log_info(hin->key.channel->log, - "Allowing large htlc using older htlc_maximum_msat"); + "Allowing htlc using older htlc_minimum/maximum_msat"); } if (!check_cltv(hin, cltv_expiry, outgoing_cltv_value, diff --git a/lightningd/routehint.c b/lightningd/routehint.c index b5f37776a..a9879e3a3 100644 --- a/lightningd/routehint.c +++ b/lightningd/routehint.c @@ -114,6 +114,8 @@ routehint_candidates(const tal_t *ctx, continue; } + /* FIXME: we don't actually check htlc_minimum_msat! */ + /* If they set an htlc_maximum_msat, consider that the * capacity ceiling. We *could* do multiple HTLCs, * but presumably that would defeat the spirit of the diff --git a/lightningd/test/run-invoice-select-inchan.c b/lightningd/test/run-invoice-select-inchan.c index 1f7db910b..08bd6eef9 100644 --- a/lightningd/test/run-invoice-select-inchan.c +++ b/lightningd/test/run-invoice-select-inchan.c @@ -626,7 +626,7 @@ void towire_bigsize(u8 **pptr UNNEEDED, const bigsize_t val UNNEEDED) void towire_channel_id(u8 **pptr UNNEEDED, const struct channel_id *channel_id UNNEEDED) { fprintf(stderr, "towire_channel_id called!\n"); abort(); } /* Generated stub for towire_channeld_config_channel */ -u8 *towire_channeld_config_channel(const tal_t *ctx UNNEEDED, u32 *feerate_base UNNEEDED, u32 *feerate_ppm UNNEEDED, struct amount_msat *htlc_maximum UNNEEDED) +u8 *towire_channeld_config_channel(const tal_t *ctx UNNEEDED, u32 *feerate_base UNNEEDED, u32 *feerate_ppm UNNEEDED, struct amount_msat *htlc_minimum UNNEEDED, struct amount_msat *htlc_maximum UNNEEDED) { fprintf(stderr, "towire_channeld_config_channel called!\n"); abort(); } /* Generated stub for towire_channeld_dev_memleak */ u8 *towire_channeld_dev_memleak(const tal_t *ctx UNNEEDED) diff --git a/tests/test_pay.py b/tests/test_pay.py index 0e532248d..fee9e3f30 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -1882,7 +1882,7 @@ def test_setchannel_usage(node_factory, bitcoind): def channel_get_config(scid): return l1.db.query( - 'SELECT feerate_base, feerate_ppm, htlc_maximum_msat FROM channels ' + 'SELECT feerate_base, feerate_ppm, htlc_minimum_msat, htlc_maximum_msat FROM channels ' 'WHERE short_channel_id=\'{}\';'.format(scid)) # get short channel id @@ -1900,8 +1900,8 @@ def test_setchannel_usage(node_factory, bitcoind): assert peers[0]['channels'][0]['fee_proportional_millionths'] == DEF_PPM assert peers[0]['channels'][0]['maximum_htlc_out_msat'] == MAX_HTLC - # custom setchannel scid - result = l1.rpc.setchannel(scid, 1337, 137, 133337) + # custom setchannel scid + result = l1.rpc.setchannel(scid, 1337, 137, 17, 133337) # check result format assert(len(result['channels']) == 1) @@ -1910,22 +1910,26 @@ def test_setchannel_usage(node_factory, bitcoind): assert(result['channels'][0]['short_channel_id'] == scid) assert(result['channels'][0]['fee_base_msat'] == 1337) assert(result['channels'][0]['fee_proportional_millionths'] == 137) + assert(result['channels'][0]['minimum_htlc_out_msat'] == 17) assert(result['channels'][0]['maximum_htlc_out_msat'] == 133337) # check if custom values made it into the database db_fees = channel_get_config(scid) assert(db_fees[0]['feerate_base'] == 1337) assert(db_fees[0]['feerate_ppm'] == 137) + assert(db_fees[0]['htlc_minimum_msat'] == 17) assert(db_fees[0]['htlc_maximum_msat'] == 133337) # also check for updated values in `listpeers` peers = l1.rpc.listpeers()['peers'] assert peers[0]['channels'][0]['fee_base_msat'] == Millisatoshi(1337) assert peers[0]['channels'][0]['fee_proportional_millionths'] == 137 + assert peers[0]['channels'][0]['minimum_htlc_out_msat'] == 17 assert peers[0]['channels'][0]['maximum_htlc_out_msat'] == 133337 # wait for gossip and check if l1 sees new fees in listchannels wait_for(lambda: [c['base_fee_millisatoshi'] for c in l1.rpc.listchannels(scid)['channels']] == [DEF_BASE, 1337]) wait_for(lambda: [c['fee_per_millionth'] for c in l1.rpc.listchannels(scid)['channels']] == [DEF_PPM, 137]) + wait_for(lambda: [c['htlc_minimum_msat'] for c in l1.rpc.listchannels(scid)['channels']] == [0, 17]) wait_for(lambda: [c['htlc_maximum_msat'] for c in l1.rpc.listchannels(scid)['channels']] == [MAX_HTLC, 133337]) # also test with named and missing parameters @@ -1935,6 +1939,7 @@ def test_setchannel_usage(node_factory, bitcoind): assert(result['channels'][0]['short_channel_id'] == scid) assert(result['channels'][0]['fee_base_msat'] == 1337) assert(result['channels'][0]['fee_proportional_millionths'] == 42) + assert result['channels'][0]['minimum_htlc_out_msat'] == 17 assert(result['channels'][0]['maximum_htlc_out_msat'] == 133337) result = l1.rpc.setchannel(feebase=43, id=scid) @@ -1943,6 +1948,16 @@ def test_setchannel_usage(node_factory, bitcoind): assert(result['channels'][0]['short_channel_id'] == scid) assert(result['channels'][0]['fee_base_msat'] == 43) assert(result['channels'][0]['fee_proportional_millionths'] == 42) + assert result['channels'][0]['minimum_htlc_out_msat'] == 17 + assert(result['channels'][0]['maximum_htlc_out_msat'] == 133337) + + result = l1.rpc.setchannel(htlcmin=45, id=scid) + assert(len(result['channels']) == 1) + assert(re.match('^[0-9a-f]{64}$', result['channels'][0]['channel_id'])) + assert(result['channels'][0]['short_channel_id'] == scid) + assert(result['channels'][0]['fee_base_msat'] == 43) + assert(result['channels'][0]['fee_proportional_millionths'] == 42) + assert result['channels'][0]['minimum_htlc_out_msat'] == 45 assert(result['channels'][0]['maximum_htlc_out_msat'] == 133337) result = l1.rpc.setchannel(htlcmax=43333, id=scid) @@ -1951,6 +1966,7 @@ def test_setchannel_usage(node_factory, bitcoind): assert(result['channels'][0]['short_channel_id'] == scid) assert(result['channels'][0]['fee_base_msat'] == 43) assert(result['channels'][0]['fee_proportional_millionths'] == 42) + assert result['channels'][0]['minimum_htlc_out_msat'] == 45 assert(result['channels'][0]['maximum_htlc_out_msat'] == 43333) # check if negative fees raise error and DB keeps values @@ -2079,6 +2095,7 @@ def test_setchannel_routing(node_factory, bitcoind): DEF_BASE = 1 DEF_PPM = 10 MAX_HTLC = Millisatoshi(int(FUNDAMOUNT * 1000 * 0.99)) + MIN_HTLC = Millisatoshi(0) l1, l2, l3 = node_factory.line_graph( 3, announce_channels=True, wait_for_announce=True, @@ -2089,11 +2106,12 @@ def test_setchannel_routing(node_factory, bitcoind): scid = l2.get_channel_scid(l3) # TEST CUSTOM VALUES - l2.rpc.setchannel(scid, 1337, 137, 4000000, enforcedelay=0) + l2.rpc.setchannel(scid, 1337, 137, 17, 4000000, enforcedelay=0) # wait for l1 to see updated channel via gossip wait_for(lambda: [c['base_fee_millisatoshi'] for c in l1.rpc.listchannels(scid)['channels']] == [1337, DEF_BASE]) wait_for(lambda: [c['fee_per_millionth'] for c in l1.rpc.listchannels(scid)['channels']] == [137, DEF_PPM]) + wait_for(lambda: [c['htlc_minimum_msat'] for c in l1.rpc.listchannels(scid)['channels']] == [17, MIN_HTLC]) wait_for(lambda: [c['htlc_maximum_msat'] for c in l1.rpc.listchannels(scid)['channels']] == [4000000, MAX_HTLC]) # test fees are applied to HTLC forwards @@ -2136,7 +2154,7 @@ def test_setchannel_routing(node_factory, bitcoind): # In case l3 includes a routehint, we need to make sure they also know # about the new fees, otherwise we may end up with the old feerate - wait_for(lambda: [(c['base_fee_millisatoshi'], c['fee_per_millionth'], c['htlc_maximum_msat'], c['active']) for c in l3.rpc.listchannels(scid)['channels']] == [(1337, 137, 4000000, True), (DEF_BASE, DEF_PPM, MAX_HTLC, True)]) + wait_for(lambda: [(c['base_fee_millisatoshi'], c['fee_per_millionth'], c['htlc_minimum_msat'], c['htlc_maximum_msat'], c['active']) for c in l3.rpc.listchannels(scid)['channels']] == [(1337, 137, 17, 4000000, True), (DEF_BASE, DEF_PPM, MIN_HTLC, MAX_HTLC, True)]) # do and check actual payment inv = l3.rpc.invoice(4000000, 'test_setchannel_2', 'desc') @@ -2153,9 +2171,33 @@ def test_setchannel_routing(node_factory, bitcoind): l1.rpc.sendpay(route_ok, inv['payment_hash'], payment_secret=inv['payment_secret']) l1.rpc.waitsendpay(inv['payment_hash']) + # Now try below minimum + route_ok = l1.rpc.getroute(l3.info['id'], 17, 1)["route"] + assert len(route_ok) == 2 + assert route_ok[0]['msatoshi'] == 1337 + 17 + assert route_ok[1]['msatoshi'] == 17 + + route_bad = copy.deepcopy(route_ok) + route_bad[0]['msatoshi'] = 1337 + 16 + route_bad[1]['msatoshi'] = 16 + route_bad[0]['amount_msat'] = Millisatoshi(1337 + 16) + route_bad[1]['amount_msat'] = Millisatoshi(16) + assert route_bad != route_ok + + inv = l3.rpc.invoice(17, 'test_setchannel_3', 'desc') + + # This will fail. + l1.rpc.sendpay(route_bad, inv['payment_hash'], payment_secret=inv['payment_secret']) + with pytest.raises(RpcError, match='WIRE_TEMPORARY_CHANNEL_FAILURE'): + l1.rpc.waitsendpay(inv['payment_hash']) + + # This will succeed + l1.rpc.sendpay(route_ok, inv['payment_hash'], payment_secret=inv['payment_secret']) + l1.rpc.waitsendpay(inv['payment_hash']) + # Check that this one warns about capacity! inv = l3.rpc.call('invoice', {'msatoshi': 4001793, - 'label': 'test_setchannel_3', + 'label': 'test_setchannel_4', 'description': 'desc'}) assert 'warning_capacity' in inv @@ -2213,6 +2255,7 @@ def test_setchannel_restart(node_factory, bitcoind): # - l1 routing can be made to l3 and global (1 10) fees are applied DEF_BASE = 1 DEF_PPM = 10 + MIN_HTLC = Millisatoshi(0) MAX_HTLC = Millisatoshi(int(FUNDAMOUNT * 1000 * 0.99)) OPTS = {'may_reconnect': True, 'fee-base': DEF_BASE, 'fee-per-satoshi': DEF_PPM} @@ -2223,7 +2266,7 @@ def test_setchannel_restart(node_factory, bitcoind): scid23 = l2.get_channel_scid(l3) # l2 set custom fees - l2.rpc.setchannel(scid23, 1337, 137, 500001) + l2.rpc.setchannel(scid23, 1337, 137, 17, 500001) # restart l2 and reconnect l2.restart() @@ -2234,11 +2277,11 @@ def test_setchannel_restart(node_factory, bitcoind): wait_for(lambda: [c['active'] for c in l1.rpc.listchannels(scid12)['channels']] == [True, True]) # l1 wait for channel update from l2 - wait_for(lambda: [(c['base_fee_millisatoshi'], c['fee_per_millionth'], c['htlc_maximum_msat'], c['active']) for c in l1.rpc.listchannels(scid23)['channels']] == [(1337, 137, 500001, True), (DEF_BASE, DEF_PPM, MAX_HTLC, True)]) + wait_for(lambda: [(c['base_fee_millisatoshi'], c['fee_per_millionth'], c['htlc_minimum_msat'], c['htlc_maximum_msat'], c['active']) for c in l1.rpc.listchannels(scid23)['channels']] == [(1337, 137, 17, 500001, True), (DEF_BASE, DEF_PPM, MIN_HTLC, MAX_HTLC, True)]) # In case l3 includes a routehint, we need to make sure they also know # about the new fees, otherwise we may end up with the old feerate - wait_for(lambda: [(c['base_fee_millisatoshi'], c['fee_per_millionth'], c['htlc_maximum_msat'], c['active']) for c in l3.rpc.listchannels(scid23)['channels']] == [(1337, 137, 500001, True), (DEF_BASE, DEF_PPM, MAX_HTLC, True)]) + wait_for(lambda: [(c['base_fee_millisatoshi'], c['fee_per_millionth'], c['htlc_minimum_msat'], c['htlc_maximum_msat'], c['active']) for c in l3.rpc.listchannels(scid23)['channels']] == [(1337, 137, 17, 500001, True), (DEF_BASE, DEF_PPM, MIN_HTLC, MAX_HTLC, True)]) # l1 can make payment to l3 with custom fees being applied # Note: BOLT #7 math works out to 1405 msat fees @@ -2269,7 +2312,7 @@ def test_setchannel_all(node_factory, bitcoind): scid3 = l1.get_channel_scid(l3) # now try to set all (two) channels using wildcard syntax - result = l1.rpc.setchannel("all", 0xDEAD, 0xBEEF, 0xCAFE) + result = l1.rpc.setchannel("all", 0xDEAD, 0xBEEF, 0xBAD, 0xCAFE) wait_for(lambda: [c['base_fee_millisatoshi'] for c in l1.rpc.listchannels(scid2)['channels']] == [DEF_BASE, 0xDEAD]) wait_for(lambda: [c['fee_per_millionth'] for c in l1.rpc.listchannels(scid2)['channels']] == [DEF_PPM, 0xBEEF]) @@ -2281,11 +2324,13 @@ def test_setchannel_all(node_factory, bitcoind): assert result['channels'][0]['short_channel_id'] == scid2 assert result['channels'][0]['fee_base_msat'] == 0xDEAD assert result['channels'][0]['fee_proportional_millionths'] == 0xBEEF + assert result['channels'][0]['minimum_htlc_out_msat'] == 0xBAD assert result['channels'][0]['maximum_htlc_out_msat'] == 0xCAFE assert result['channels'][1]['peer_id'] == l3.info['id'] assert result['channels'][1]['short_channel_id'] == scid3 assert result['channels'][1]['fee_base_msat'] == 0xDEAD assert result['channels'][1]['fee_proportional_millionths'] == 0xBEEF + assert result['channels'][1]['minimum_htlc_out_msat'] == 0xBAD assert result['channels'][1]['maximum_htlc_out_msat'] == 0xCAFE diff --git a/wallet/db.c b/wallet/db.c index 09721db2c..79576b9a6 100644 --- a/wallet/db.c +++ b/wallet/db.c @@ -871,6 +871,7 @@ static struct migration dbmigrations[] = { {SQL("ALTER TABLE channel_funding_inflights ADD lease_fee BIGINT DEFAULT 0"), NULL}, /* Default is too big; we set to max after loading */ {SQL("ALTER TABLE channels ADD htlc_maximum_msat BIGINT DEFAULT 2100000000000000"), NULL}, + {SQL("ALTER TABLE channels ADD htlc_minimum_msat BIGINT DEFAULT 0"), NULL}, }; /** diff --git a/wallet/test/run-wallet.c b/wallet/test/run-wallet.c index d6573857e..71280026a 100644 --- a/wallet/test/run-wallet.c +++ b/wallet/test/run-wallet.c @@ -694,7 +694,7 @@ void topology_add_sync_waiter_(const tal_t *ctx UNNEEDED, u8 *towire_channel_disabled(const tal_t *ctx UNNEEDED) { fprintf(stderr, "towire_channel_disabled called!\n"); abort(); } /* Generated stub for towire_channeld_config_channel */ -u8 *towire_channeld_config_channel(const tal_t *ctx UNNEEDED, u32 *feerate_base UNNEEDED, u32 *feerate_ppm UNNEEDED, struct amount_msat *htlc_maximum UNNEEDED) +u8 *towire_channeld_config_channel(const tal_t *ctx UNNEEDED, u32 *feerate_base UNNEEDED, u32 *feerate_ppm UNNEEDED, struct amount_msat *htlc_minimum UNNEEDED, struct amount_msat *htlc_maximum UNNEEDED) { fprintf(stderr, "towire_channeld_config_channel called!\n"); abort(); } /* Generated stub for towire_channeld_dev_memleak */ u8 *towire_channeld_dev_memleak(const tal_t *ctx UNNEEDED) @@ -1598,6 +1598,7 @@ static bool test_channel_inflight_crud(struct lightningd *ld, const tal_t *ctx) 100, lease_commit_sig, 7777, 22, + AMOUNT_MSAT(0), AMOUNT_MSAT(-1ULL)); db_begin_transaction(w->db); CHECK(!wallet_err); diff --git a/wallet/wallet.c b/wallet/wallet.c index 7cc5676f0..47e43bfab 100644 --- a/wallet/wallet.c +++ b/wallet/wallet.c @@ -1263,7 +1263,7 @@ static struct channel *wallet_stmt2channel(struct wallet *w, struct db_stmt *stm struct pubkey local_funding_pubkey; struct pubkey *future_per_commitment_point; struct amount_sat funding_sat, our_funding_sat; - struct amount_msat push_msat, our_msat, msat_to_us_min, msat_to_us_max, htlc_maximum_msat; + struct amount_msat push_msat, our_msat, msat_to_us_min, msat_to_us_max, htlc_minimum_msat, htlc_maximum_msat; struct channel_type *type; secp256k1_ecdsa_signature *lease_commit_sig; u32 lease_chan_max_msat; @@ -1403,6 +1403,7 @@ static struct channel *wallet_stmt2channel(struct wallet *w, struct db_stmt *stm db_col_amount_msat(stmt, "msatoshi_local", &our_msat); db_col_amount_msat(stmt, "msatoshi_to_us_min", &msat_to_us_min); db_col_amount_msat(stmt, "msatoshi_to_us_max", &msat_to_us_max); + db_col_amount_msat(stmt, "htlc_minimum_msat", &htlc_minimum_msat); db_col_amount_msat(stmt, "htlc_maximum_msat", &htlc_maximum_msat); if (!db_col_is_null(stmt, "lease_commit_sig")) { @@ -1480,6 +1481,7 @@ static struct channel *wallet_stmt2channel(struct wallet *w, struct db_stmt *stm lease_commit_sig, lease_chan_max_msat, lease_chan_max_ppt, + htlc_minimum_msat, htlc_maximum_msat); if (!wallet_channel_load_inflights(w, chan)) { @@ -1574,6 +1576,7 @@ static bool wallet_channels_load_active(struct wallet *w) ", lease_commit_sig" ", lease_chan_max_msat" ", lease_chan_max_ppt" + ", htlc_minimum_msat" ", htlc_maximum_msat" " FROM channels" " WHERE state != ?;")); //? 0 @@ -1855,8 +1858,9 @@ void wallet_channel_save(struct wallet *w, struct channel *chan) " lease_commit_sig=?," // 39 " lease_chan_max_msat=?," // 40 " lease_chan_max_ppt=?," // 41 - " htlc_maximum_msat=?" // 42 - " WHERE id=?")); // 43 + " htlc_minimum_msat=?," // 42 + " htlc_maximum_msat=?" // 43 + " WHERE id=?")); // 44 db_bind_u64(stmt, 0, chan->their_shachain.id); if (chan->scid) db_bind_short_channel_id(stmt, 1, chan->scid); @@ -1919,8 +1923,9 @@ void wallet_channel_save(struct wallet *w, struct channel *chan) db_bind_null(stmt, 40); db_bind_null(stmt, 41); } - db_bind_amount_msat(stmt, 42, &chan->htlc_maximum_msat); - db_bind_u64(stmt, 43, chan->dbid); + db_bind_amount_msat(stmt, 42, &chan->htlc_minimum_msat); + db_bind_amount_msat(stmt, 43, &chan->htlc_maximum_msat); + db_bind_u64(stmt, 44, chan->dbid); db_exec_prepared_v2(take(stmt)); wallet_channel_config_save(w, &chan->channel_info.their_config);