feat: adds state change cause and message

This adds a `state_change` 'cause' to a channel.
A 'cause' is some initial 'reason' a channel was created or closed by:

  /* Anything other than the reasons below. Should not happen. */
  REASON_UNKNOWN,
  /* Unconscious internal reasons, e.g. dev fail of a channel. */
  REASON_LOCAL,
  /* The operator or a plugin opened or closed a channel by intention. */
  REASON_USER,
  /* The remote closed or funded a channel with us by intention. */
  REASON_REMOTE,
  /* E.g. We need to close a channel because of bad signatures and such. */
  REASON_PROTOCOL,
  /* A channel was closed onchain, while we were offline. */
  /* Note: This is very likely a conscious remote decision. */
  REASON_ONCHAIN

If a 'cause' is known and a subsequent state change is made with
`REASON_UNKNOWN` the preceding cause will be used as reason, since a lot
(all `REASON_UNKNOWN`) state changes are a subsequent consequences of a prior
cause: local, user, remote, protocol or onchain.

Changelog-Added: Plugins: Channel closure resaon/cause to channel_state_changed notification
This commit is contained in:
Michael Schmoock 2020-10-28 11:46:12 +01:00 committed by neil saitug
parent d5d9858b7b
commit 8a8dabaa58
15 changed files with 199 additions and 51 deletions

View File

@ -289,6 +289,11 @@ struct channel *new_channel(struct peer *peer, u64 dbid,
channel->rr_number = peer->ld->rr_counter++;
tal_add_destructor(channel, destroy_channel);
channel->closer = NUM_SIDES;
channel->state_change_cause = REASON_USER;
if (opener == REMOTE)
channel->state_change_cause = REASON_REMOTE;
/* Make sure we see any spends using this key */
txfilter_add_scriptpubkey(peer->ld->owned_txfilter,
take(p2wpkh_for_keyidx(NULL, peer->ld,
@ -420,10 +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;
/* 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));
if (channel->state != old_state)
@ -443,11 +463,29 @@ void channel_set_state(struct channel *channel,
&cid,
channel->scid,
old_state,
state);
state,
reason,
why);
}
}
void channel_fail_permanent(struct channel *channel, const char *fmt, ...)
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,
enum state_change reason,
const char *fmt,
...)
{
struct lightningd *ld = channel->peer->ld;
va_list ap;
@ -470,7 +508,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);
}
@ -512,9 +554,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);
}
@ -545,7 +587,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

View File

@ -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,
@ -222,7 +228,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, ...);
@ -231,7 +240,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);

View File

@ -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. */
@ -224,7 +228,9 @@ 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;
}
@ -232,7 +238,10 @@ static void peer_got_shutdown(struct channel *channel, const u8 *msg)
/* 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);
@ -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,
@ -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)
@ -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;

View File

@ -33,4 +33,25 @@ enum channel_state {
};
#define CHANNEL_STATE_MAX CLOSED
enum state_change {
/* Anything other than the reasons below. Should not happen. */
REASON_UNKNOWN,
/* Unconscious internal reasons, e.g. dev fail of a channel. */
REASON_LOCAL,
/* The operator or a plugin opened or closed a channel by intention. */
REASON_USER,
/* The remote closed or funded a channel with us by intention. */
REASON_REMOTE,
/* E.g. We need to close a channel because of bad signatures and such. */
REASON_PROTOCOL,
/* A channel was closed onchain, while we were offline. */
/* Note: This is very likely a conscious remote decision. */
REASON_ONCHAIN
};
#endif /* LIGHTNING_LIGHTNINGD_CHANNEL_STATE_H */

View File

@ -137,7 +137,11 @@ static void peer_closing_complete(struct channel *channel, const u8 *msg)
/* Channel gets dropped to chain cooperatively. */
drop_to_chain(channel->peer->ld, channel, true);
channel_set_state(channel, CLOSINGD_SIGEXCHANGE, CLOSINGD_COMPLETE);
channel_set_state(channel,
CLOSINGD_SIGEXCHANGE,
CLOSINGD_COMPLETE,
REASON_UNKNOWN,
"Closing complete");
}
static unsigned closing_msg(struct subd *sd, const u8 *msg, const int *fds UNUSED)
@ -256,7 +260,9 @@ void peer_start_closingd(struct channel *channel,
&channel->funding),
type_to_string(tmpctx, struct amount_msat,
&channel->our_msat));
channel_fail_permanent(channel, "our_msat overflow on closing");
channel_fail_permanent(channel,
REASON_LOCAL,
"our_msat overflow on closing");
return;
}
@ -274,6 +280,7 @@ void peer_start_closingd(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;

View File

@ -215,7 +215,9 @@ static void channel_state_changed_notification_serialize(struct json_stream *str
struct channel_id *cid,
struct short_channel_id *scid,
enum channel_state old_state,
enum channel_state new_state)
enum channel_state new_state,
enum state_change cause,
char *message)
{
json_object_start(stream, "channel_state_changed");
json_add_node_id(stream, "peer_id", peer_id);
@ -226,6 +228,11 @@ static void channel_state_changed_notification_serialize(struct json_stream *str
json_add_null(stream, "short_channel_id");
json_add_string(stream, "old_state", channel_state_str(old_state));
json_add_string(stream, "new_state", channel_state_str(new_state));
json_add_string(stream, "cause", channel_change_state_reason_str(cause));
if (message != NULL)
json_add_string(stream, "message", message);
else
json_add_null(stream, "message");
json_object_end(stream);
}
@ -238,18 +245,22 @@ void notify_channel_state_changed(struct lightningd *ld,
struct channel_id *cid,
struct short_channel_id *scid,
enum channel_state old_state,
enum channel_state new_state)
enum channel_state new_state,
enum state_change cause,
char *message)
{
void (*serialize)(struct json_stream *,
struct node_id *,
struct channel_id *,
struct short_channel_id *,
enum channel_state,
enum channel_state) = channel_state_changed_notification_gen.serialize;
enum channel_state,
enum state_change,
char *message) = channel_state_changed_notification_gen.serialize;
struct jsonrpc_notification *n
= jsonrpc_notification_start(NULL, channel_state_changed_notification_gen.topic);
serialize(n->stream, peer_id, cid, scid, old_state, new_state);
serialize(n->stream, peer_id, cid, scid, old_state, new_state, cause, message);
jsonrpc_notification_end(n);
plugins_notify(ld->plugins, take(n));
}

