diff --git a/doc/lightning-close.7 b/doc/lightning-close.7 index a73594afe0a0..ce0d489b29b5 100644 --- a/doc/lightning-close.7 +++ b/doc/lightning-close.7 @@ -2,12 +2,12 @@ .\" Title: lightning-close .\" Author: [see the "AUTHOR" section] .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 04/15/2018 +.\" Date: 04/30/2018 .\" Manual: \ \& .\" Source: \ \& .\" Language: English .\" -.TH "LIGHTNING\-CLOSE" "7" "04/15/2018" "\ \&" "\ \&" +.TH "LIGHTNING\-CLOSE" "7" "04/30/2018" "\ \&" "\ \&" .\" ----------------------------------------------------------------- .\" * Define some portability stuff .\" ----------------------------------------------------------------- @@ -34,7 +34,7 @@ lightning-close \- Command for closing channels with direct peers \fBclose\fR \fIid\fR [\fIforce\fR] [\fItimeout\fR] .SH "DESCRIPTION" .sp -The \fBclose\fR RPC command attempts to close the channel cooperatively with the peer\&. It applies to the active channel of the direct peer corresponding to the given peer \fIid\fR\&. +The \fBclose\fR RPC command attempts to close the channel cooperatively with the peer\&. If the given \fIid\fR is a peer ID (66 hex digits as a string), then it applies to the active channel of the direct peer corresponding to the given peer ID\&. If the given \fIid\fR is a channel ID (64 hex digits as a string, or the short channel ID \fIblockheight:txindex:outindex\fR form), then it applies to that channel\&. .sp The \fBclose\fR command will time out and return with an error when the number of seconds specified in \fItimeout\fR is reached\&. If unspecified, it times out in 30 seconds\&. .sp diff --git a/doc/lightning-close.7.txt b/doc/lightning-close.7.txt index 7c6dd4d145be..cb84447c8b9a 100644 --- a/doc/lightning-close.7.txt +++ b/doc/lightning-close.7.txt @@ -15,8 +15,12 @@ DESCRIPTION The *close* RPC command attempts to close the channel cooperatively with the peer. -It applies to the active channel of the direct peer corresponding to -the given peer 'id'. +If the given 'id' is a peer ID (66 hex digits as a string), then +it applies to the active channel of the direct peer corresponding to +the given peer ID. +If the given 'id' is a channel ID (64 hex digits as a string, or +the short channel ID 'blockheight:txindex:outindex' form), then it +applies to that channel. The *close* command will time out and return with an error when the number of seconds specified in 'timeout' is reached. diff --git a/lightningd/json.c b/lightningd/json.c index 540507e23e50..05ab73bb3fc3 100644 --- a/lightningd/json.c +++ b/lightningd/json.c @@ -8,6 +8,7 @@ #include #include #include +#include /* Output a route hop */ static void @@ -110,6 +111,14 @@ bool json_tok_short_channel_id(const char *buffer, const jsmntok_t *tok, scid); } +bool +json_tok_channel_id(const char *buffer, const jsmntok_t *tok, + struct channel_id *cid) +{ + return hex_decode(buffer + tok->start, tok->end - tok->start, + cid, sizeof(*cid)); +} + void json_add_address(struct json_result *response, const char *fieldname, const struct wireaddr *addr) { diff --git a/lightningd/json.h b/lightningd/json.h index 4ad44ff0d7b8..2e4230d3b4bb 100644 --- a/lightningd/json.h +++ b/lightningd/json.h @@ -12,6 +12,7 @@ # include struct bitcoin_txid; +struct channel_id; struct json_result; struct pubkey; struct route_hop; @@ -50,6 +51,9 @@ void json_add_short_channel_id(struct json_result *response, const char *fieldname, const struct short_channel_id *id); +bool json_tok_channel_id(const char *buffer, const jsmntok_t *tok, + struct channel_id *cid); + /* JSON serialize a network address for a node */ void json_add_address(struct json_result *response, const char *fieldname, const struct wireaddr *addr); diff --git a/lightningd/peer_control.c b/lightningd/peer_control.c index c068b044b080..3730409dc218 100644 --- a/lightningd/peer_control.c +++ b/lightningd/peer_control.c @@ -985,10 +985,60 @@ static const struct json_command listpeers_command = { }; AUTODATA(json_command, &listpeers_command); +static struct channel * +command_find_channel(struct command *cmd, + const char *buffer, const jsmntok_t *tok) +{ + struct lightningd *ld = cmd->ld; + struct channel_id cid; + struct channel_id channel_cid; + struct short_channel_id scid; + struct peer *peer; + struct channel *channel; + + if (json_tok_channel_id(buffer, tok, &cid)) { + list_for_each(&ld->peers, peer, list) { + channel = peer_active_channel(peer); + if (!channel) + continue; + derive_channel_id(&channel_cid, + &channel->funding_txid, + channel->funding_outnum); + if (structeq(&channel_cid, &cid)) + return channel; + } + command_fail(cmd, + "Channel ID not found: '%.*s'", + tok->end - tok->start, + buffer + tok->start); + return NULL; + } else if (json_tok_short_channel_id(buffer, tok, &scid)) { + list_for_each(&ld->peers, peer, list) { + channel = peer_active_channel(peer); + if (!channel) + continue; + if (channel->scid && channel->scid->u64 == scid.u64) + return channel; + } + command_fail(cmd, + "Short channel ID not found: '%.*s'", + tok->end - tok->start, + buffer + tok->start); + return NULL; + } else { + command_fail(cmd, + "Given id is not a channel ID or " + "short channel ID: '%.*s'", + tok->end - tok->start, + buffer + tok->start); + return NULL; + } +} + static void json_close(struct command *cmd, const char *buffer, const jsmntok_t *params) { - jsmntok_t *peertok; + jsmntok_t *idtok; jsmntok_t *timeouttok; jsmntok_t *forcetok; struct peer *peer; @@ -997,18 +1047,13 @@ static void json_close(struct command *cmd, bool force = false; if (!json_get_params(cmd, buffer, params, - "id", &peertok, + "id", &idtok, "?force", &forcetok, "?timeout", &timeouttok, NULL)) { return; } - peer = peer_from_json(cmd->ld, buffer, peertok); - if (!peer) { - command_fail(cmd, "Could not find peer with that id"); - return; - } if (forcetok && !json_tok_bool(buffer, forcetok, &force)) { command_fail(cmd, "Force '%.*s' must be true or false", forcetok->end - forcetok->start, @@ -1022,8 +1067,16 @@ static void json_close(struct command *cmd, return; } - channel = peer_active_channel(peer); - if (!channel) { + peer = peer_from_json(cmd->ld, buffer, idtok); + if (peer) + channel = peer_active_channel(peer); + else { + channel = command_find_channel(cmd, buffer, idtok); + if (!channel) + return; + } + + if (!channel && peer) { struct uncommitted_channel *uc = peer->uncommitted_channel; if (uc) { /* Easy case: peer can simply be forgotten. */ @@ -1045,7 +1098,7 @@ static void json_close(struct command *cmd, channel->state != CHANNELD_AWAITING_LOCKIN && channel->state != CHANNELD_SHUTTING_DOWN && channel->state != CLOSINGD_SIGEXCHANGE) - command_fail(cmd, "Peer is in state %s", + command_fail(cmd, "Channel is in state %s", channel_state_name(channel)); /* If normal or locking in, transition to shutting down @@ -1071,7 +1124,12 @@ static void json_close(struct command *cmd, static const struct json_command close_command = { "close", json_close, - "Close the channel with peer {id}" + "Close the channel with {id} " + "(either peer ID, channel ID, or short channel ID). " + "If {force} (default false) is true, force a unilateral close " + "after {timeout} seconds (default 30), " + "otherwise just schedule a mutual close later and fail after " + "timing out." }; AUTODATA(json_command, &close_command); diff --git a/wallet/test/run-wallet.c b/wallet/test/run-wallet.c index de3090561de3..34856bb73c74 100644 --- a/wallet/test/run-wallet.c +++ b/wallet/test/run-wallet.c @@ -257,6 +257,10 @@ void json_object_start(struct json_result *ptr UNNEEDED, const char *fieldname U /* Generated stub for json_tok_bool */ bool json_tok_bool(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, bool *b UNNEEDED) { fprintf(stderr, "json_tok_bool called!\n"); abort(); } +/* Generated stub for json_tok_channel_id */ +bool json_tok_channel_id(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, + struct channel_id *cid UNNEEDED) +{ fprintf(stderr, "json_tok_channel_id called!\n"); abort(); } /* Generated stub for json_tok_loglevel */ bool json_tok_loglevel(const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, enum log_level *level UNNEEDED)