Skip to content
Open
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
60 changes: 60 additions & 0 deletions contrib/lightning-graceful-stop.sh
Original file line number Diff line number Diff line change
@@ -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}" <<EOF

Graceful stop timed out after ${TIMEOUT} seconds.
An outstanding HTLC will next expire at block height ${next_expiry} in about $((next_expiry - headercount))0 minutes.
The node is still trying to stop gracefully.
To cancel, run \`lightning-cli setconfig snub-idle-channels false true\`.
EOF
exit 1
1 change: 1 addition & 0 deletions lightningd/lightningd.c
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ static struct lightningd *new_lightningd(const tal_t *ctx)
ld->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;
Expand Down
4 changes: 4 additions & 0 deletions lightningd/lightningd.h
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,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 */
Expand Down
15 changes: 15 additions & 0 deletions lightningd/options.c
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,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") ||
Expand Down Expand Up @@ -1647,6 +1658,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,
Expand Down
34 changes: 32 additions & 2 deletions lightningd/peer_control.c
Original file line number Diff line number Diff line change
Expand Up @@ -1307,6 +1307,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)
{
Expand Down Expand Up @@ -1458,6 +1465,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));

Expand Down Expand Up @@ -1913,6 +1926,22 @@ void 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);
Expand Down Expand Up @@ -1947,7 +1976,7 @@ void 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);
Expand Down Expand Up @@ -2585,7 +2614,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;
Expand Down
Loading