View File

@ -64,7 +64,9 @@ void notify_channel_state_changed(struct lightningd *ld,
struct channel_id *cid,
struct short_channel_id *scid,
enum channel_state old_state,
enum channel_state new_state);
enum channel_state new_state,
enum state_change cause,
char *message);
void notify_forward_event(struct lightningd *ld,
const struct htlc_in *in,

View File

@ -72,7 +72,11 @@ static void onchaind_tell_fulfill(struct channel *channel)
static void handle_onchain_init_reply(struct channel *channel, const u8 *msg UNUSED)
{
/* FIXME: We may already be ONCHAIN state when we implement restart! */
channel_set_state(channel, FUNDING_SPEND_SEEN, ONCHAIN);
channel_set_state(channel,
FUNDING_SPEND_SEEN,
ONCHAIN,
REASON_UNKNOWN,
"Onchain init reply");
}
/**
@ -560,11 +564,22 @@ enum watch_result onchaind_funding_spent(struct channel *channel,
struct pubkey final_key;
int hsmfd;
u32 feerates[3];
enum state_change reason;
channel_fail_permanent(channel, "Funding transaction spent");
/* use REASON_ONCHAIN or closer's reason, if known */
reason = REASON_ONCHAIN;
if (channel->closer != NUM_SIDES)
reason = REASON_UNKNOWN; /* will use last cause as reason */
channel_fail_permanent(channel, reason, "Funding transaction spent");
/* We could come from almost any state. */
channel_set_state(channel, channel->state, FUNDING_SPEND_SEEN);
/* NOTE(mschmoock) above comment is wrong, since we failed above! */
channel_set_state(channel,
channel->state,
FUNDING_SPEND_SEEN,
reason,
"Onchain funding spend");
hsmfd = hsm_get_client_fd(ld, &channel->peer->id,
channel->dbid,

View File

@ -287,8 +287,8 @@ close_command_timeout(struct close_command *cc)
if (!deprecated_apis)
json_notify_fmt(cc->cmd, LOG_INFORM,
"Timed out, forcing close.");
channel_fail_permanent(cc->channel,
"Forcibly closed by 'close' command timeout");
channel_fail_permanent(cc->channel, REASON_USER,
"Forcibly closed by `close` command timeout");
}
/* Construct a close command structure and add to ld. */
@ -491,7 +491,9 @@ void channel_errmsg(struct channel *channel,
channel->owner->name,
err_for_them ? "sent" : "received", desc);
else
channel_fail_permanent(channel, "%s: %s ERROR %s",
channel_fail_permanent(channel,
err_for_them ? REASON_LOCAL : REASON_PROTOCOL,
"%s: %s ERROR %s",
channel->owner->name,
err_for_them ? "sent" : "received", desc);
}
@ -1022,6 +1024,7 @@ peer_connected_hook_cb(struct peer_connected_hook_payload *payload STEALS,
#if DEVELOPER
if (dev_disconnect_permanent(ld)) {
channel_fail_permanent(channel,
REASON_LOCAL,
"dev_disconnect permfail");
error = channel->error;
goto send_error;
@ -1199,7 +1202,9 @@ static enum watch_result funding_depth_cb(struct lightningd *ld,
if (!mk_short_channel_id(&scid,
loc->blkheight, loc->index,
channel->funding_outnum)) {
channel_fail_permanent(channel, "Invalid funding scid %u:%u:%u",
channel_fail_permanent(channel,
REASON_LOCAL,
"Invalid funding scid %u:%u:%u",
loc->blkheight, loc->index,
channel->funding_outnum);
return DELETE_WATCH;
@ -1518,7 +1523,9 @@ static struct command_result *json_close(struct command *cmd,
case CHANNELD_NORMAL:
case CHANNELD_AWAITING_LOCKIN:
channel_set_state(channel,
channel->state, CHANNELD_SHUTTING_DOWN);
channel->state, CHANNELD_SHUTTING_DOWN,
REASON_USER,
"User or plugin invoked close command");
/* fallthrough */
case CHANNELD_SHUTTING_DOWN:
if (channel->owner)
@ -2104,7 +2111,9 @@ static struct command_result *json_dev_fail(struct command *cmd,
"Could not find active channel with peer");
}
channel_fail_permanent(channel, "Failing due to dev-fail command");
channel_fail_permanent(channel,
REASON_USER,
"Failing due to dev-fail command");
return command_success(cmd, json_stream_success(cmd));
}

View File

@ -2099,10 +2099,11 @@ void peer_got_revoke(struct channel *channel, const u8 *msg)
shachain_index(revokenum),
&per_commitment_secret)) {
channel_fail_permanent(channel,
"Bad per_commitment_secret %s for %"PRIu64,
type_to_string(msg, struct secret,
&per_commitment_secret),
revokenum);
REASON_PROTOCOL,
"Bad per_commitment_secret %s for %"PRIu64,
type_to_string(msg, struct secret,
&per_commitment_secret),
revokenum);
return;
}
@ -2320,11 +2321,12 @@ void htlcs_notify_new_block(struct lightningd *ld, u32 height)
continue;
channel_fail_permanent(hout->key.channel,
"Offered HTLC %"PRIu64
" %s cltv %u hit deadline",
hout->key.id,
htlc_state_name(hout->hstate),
hout->cltv_expiry);
REASON_PROTOCOL,
"Offered HTLC %"PRIu64
" %s cltv %u hit deadline",
hout->key.id,
htlc_state_name(hout->hstate),
hout->cltv_expiry);
removed = true;
}
/* Iteration while removing is safe, but can skip entries! */
@ -2368,11 +2370,12 @@ void htlcs_notify_new_block(struct lightningd *ld, u32 height)
continue;
channel_fail_permanent(channel,
"Fulfilled HTLC %"PRIu64
" %s cltv %u hit deadline",
hin->key.id,
htlc_state_name(hin->hstate),
hin->cltv_expiry);
REASON_PROTOCOL,
"Fulfilled HTLC %"PRIu64
" %s cltv %u hit deadline",
hin->key.id,
htlc_state_name(hin->hstate),
hin->cltv_expiry);
removed = true;
}
/* Iteration while removing is safe, but can skip entries! */

