Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add channel closure details #4126

Merged
merged 11 commits into from
Nov 6, 2020
16 changes: 16 additions & 0 deletions common/json.c
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,22 @@ void json_add_time(struct json_stream *result, const char *fieldname,
json_add_string(result, fieldname, timebuf);
}

void json_add_timeiso(struct json_stream *result,
const char *fieldname,
struct timeabs *time)
{
char iso8601_msec_fmt[sizeof("YYYY-mm-ddTHH:MM:SS.%03dZ")];
char iso8601_s[sizeof("YYYY-mm-ddTHH:MM:SS.nnnZ")];

strftime(iso8601_msec_fmt, sizeof(iso8601_msec_fmt),
"%FT%T.%%03dZ", gmtime(&time->ts.tv_sec));
snprintf(iso8601_s, sizeof(iso8601_s),
iso8601_msec_fmt, (int) time->ts.tv_nsec / 1000000);

json_add_string(result, fieldname, iso8601_s);
}


void json_add_tok(struct json_stream *result, const char *fieldname,
const jsmntok_t *tok, const char *buffer)
{
Expand Down
5 changes: 5 additions & 0 deletions common/json.h
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,11 @@ void json_add_timeabs(struct json_stream *result, const char *fieldname,
void json_add_time(struct json_stream *result, const char *fieldname,
struct timespec ts);

/* Add ISO_8601 timestamp string, i.e. "2019-09-07T15:50+01:00" */
void json_add_timeiso(struct json_stream *result,
const char *fieldname,
struct timeabs *time);

/* Add any json token */
void json_add_tok(struct json_stream *result, const char *fieldname,
const jsmntok_t *tok, const char *buffer);
Expand Down
30 changes: 27 additions & 3 deletions doc/PLUGINS.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,8 +325,8 @@ into a block.
### `channel_state_changed`

A notification for topic `channel_state_changed` is sent every time a channel
changes its state. The notification includes the peer and channel ids as well
as the old and the new channel states.
changes its state. The notification includes the `peer_id` and `channel_id`, the
old and new channel states, the type of `cause` and a `message`.

```json
{
Expand All @@ -335,11 +335,35 @@ as the old and the new channel states.
"channel_id": "a2d0851832f0e30a0cf778a826d72f077ca86b69f72677e0267f23f63a0599b4",
"short_channel_id" : "561820x1020x1",
"old_state": "CHANNELD_NORMAL",
"new_state": "CHANNELD_SHUTTING_DOWN"
"new_state": "CHANNELD_SHUTTING_DOWN",
"cause" : "remote",
"message" : "Peer closes channel"
}
}
```

A `cause` can have the following values:
- "unknown" Anything other than the reasons below. Should not happen.
- "local" Unconscious internal reasons, e.g. dev fail of a channel.
- "user" The operator or a plugin opened or closed a channel by intention.
- "remote" The remote closed or funded a channel with us by intention.
- "protocol" We need to close a channel because of bad signatures and such.
- "onchain" A channel was closed onchain, while we were offline.

Most state changes are caused subsequentially for a prior state change, e.g.
"CLOSINGD_COMPLETE" is followed by "FUNDING_SPEND_SEEN". Because of this, the
`cause` reflects the last known reason in terms of local or remote user
interaction, protocol reasons, etc. More specifically, a `new_state`
"FUNDING_SPEND_SEEN" will likely _not_ have "onchain" as a `cause` but some
value such as "REMOTE" or "LOCAL" depending on who initiated the closing of a
channel.

Note: If the channel is not closed or being closed yet, the `cause` will reflect
which side "remote" or "local" opened the channel.

Note: If the cause is "onchain" this was very likely a conscious decision of the
remote peer, but we have been offline.

### `connect`

A notification for topic `connect` is sent every time a new connection
Expand Down
12 changes: 10 additions & 2 deletions doc/lightning-listpeers.7

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion doc/lightning-listpeers.7.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ The objects in the *channels* array will have at least these fields:
peer, or a theft attempt).
* `"CLOSED"`: The channel closure has been confirmed deeply.
The channel will eventually be removed from this array.
* *state_changes*: An array of objects describing prior state change events.
* *opener*: A string `"local"` or `"remote`" describing which side opened this
channel.
* *closer*: A string `"local"` or `"remote`" describing which side
closed this channel or `null` if the channel is not (being) closed yet.
* *status*: An array of strings containing the most important log messages
relevant to this channel.
Also known as the "billboard".
Expand Down Expand Up @@ -211,7 +216,7 @@ Objects in the *htlcs* array will contain these fields:
* *payment\_hash*: The payment hash, whose preimage must be revealed to
successfully claim this HTLC.
* *state*: A string describing whether the HTLC has been communicated to
or from the peer, whether it has been signed in a new commitment, whether
or from the peer, whether it has been signed in a new commitment, whether
the previous commitment (that does not contain it) has been revoked, as
well as when the HTLC is fulfilled or failed offchain.
* *local\_trimmed*: A boolean, existing and `true` if the HTLC is not
Expand Down
72 changes: 63 additions & 9 deletions lightningd/channel.c
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,9 @@ struct channel *new_channel(struct peer *peer, u64 dbid,
const u8 *remote_upfront_shutdown_script,
bool option_static_remotekey,
bool option_anchor_outputs,
struct wally_psbt *psbt STEALS)
struct wally_psbt *psbt STEALS,
enum side closer,
enum state_change reason)
{
struct channel *channel = tal(peer->ld, struct channel);

Expand Down Expand Up @@ -289,6 +291,9 @@ struct channel *new_channel(struct peer *peer, u64 dbid,
channel->rr_number = peer->ld->rr_counter++;
tal_add_destructor(channel, destroy_channel);

channel->closer = closer;
channel->state_change_cause = reason;

/* Make sure we see any spends using this key */
txfilter_add_scriptpubkey(peer->ld->owned_txfilter,
take(p2wpkh_for_keyidx(NULL, peer->ld,
Expand Down Expand Up @@ -420,9 +425,25 @@ void channel_set_last_tx(struct channel *channel,

void channel_set_state(struct channel *channel,
enum channel_state old_state,
enum channel_state state)
enum channel_state state,
enum state_change reason,
char *why)
{
struct channel_id cid;
struct timeabs timestamp;

/* set closer, if known */
if (state > CHANNELD_NORMAL && channel->closer == NUM_SIDES) {
if (reason == REASON_LOCAL) channel->closer = LOCAL;
if (reason == REASON_USER) channel->closer = LOCAL;
if (reason == REASON_REMOTE) channel->closer = REMOTE;
}

/* use or update state_change_cause, if known */
if (reason != REASON_UNKNOWN)
channel->state_change_cause = reason;
else
reason = channel->state_change_cause;

log_info(channel->log, "State changed from %s to %s",
channel_state_name(channel), channel_state_str(state));
Expand All @@ -435,19 +456,46 @@ void channel_set_state(struct channel *channel,
/* TODO(cdecker) Selectively save updated fields to DB */
wallet_channel_save(channel->peer->ld->wallet, channel);

/* plugin notification channel_state_changed */
/* plugin notification channel_state_changed and DB entry */
if (state != old_state) { /* see issue #4029 */
timestamp = time_now();
wallet_state_change_add(channel->peer->ld->wallet,
channel->dbid,
&timestamp,
old_state,
state,
reason,
why);
derive_channel_id(&cid, &channel->funding_txid, channel->funding_outnum);
notify_channel_state_changed(channel->peer->ld,
&channel->peer->id,
&cid,
channel->scid,
&timestamp,
old_state,
state);
state,
reason,
why);
}
}

const char *channel_change_state_reason_str(enum state_change reason)
{
switch (reason) {
case REASON_UNKNOWN: return "unknown";
case REASON_LOCAL: return "local";
case REASON_USER: return "user";
case REASON_REMOTE: return "remote";
case REASON_PROTOCOL: return "protocol";
case REASON_ONCHAIN: return "onchain";
}
abort();
}

void channel_fail_permanent(struct channel *channel, const char *fmt, ...)
void channel_fail_permanent(struct channel *channel,
enum state_change reason,
const char *fmt,
...)
{
struct lightningd *ld = channel->peer->ld;
va_list ap;
Expand All @@ -470,7 +518,11 @@ void channel_fail_permanent(struct channel *channel, const char *fmt, ...)
drop_to_chain(ld, channel, false);

if (channel_active(channel))
channel_set_state(channel, channel->state, AWAITING_UNILATERAL);
channel_set_state(channel,
channel->state,
AWAITING_UNILATERAL,
reason,
why);

tal_free(why);
}
Expand Down Expand Up @@ -512,9 +564,9 @@ void channel_internal_error(struct channel *channel, const char *fmt, ...)

/* Don't expose internal error causes to remove unless doing dev */
#if DEVELOPER
channel_fail_permanent(channel, "Internal error: %s", why);
channel_fail_permanent(channel, REASON_LOCAL, "Internal error: %s", why);
#else
channel_fail_permanent(channel, "Internal error");
channel_fail_permanent(channel, REASON_LOCAL, "Internal error");
#endif
tal_free(why);
}
Expand Down Expand Up @@ -545,7 +597,9 @@ static void err_and_reconnect(struct channel *channel,

#if DEVELOPER
if (dev_disconnect_permanent(channel->peer->ld)) {
channel_fail_permanent(channel, "dev_disconnect permfail");
channel_fail_permanent(channel,
REASON_LOCAL,
"dev_disconnect permfail");
return;
}
#endif
Expand Down
21 changes: 18 additions & 3 deletions lightningd/channel.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,12 @@ struct channel {

/* PSBT, for v2 channels. Saved until it's sent */
struct wally_psbt *psbt;

/* the one that initiated a bilateral close, NUM_SIDES if unknown. */
enum side closer;

/* Last known state_change cause */
enum state_change state_change_cause;
};

struct channel *new_channel(struct peer *peer, u64 dbid,
Expand Down Expand Up @@ -205,7 +211,9 @@ struct channel *new_channel(struct peer *peer, u64 dbid,
const u8 *remote_upfront_shutdown_script STEALS,
bool option_static_remotekey,
bool option_anchor_outputs,
struct wally_psbt *psbt STEALS);
struct wally_psbt *psbt STEALS,
enum side closer,
enum state_change reason);

void delete_channel(struct channel *channel STEALS);

Expand All @@ -222,7 +230,10 @@ void channel_fail_reconnect_later(struct channel *channel,
const char *fmt,...) PRINTF_FMT(2,3);

/* Channel has failed, give up on it. */
void channel_fail_permanent(struct channel *channel, const char *fmt, ...);
void channel_fail_permanent(struct channel *channel,
enum state_change reason,
const char *fmt,
...);
/* Forget the channel. This is only used for the case when we "receive" error
* during CHANNELD_AWAITING_LOCKIN if we are "fundee". */
void channel_fail_forget(struct channel *channel, const char *fmt, ...);
Expand All @@ -231,7 +242,11 @@ void channel_internal_error(struct channel *channel, const char *fmt, ...);

void channel_set_state(struct channel *channel,
enum channel_state old_state,
enum channel_state state);
enum channel_state state,
enum state_change reason,
char *why);

const char *channel_change_state_reason_str(enum state_change reason);

/* Find a channel which is not onchain, if any */
struct channel *peer_active_channel(struct peer *peer);
Expand Down
26 changes: 21 additions & 5 deletions lightningd/channel_control.c
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,11 @@ static void lockin_complete(struct channel *channel)
return;
}

channel_set_state(channel, CHANNELD_AWAITING_LOCKIN, CHANNELD_NORMAL);
channel_set_state(channel,
CHANNELD_AWAITING_LOCKIN,
CHANNELD_NORMAL,
REASON_UNKNOWN,
"Lockin complete");

/* Fees might have changed (and we use IMMEDIATE once we're funded),
* so update now. */
Expand Down Expand Up @@ -224,15 +228,20 @@ static void peer_got_shutdown(struct channel *channel, const u8 *msg)
*/
if (!is_p2pkh(scriptpubkey, NULL) && !is_p2sh(scriptpubkey, NULL)
&& !is_p2wpkh(scriptpubkey, NULL) && !is_p2wsh(scriptpubkey, NULL)) {
channel_fail_permanent(channel, "Bad shutdown scriptpubkey %s",
channel_fail_permanent(channel,
REASON_PROTOCOL,
"Bad shutdown scriptpubkey %s",
tal_hex(channel, scriptpubkey));
return;
}

/* If we weren't already shutting down, we are now */
if (channel->state != CHANNELD_SHUTTING_DOWN)
channel_set_state(channel,
channel->state, CHANNELD_SHUTTING_DOWN);
channel->state,
CHANNELD_SHUTTING_DOWN,
REASON_REMOTE,
"Peer closes channel");

/* TODO(cdecker) Selectively save updated fields to DB */
wallet_channel_save(ld->wallet, channel);
Expand Down Expand Up @@ -265,7 +274,9 @@ static void channel_fail_fallen_behind(struct channel *channel, const u8 *msg)
}

/* Peer sees this, so send a generic msg about unilateral close. */
channel_fail_permanent(channel, "Awaiting unilateral close");
channel_fail_permanent(channel,
REASON_LOCAL,
"Awaiting unilateral close");
}

static void peer_start_closingd_after_shutdown(struct channel *channel,
Expand All @@ -283,7 +294,11 @@ static void peer_start_closingd_after_shutdown(struct channel *channel,

/* This sets channel->owner, closes down channeld. */
peer_start_closingd(channel, pps, false, NULL);
channel_set_state(channel, CHANNELD_SHUTTING_DOWN, CLOSINGD_SIGEXCHANGE);
channel_set_state(channel,
CHANNELD_SHUTTING_DOWN,
CLOSINGD_SIGEXCHANGE,
REASON_UNKNOWN,
"Start closingd");
}

static void forget(struct channel *channel)
Expand Down Expand Up @@ -638,6 +653,7 @@ void peer_start_channeld(struct channel *channel,
num_revocations-1,
&last_remote_per_commit_secret)) {
channel_fail_permanent(channel,
REASON_LOCAL,
"Could not get revocation secret %"PRIu64,
num_revocations-1);
return;
Expand Down
Loading