diff --git a/contrib/pyln-client/pyln/client/lightning.py b/contrib/pyln-client/pyln/client/lightning.py index 12759d694..92185ec8a 100644 --- a/contrib/pyln-client/pyln/client/lightning.py +++ b/contrib/pyln-client/pyln/client/lightning.py @@ -1176,6 +1176,31 @@ class LightningRpc(UnixDomainSocketRpc): } return self.call("setchannelfee", payload) + def setchannel(self, id, feebase=None, feeppm=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 + to any routed payment. + + {feeppm} is a value added proportionally per-millionths to any + routed payment volume in satoshi. + + {htlcmax} is the maximum (outgoing) htlc amount to allow and + advertize. + + {enforcedelay} is the number of seconds before enforcing this + change. + + """ + payload = { + "id": id, + "feebase": feebase, + "feeppm": feeppm, + "htlcmax": htlcmax, + "enforcedelay": enforcedelay, + } + return self.call("setchannel", payload) + def stop(self): """ Shut down the lightningd process. diff --git a/doc/Makefile b/doc/Makefile index 75e5fbdb1..68d4eb842 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -65,6 +65,7 @@ MANPAGES := doc/lightning-cli.1 \ doc/lightning-sendonion.7 \ doc/lightning-sendonionmessage.7 \ doc/lightning-sendpay.7 \ + doc/lightning-setchannel.7 \ doc/lightning-setchannelfee.7 \ doc/lightning-sendcustommsg.7 \ doc/lightning-signmessage.7 \ diff --git a/doc/index.rst b/doc/index.rst index 349f32086..4671ccae8 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -97,6 +97,7 @@ c-lightning Documentation lightning-sendonionmessage lightning-sendpay lightning-sendpsbt + lightning-setchannel lightning-setchannelfee lightning-signmessage lightning-signpsbt diff --git a/doc/lightning-setchannel.7.md b/doc/lightning-setchannel.7.md new file mode 100644 index 000000000..658511a18 --- /dev/null +++ b/doc/lightning-setchannel.7.md @@ -0,0 +1,89 @@ +lightning-setchannel -- Command for configuring fees / maximum htlc on a lightning channel +=========================================================================================== + +SYNOPSIS +-------- + +**setchannel** *id* [*feebase*] [*feeppm*] [*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 +normal or awaiting state. This can be checked by **listpeers** +reporting a *state* of CHANNELD\_NORMAL or CHANNELD\_AWAITING\_LOCKIN +for the channel. + +*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 +CHANNELD\_NORMAL or CHANNELD\_AWAITING\_LOCKIN. + +*feebase* is an optional value in millisatoshi that is added as base fee to +any routed payment: if omitted, it is unchanged. 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*. + +*feeppm* is an optional value that is added proportionally per-millionths +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. + +*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* +or *sat*, or a number with three decimal places ending in *sat*, or a +number with 1 to 11 decimal places ending in *btc*. + +*enforcedelay* is the number of seconds to delay before enforcing the +new fees/htlc max (default 600, which is ten minutes). This gives the +network a chance to catch up with the new rates and avoids rejecting +HTLCs before they do. This only has an effect if rates are increased +(we always allow users to overpay fees) or *htlcmax* is decreased, and +only applied to a single rate increase per channel (we don't remember +an arbitrary number of prior feerates) and if the node is restarted +the updated configuration is enforced immediately. + +RETURN VALUE +------------ + +[comment]: # (GENERATE-FROM-SCHEMA-START) +On success, an object containing **channels** is returned. It is an array of objects, where each object contains: +- **peer_id** (pubkey): The node_id of the peer +- **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) +- **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) + +[comment]: # (GENERATE-FROM-SCHEMA-END) + +ERRORS +------ + +The following error codes may occur: +- -1: Channel is in incorrect state, i.e. Catchall nonspecific error. +- -32602: JSONRPC2\_INVALID\_PARAMS, i.e. Given id is not a channel ID +or short channel ID. + +AUTHOR +------ + +Michael Schmoock <> is the author of this +feature. Rusty Russell <> is mainly +responsible for the c-lightning project. + +SEE ALSO +-------- + +lightningd-config(5), lightning-fundchannel(7), +lightning-listchannels(7), lightning-listpeers(7) + +RESOURCES +--------- + +Main web site: + +[comment]: # ( SHA256STAMP:25c6733af784e8a21a8eed4bcb0f12767ae49d16fe623187ae5313b5bb5cdd80) diff --git a/doc/schemas/setchannel.schema.json b/doc/schemas/setchannel.schema.json new file mode 100644 index 000000000..fcd896a51 --- /dev/null +++ b/doc/schemas/setchannel.schema.json @@ -0,0 +1,53 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "additionalProperties": false, + "required": [ + "channels" + ], + "properties": { + "channels": { + "type": "array", + "description": "channel(s) set, and their resulting configuration", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "peer_id", + "channel_id", + "fee_base_msat", + "fee_proportional_millionths", + "maximum_htlc_out_msat" + ], + "properties": { + "peer_id": { + "type": "pubkey", + "description": "The node_id of the peer" + }, + "channel_id": { + "type": "hex", + "description": "The channel_id of the channel", + "minLength": 64, + "maxLength": 64 + }, + "short_channel_id": { + "type": "short_channel_id", + "description": "the short_channel_id (if locked in)" + }, + "fee_base_msat": { + "type": "msat", + "description": "The resulting feebase (this is the BOLT #7 name)" + }, + "fee_proportional_millionths": { + "type": "u32", + "description": "The resulting feeppm (this is the BOLT #7 name)" + }, + "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/peer_control.c b/lightningd/peer_control.c index 38809d433..675b00b77 100644 --- a/lightningd/peer_control.c +++ b/lightningd/peer_control.c @@ -2007,13 +2007,20 @@ static struct command_result *param_msat_u32(struct command *cmd, return NULL; } -static void set_channel_fees(struct command *cmd, struct channel *channel, - u32 base, u32 ppm, u32 delaysecs, - struct json_stream *response) +static void set_channel_config(struct command *cmd, struct channel *channel, + u32 *base, + u32 *ppm, + struct amount_msat *htlc_max, + u32 delaysecs, + struct json_stream *response, + bool add_details) { - /* We only need to defer values if we *increase* them; we always - * allow users to overpay fees. */ - if (base > channel->feerate_base || ppm > channel->feerate_ppm) { + /* We only need to defer values if we *increase* fees (or drop + * max); we always allow users to overpay fees. */ + if ((base && *base > channel->feerate_base) + || (ppm && *ppm > channel->feerate_ppm) + || (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; @@ -2022,14 +2029,18 @@ static void set_channel_fees(struct command *cmd, struct channel *channel, } /* set new values */ - channel->feerate_base = base; - channel->feerate_ppm = ppm; + if (base) + channel->feerate_base = *base; + if (ppm) + channel->feerate_ppm = *ppm; + if (htlc_max) + channel->htlc_maximum_msat = *htlc_max; /* tell channeld to make a send_channel_update */ if (channel->owner && streq(channel->owner->name, "channeld")) subd_send_msg(channel->owner, - take(towire_channeld_config_channel(NULL, &base, &ppm, - NULL))); + take(towire_channeld_config_channel(NULL, base, ppm, + htlc_max))); /* save values to database */ wallet_channel_save(cmd->ld->wallet, channel); @@ -2041,6 +2052,17 @@ static void set_channel_fees(struct command *cmd, struct channel *channel, type_to_string(tmpctx, struct channel_id, &channel->cid)); if (channel->scid) json_add_short_channel_id(response, "short_channel_id", channel->scid); + + /* setchannel lists these explicitly */ + if (add_details) { + json_add_amount_msat_only(response, "fee_base_msat", + amount_msat(channel->feerate_base)); + json_add_u32(response, "fee_proportional_millionths", + channel->feerate_ppm); + json_add_amount_msat_only(response, + "maximum_htlc_out_msat", + channel->htlc_maximum_msat); + } json_object_end(response); } @@ -2088,14 +2110,14 @@ static struct command_result *json_setchannelfee(struct command *cmd, channel->state != CHANNELD_AWAITING_LOCKIN && channel->state != DUALOPEND_AWAITING_LOCKIN) continue; - set_channel_fees(cmd, channel, *base, *ppm, *delaysecs, - response); + set_channel_config(cmd, channel, base, ppm, NULL, + *delaysecs, response, false); } /* single channel should be updated */ } else { - set_channel_fees(cmd, channel, *base, *ppm, *delaysecs, - response); + set_channel_config(cmd, channel, base, ppm, NULL, + *delaysecs, response, false); } /* Close and return response */ @@ -2117,6 +2139,75 @@ static const struct json_command setchannelfee_command = { }; AUTODATA(json_command, &setchannelfee_command); +static struct command_result *json_setchannel(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) +{ + struct json_stream *response; + struct peer *peer; + struct channel *channel; + u32 *base, *ppm, *delaysecs; + struct amount_msat *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("htlcmax", param_msat, &htlc_max), + p_opt_def("enforcedelay", param_number, &delaysecs, 600), + NULL)) + return command_param_failed(); + + if (channel + && channel->state != CHANNELD_NORMAL + && channel->state != CHANNELD_AWAITING_LOCKIN + && channel->state != DUALOPEND_AWAITING_LOCKIN) + return command_fail(cmd, LIGHTNINGD, + "Channel is in state %s", channel_state_name(channel)); + + /* Open JSON response object for later iteration */ + response = json_stream_success(cmd); + json_array_start(response, "channels"); + + /* If the users requested 'all' channels we need to iterate */ + if (channel == NULL) { + list_for_each(&cmd->ld->peers, peer, list) { + channel = peer_active_channel(peer); + if (!channel) + continue; + if (channel->state != CHANNELD_NORMAL && + channel->state != CHANNELD_AWAITING_LOCKIN && + channel->state != DUALOPEND_AWAITING_LOCKIN) + continue; + set_channel_config(cmd, channel, base, ppm, htlc_max, + *delaysecs, response, true); + } + + /* single channel should be updated */ + } else { + set_channel_config(cmd, channel, base, ppm, htlc_max, + *delaysecs, response, true); + } + + /* Close and return response */ + json_array_end(response); + return command_success(cmd, response); +} + +static const struct json_command setchannel_command = { + "setchannel", + "channels", + json_setchannel, + "Sets fees and/or htlc_max for channel with {id} " + "(either peer ID, channel ID, short channel ID or 'all'). " + "If {feebase}, {feeppm} or {htlcmax} is missing, it is unchanged." + "{base} can also be defined in other units, for example '1sat'. " + "If {id} is 'all', the fees will be applied for all channels. " +}; +AUTODATA(json_command, &setchannel_command); + #if DEVELOPER static struct command_result *json_sign_last_tx(struct command *cmd, const char *buffer,