View File

@ -46,7 +46,10 @@ void broadcast_tx(struct chain_topology *topo UNNEEDED,
void channel_fail_forget(struct channel *channel UNNEEDED, const char *fmt UNNEEDED, ...)
{ fprintf(stderr, "channel_fail_forget called!\n"); abort(); }
/* Generated stub for channel_fail_permanent */
void channel_fail_permanent(struct channel *channel UNNEEDED, const char *fmt UNNEEDED, ...)
void channel_fail_permanent(struct channel *channel UNNEEDED,
enum state_change reason UNNEEDED,
const char *fmt UNNEEDED,
...)
{ fprintf(stderr, "channel_fail_permanent called!\n"); abort(); }
/* Generated stub for channel_fail_reconnect */
void channel_fail_reconnect(struct channel *channel UNNEEDED,
@ -72,7 +75,9 @@ void channel_set_billboard(struct channel *channel UNNEEDED, bool perm UNNEEDED,
/* Generated stub for channel_set_state */
void channel_set_state(struct channel *channel UNNEEDED,
enum channel_state old_state UNNEEDED,
enum channel_state state UNNEEDED)
enum channel_state state UNNEEDED,
enum state_change reason UNNEEDED,
char *why UNNEEDED)
{ fprintf(stderr, "channel_set_state called!\n"); abort(); }
/* Generated stub for channel_state_name */
const char *channel_state_name(const struct channel *channel UNNEEDED)

View File

@ -1660,4 +1660,4 @@ struct db_query db_postgres_queries[] = {
#endif /* LIGHTNINGD_WALLET_GEN_DB_POSTGRES */
// SHA256STAMP:8a260050ced7606fcad6e15df51a42a442f3119ad82d8e86fa2d348a2a45ee1a
// SHA256STAMP:bb84a713ee593a4ac2441ae971851f3b466e8c2bd6cf3cbf6261f3d4d0fb803d

View File

@ -1660,4 +1660,4 @@ struct db_query db_sqlite3_queries[] = {
#endif /* LIGHTNINGD_WALLET_GEN_DB_SQLITE3 */
// SHA256STAMP:8a260050ced7606fcad6e15df51a42a442f3119ad82d8e86fa2d348a2a45ee1a
// SHA256STAMP:bb84a713ee593a4ac2441ae971851f3b466e8c2bd6cf3cbf6261f3d4d0fb803d

View File

@ -1090,7 +1090,7 @@ msgstr ""
msgid "not a valid SQL statement"
msgstr ""
#: wallet/test/run-wallet.c:1365
#: wallet/test/run-wallet.c:1367
msgid "INSERT INTO channels (id) VALUES (1);"
msgstr ""
# SHA256STAMP:9d0158f039940ef277ed6fc3a27b3695dec7f9869ea11e0f9eea8313a5849f08
# SHA256STAMP:1ad776f09062dbee2206b358b64812c1a5bf4a4e2a1de08c1c5dc659481df667

View File

@ -440,7 +440,9 @@ void notify_channel_state_changed(struct lightningd *ld UNNEEDED,
struct channel_id *cid UNNEEDED,
struct short_channel_id *scid UNNEEDED,
enum channel_state old_state UNNEEDED,
enum channel_state new_state UNNEEDED)
enum channel_state new_state UNNEEDED,
enum state_change cause UNNEEDED,
char *message UNNEEDED)
{ fprintf(stderr, "notify_channel_state_changed called!\n"); abort(); }
/* Generated stub for notify_connect */
void notify_connect(struct lightningd *ld UNNEEDED, struct node_id *nodeid UNNEEDED,