diff --git a/contrib/lightning-graceful-stop.sh b/contrib/lightning-graceful-stop.sh new file mode 100755 index 000000000000..9a4b69db7b84 --- /dev/null +++ b/contrib/lightning-graceful-stop.sh @@ -0,0 +1,60 @@ +#!/bin/bash +set -e + +: "${TIMEOUT:=${1:-60}}" +let DEADLINE=EPOCHSECONDS+TIMEOUT + +lightning-cli() { + echo lightning-cli "${@@Q}" >&2 + command lightning-cli "${@}" >&2 +} + +lightning-cli setconfig snub-idle-channels true true +while (( EPOCHSECONDS < DEADLINE )) ; do + echo "Attempting graceful stop ($((DEADLINE - EPOCHSECONDS))s remaining) ..." + while read -r rpc ; do + if [[ "${rpc}" == '# '* ]] ; then + set ${rpc} + echo "# ${2} reestablished channels, ${3} outstanding HTLCs" >&2 + next_expiry=${4} + else + eval "lightning-cli ${rpc}" + if [[ "${rpc}" == stop ]] ; then + echo 'Graceful stop succeeded.' + exit 0 + fi + fi + done < <(command lightning-cli listpeerchannels | jq -r ' + reduce (.channels[] | select(.state | IN("CHANNELD_NORMAL", "CHANNELD_AWAITING_SPLICE"))) + as { $peer_id, $peer_connected, $reestablished, $state, $htlcs } + ( + {}; + .[$peer_id] |= ( + .connected |= . or $peer_connected | + .reestablished += if $reestablished then 1 else 0 end | + .htlcs += ($htlcs | length) | + .next_expiry |= ([. // empty, $htlcs[].expiry] | min) + ) + ) | + ( + "# \(map(.reestablished) | add) \(map(.htlcs) | add) \(map(.next_expiry // empty) | min)", + if all(.reestablished == 0) and all(.htlcs == 0) then + "stop" + else + to_entries[] | + select(.value | .connected and .reestablished > 0 and .htlcs == 0) | + @sh "disconnect \(.key) true" + end + ) + ') + sleep 1 +done +let headercount=$(command lightning-cli getchaininfo | jq '.headercount') +fmt --width="${COLUMNS:-80}" <discovered_ip_v6 = NULL; ld->listen = true; ld->autolisten = true; + ld->snub_idle_channels = false; ld->reconnect = true; ld->reconnect_private = true; ld->try_reexec = false; diff --git a/lightningd/lightningd.h b/lightningd/lightningd.h index 3b4e0e84d904..8c7e350be85c 100644 --- a/lightningd/lightningd.h +++ b/lightningd/lightningd.h @@ -179,6 +179,10 @@ struct lightningd { /* Do we want to guess addresses to listen and announce? */ bool autolisten; + /* Do we want to avoid reestablishing channels with zero outstanding HTLCs? + * This is useful for gracefully stopping the node. */ + bool snub_idle_channels; + /* Setup: Addresses to bind/announce to the network (tal_count()) */ struct wireaddr_internal *proposed_wireaddr; /* Setup: And the bitset for each, whether to listen, announce or both */ diff --git a/lightningd/options.c b/lightningd/options.c index b724b200a089..fbc85e5ad90e 100644 --- a/lightningd/options.c +++ b/lightningd/options.c @@ -109,6 +109,17 @@ static char *opt_set_s32(const char *arg, s32 *u) return NULL; } +static char *opt_set_bool_dynamic(const char *arg, bool *b) +{ + bool ignored; + + /* In case we're called for arg checking only */ + if (!b) + b = &ignored; + + return opt_set_bool_arg(arg, b); +} + char *opt_set_autobool_arg(const char *arg, enum opt_autobool *b) { if (!strcasecmp(arg, "yes") || @@ -1585,6 +1596,10 @@ static void register_opts(struct lightningd *ld) "Sets the public TCP port to use for announcing discovered IPs."); opt_register_noarg("--offline", opt_set_offline, ld, "Start in offline-mode (do not automatically reconnect and do not accept incoming connections)"); + clnopt_witharg("--snub-idle-channels", OPT_SHOWBOOL|OPT_DYNAMIC, + opt_set_bool_dynamic, opt_show_bool, + &ld->snub_idle_channels, + "If true, do not reestablish channels with zero outstanding HTLCs"); clnopt_witharg("--autolisten", OPT_SHOWBOOL, opt_set_bool_arg, opt_show_bool, &ld->autolisten, diff --git a/lightningd/peer_control.c b/lightningd/peer_control.c index ed00651814bc..57b563c0a709 100644 --- a/lightningd/peer_control.c +++ b/lightningd/peer_control.c @@ -1382,6 +1382,13 @@ peer_connected_serialize(struct peer_connected_hook_payload *payload, json_object_end(stream); /* .peer */ } +static bool should_snub_channel(const struct lightningd *ld, + /*const*/ struct channel *channel) +{ + return ld->snub_idle_channels && !channel_state_closed(channel->state) && + !channel_has_htlc_out(channel) && !channel_has_htlc_in(channel); +} + /* Talk to connectd about an active channel */ static void connect_activate_subd(struct lightningd *ld, struct channel *channel) { @@ -1558,6 +1565,12 @@ static void peer_connected_hook_final(struct peer_connected_hook_payload *payloa list_for_each(&peer->channels, channel, list) { /* FIXME: It can race by opening a channel before this! */ if (channel_state_wants_peercomms(channel->state) && !channel->owner) { + if (should_snub_channel(ld, channel)) { + log_debug(channel->log, + "Peer has reconnected, but channel is snubbed; " + "not connecting subd"); + continue; + } log_debug(channel->log, "Peer has reconnected, state %s: connecting subd", channel_state_name(channel)); @@ -2064,6 +2077,22 @@ void handle_peer_spoke(struct lightningd *ld, const u8 *msg) return; } + if (msgtype == WIRE_CHANNEL_REESTABLISH && + should_snub_channel(ld, channel)) { + log_debug(channel->log, + "Peer sent channel_reestablish, but channel is snubbed; " + "sending warning and ignoring"); + error = towire_warningfmt(tmpctx, &channel_id, + "Declining to reestablish idle channel " + "because this node will be halting soon."); + /* Don't goto send_error; we don't want to disconnect. */ + subd_send_msg(ld->connectd, + take(towire_connectd_peer_send_msg(NULL, &peer->id, + peer->connectd_counter, + error))); + return; + } + log_debug(channel->log, "channel already active"); if (channel->state == DUALOPEND_AWAITING_LOCKIN) { pfd = sockpair(tmpctx, channel, &other_fd, &error); @@ -2098,7 +2127,7 @@ void handle_peer_spoke(struct lightningd *ld, const u8 *msg) } if (peer->uncommitted_channel) { error = towire_errorfmt(tmpctx, &channel_id, - "Multiple simulteneous opens not supported"); + "Multiple simultaneous opens not supported"); goto send_error; } peer->uncommitted_channel = new_uncommitted_channel(peer); @@ -2868,7 +2897,8 @@ static void setup_peer(struct peer *peer) && !(channel->channel_flags & CHANNEL_FLAGS_ANNOUNCE_CHANNEL)) continue; - if (channel_state_wants_peercomms(channel->state)) + if (channel_state_wants_peercomms(channel->state) && + !should_snub_channel(ld, channel)) connect = true; if (channel_important_filter(channel, NULL)) important = true;