JSON-API: Allow close channel to specified address

Command format: close id [unilateraltimeout] [destination]

Close the channel with peer {id}, forcing a unilateral
close after {unilateraltimeout} seconds if non-zero, and
the to-local output will be sent to {destination}. If
{destination} isn't specified, the default is the address
of lightningd.

Also change the pylightning:
update the `close` API to support `destination` parameter
This commit is contained in:
trueptolemy 2019-09-29 16:53:26 +08:00 committed by neil saitug
parent 71b606e050
commit 019c052123
8 changed files with 256 additions and 53 deletions

View File

@ -159,6 +159,8 @@ msgtype,channel_got_revoke_reply,1122
# Tell peer to shut down channel.
msgtype,channel_send_shutdown,1023
msgdata,channel_send_shutdown,shutdown_len,u16,
msgdata,channel_send_shutdown,shutdown_scriptpubkey,u8,shutdown_len
# Peer told us that channel is shutting down
msgtype,channel_got_shutdown,1024

1 #include <common/cryptomsg.h>
159 msgdata,channel_feerates,feerate,u32, msgtype,channel_dev_reenable_commit_reply,1126,
160 msgdata,channel_feerates,min_feerate,u32, msgtype,channel_feerates,1027
161 msgdata,channel_feerates,max_feerate,u32, msgdata,channel_feerates,feerate,u32,
162 msgdata,channel_feerates,min_feerate,u32,
163 msgdata,channel_feerates,max_feerate,u32,
164 # master -> channeld: do you have a memleak?
165 msgtype,channel_dev_memleak,1033
166 msgtype,channel_dev_memleak_reply,1133

View File

@ -2783,9 +2783,16 @@ static void handle_fail(struct peer *peer, const u8 *inmsg)
static void handle_shutdown_cmd(struct peer *peer, const u8 *inmsg)
{
if (!fromwire_channel_send_shutdown(inmsg))
u8 *local_shutdown_script;
if (!fromwire_channel_send_shutdown(peer, inmsg, &local_shutdown_script))
master_badmsg(WIRE_CHANNEL_SEND_SHUTDOWN, inmsg);
/* FIXME: When we support local upfront_shutdown_script, local_shutdown_script
* must equal to the local upfront_shutdown_script. */
tal_free(peer->final_scriptpubkey);
peer->final_scriptpubkey = local_shutdown_script;
/* We can't send this until commit (if any) is done, so start timer. */
peer->send_shutdown = true;
start_commit_timer(peer);

View File

@ -329,32 +329,30 @@ class LightningRpc(UnixDomainSocketRpc):
def close(self, peer_id, *args, **kwargs):
"""
Close the channel with peer {id}, forcing a unilateral
close after {unilateraltimeout} seconds if non-zero.
close after {unilateraltimeout} seconds if non-zero, and
the to-local output will be sent to {destination}.
Deprecated usage has {force} and {timeout} args.
"""
unilateraltimeout = None
if 'force' in kwargs or 'timeout' in kwargs:
return self._deprecated_close(peer_id, *args, **kwargs)
# Single arg is ambigious.
if len(args) == 1:
if len(args) >= 1:
if isinstance(args[0], bool):
return self._deprecated_close(peer_id, *args, **kwargs)
unilateraltimeout = args[0]
elif len(args) > 1:
return self._deprecated_close(peer_id, *args, **kwargs)
if 'unilateraltimeout' in kwargs:
unilateraltimeout = kwargs['unilateraltimeout']
def _close(peer_id, unilateraltimeout=None, destination=None):
payload = {
"id": peer_id,
"unilateraltimeout": unilateraltimeout
"unilateraltimeout": unilateraltimeout,
"destination": destination
}
return self.call("close", payload)
return _close(peer_id, *args, **kwargs)
def connect(self, peer_id, host=None, port=None):
"""
Connect to {peer_id} at {host} and {port}

View File

@ -291,8 +291,7 @@ void peer_start_closingd(struct channel *channel,
amount_msat_to_sat_round_down(their_msat),
channel->our_config.dust_limit,
minfee, feelimit, startfee,
p2wpkh_for_keyidx(tmpctx, ld,
channel->final_key_idx),
channel->shutdown_scriptpubkey[LOCAL],
channel->shutdown_scriptpubkey[REMOTE],
reconnected,
channel->next_index[LOCAL],

View File

@ -550,8 +550,7 @@ enum watch_result onchaind_funding_spent(struct channel *channel,
feerate,
channel->our_config.dust_limit,
&our_last_txid,
p2wpkh_for_keyidx(tmpctx, ld,
channel->final_key_idx),
channel->shutdown_scriptpubkey[LOCAL],
channel->shutdown_scriptpubkey[REMOTE],
&final_key,
channel->funder,

View File

@ -6,7 +6,9 @@
#include <bitcoin/script.h>
#include <bitcoin/tx.h>
#include <ccan/array_size/array_size.h>
#include <ccan/cast/cast.h>
#include <ccan/io/io.h>
#include <ccan/mem/mem.h>
#include <ccan/noerr/noerr.h>
#include <ccan/str/str.h>
#include <ccan/take/take.h>
@ -1222,6 +1224,61 @@ command_find_channel(struct command *cmd,
}
}
/* param_tok_timeout_or_force and param_tok_dest_or_timeout are made to
* support 'check' command for array type parameters.
*
* But the parameters are mixed with the old style and new style(like
* close {id} {force} {destination}), 'check' is unable to tell the error.
*/
static struct command_result *param_tok_timeout_or_force(
struct command *cmd, const char *name,
const char *buffer, const jsmntok_t * tok,
const jsmntok_t **out)
{
if (command_check_only(cmd)) {
unsigned int timeout;
bool force;
if (!json_to_bool(buffer, tok, &force)) {
if (!json_to_number(buffer, tok, &timeout))
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Expected unilerataltimeout to be a number");
}
return NULL;
}
*out = tok;
return NULL;
}
static struct command_result *param_tok_dest_or_timeout(
struct command *cmd, const char *name,
const char *buffer, const jsmntok_t * tok,
const jsmntok_t **out)
{
if (command_check_only(cmd)) {
unsigned int timeout;
const u8 *script;
if (!json_to_number(buffer, tok, &timeout)) {
enum address_parse_result res;
res = json_to_address_scriptpubkey(cmd,
get_chainparams(cmd->ld),
buffer, tok,
&script);
if (res == ADDRESS_PARSE_UNRECOGNIZED)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Could not parse destination address");
else if (res == ADDRESS_PARSE_WRONG_NETWORK)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Destination address is not on network %s",
get_chainparams(cmd->ld)->network_name);
}
return NULL;
}
*out = tok;
return NULL;
}
static struct command_result *json_close(struct command *cmd,
const char *buffer,
const jsmntok_t *obj UNNEEDED,
@ -1230,9 +1287,12 @@ static struct command_result *json_close(struct command *cmd,
const jsmntok_t *idtok;
struct peer *peer;
struct channel *channel COMPILER_WANTS_INIT("gcc 7.3.0 fails, 8.3 OK");
unsigned int *timeout;
unsigned int *timeout = NULL;
bool force = true;
bool do_timeout;
const u8 *local_shutdown_script = NULL;
unsigned int *old_timeout;
bool *old_force;
/* For generating help, give new-style. */
if (!params || !deprecated_apis) {
@ -1240,57 +1300,113 @@ static struct command_result *json_close(struct command *cmd,
p_req("id", param_tok, &idtok),
p_opt_def("unilateraltimeout", param_number,
&timeout, 48 * 3600),
p_opt("destination", param_bitcoin_address,
&local_shutdown_script),
NULL))
return command_param_failed();
do_timeout = (*timeout != 0);
} else if (params->type == JSMN_ARRAY) {
const jsmntok_t *tok;
const jsmntok_t *firsttok, *secondtok;
bool old_style;
/* Could be new or old style; get as tok. */
if (!param(cmd, buffer, params,
p_req("id", param_tok, &idtok),
p_opt("unilateraltimeout_or_force", param_tok, &tok),
p_opt("timeout", param_number, &timeout),
p_opt("unilateraltimeout_or_force",
param_tok_timeout_or_force, &firsttok),
p_opt("destination_or_timeout",
param_tok_dest_or_timeout, &secondtok),
NULL))
return command_param_failed();
if (tok) {
if (firsttok) {
/* old-style force bool? */
if (json_to_bool(buffer, tok, &force)) {
/* Old default timeout */
if (!timeout) {
if (json_to_bool(buffer, firsttok, &force)) {
old_style = true;
timeout = tal(cmd, unsigned int);
/* Old default timeout */
if (!secondtok)
*timeout = 30;
else {
if (!json_to_number(buffer, secondtok, timeout))
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"close: Expected timeout to be a number. "
"This argument ordering is deprecated!");
}
/* New-style timeout */
} else {
old_style = false;
timeout = tal(cmd, unsigned int);
if (!json_to_number(buffer, tok, timeout)) {
if (!json_to_number(buffer, firsttok, timeout))
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Expected unilerataltimeout to be a number");
if (secondtok) {
enum address_parse_result res;
res = json_to_address_scriptpubkey(cmd,
get_chainparams(cmd->ld),
buffer, secondtok,
&local_shutdown_script);
if (res == ADDRESS_PARSE_UNRECOGNIZED)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Could not parse destination address");
else if (res == ADDRESS_PARSE_WRONG_NETWORK)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Destination address is not on network %s",
get_chainparams(cmd->ld)->network_name);
}
}
} else if (secondtok) {
unsigned int *tmp_timeout = tal(tmpctx, unsigned int);
if (json_to_number(buffer, secondtok, tmp_timeout)) {
old_style = true;
timeout = tal_steal(cmd, tmp_timeout);
} else {
old_style = false;
enum address_parse_result res;
res = json_to_address_scriptpubkey(cmd,
get_chainparams(cmd->ld),
buffer, secondtok,
&local_shutdown_script);
if (res == ADDRESS_PARSE_UNRECOGNIZED)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Could not parse destination address");
else if (res == ADDRESS_PARSE_WRONG_NETWORK)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Destination address is not on network %s",
get_chainparams(cmd->ld)->network_name);
}
} else
old_style = false;
/* If they didn't specify timeout, it's the (new) default */
if (!timeout) {
timeout = tal(cmd, unsigned int);
*timeout = 48 * 3600;
}
/* New style: do_timeout unless it's 0 */
if (!old_style)
do_timeout = (*timeout != 0);
else
do_timeout = true;
} else {
unsigned int *old_timeout;
bool *old_force;
/* Named parameters are easy to distinguish */
if (!param(cmd, buffer, params,
p_req("id", param_tok, &idtok),
p_opt_def("unilateraltimeout", param_number,
&timeout, 48 * 3600),
p_opt("destination", param_bitcoin_address,
&local_shutdown_script),
p_opt("force", param_bool, &old_force),
p_opt("timeout", param_number, &old_timeout),
NULL))
return command_param_failed();
/* Old style has lower priority. */
if (!local_shutdown_script) {
/* Old style. */
if (old_timeout) {
*timeout = *old_timeout;
@ -1301,6 +1417,7 @@ static struct command_result *json_close(struct command *cmd,
*timeout = 30;
force = *old_force;
}
}
/* New style: do_timeout unless it's 0 */
if (!old_timeout && !old_force)
@ -1336,31 +1453,100 @@ static struct command_result *json_close(struct command *cmd,
* close command may have timed out, and this current command
* will continue waiting for the effects of the previous
* close command. */
if (channel->state != CHANNELD_NORMAL &&
channel->state != CHANNELD_AWAITING_LOCKIN &&
channel->state != CHANNELD_SHUTTING_DOWN &&
channel->state != CLOSINGD_SIGEXCHANGE) {
return command_fail(cmd, LIGHTNINGD, "Channel is in state %s",
channel_state_name(channel));
}
/* If normal or locking in, transition to shutting down
* state.
* (if already shutting down or sigexchange, just keep
* waiting) */
if (channel->state == CHANNELD_NORMAL || channel->state == CHANNELD_AWAITING_LOCKIN) {
/* Change the channel state first. */
channel_set_state(channel,
channel->state, CHANNELD_SHUTTING_DOWN);
/* FIXME: When we support local upfront_shutdown_script, local_shutdown_script
* must equal to the local upfront_shutdown_script. */
if (local_shutdown_script) {
tal_free(channel->shutdown_scriptpubkey[LOCAL]);
channel->shutdown_scriptpubkey[LOCAL]
= tal_steal(channel, cast_const(u8 *, local_shutdown_script));
}
if (channel->owner)
subd_send_msg(channel->owner,
take(towire_channel_send_shutdown(channel)));
take(towire_channel_send_shutdown(NULL,
channel->shutdown_scriptpubkey[LOCAL])));
} else if (channel->state == CHANNELD_SHUTTING_DOWN) {
/* FIXME: Add to spec that we must allow repeated shutdown! */
if (!local_shutdown_script)
local_shutdown_script = p2wpkh_for_keyidx(channel,
cmd->ld,
channel->final_key_idx);
bool change_script = !memeq(local_shutdown_script,
tal_count(local_shutdown_script),
channel->shutdown_scriptpubkey[LOCAL],
tal_count(channel->shutdown_scriptpubkey[LOCAL]));
if (change_script) {
log_debug(channel->log, "Repeated close command: "
"the new local scriptpubkey is %s, "
"and the old local scriptpubkey is %s",
local_shutdown_script,
channel->shutdown_scriptpubkey[LOCAL]);
if (!channel->owner)
return command_fail(cmd, LIGHTNINGD,
"The sub-daemon of channel is down(state %s), "
"can't change to-local destination "
"from %s to %s",
channel_state_name(channel),
channel->shutdown_scriptpubkey[LOCAL],
local_shutdown_script);
}
tal_free(channel->shutdown_scriptpubkey[LOCAL]);
channel->shutdown_scriptpubkey[LOCAL]
= tal_steal(channel, cast_const(u8 *, local_shutdown_script));
if (channel->owner)
subd_send_msg(channel->owner,
take(towire_channel_send_shutdown(NULL,
channel->shutdown_scriptpubkey[LOCAL])));
} else if (channel->state == CLOSINGD_SIGEXCHANGE) {
u8 *default_script = p2wpkh_for_keyidx(tmpctx, cmd->ld,
channel->final_key_idx);
bool is_default = memeq(default_script,
tal_count(default_script),
channel->shutdown_scriptpubkey[LOCAL],
tal_count(channel->shutdown_scriptpubkey[LOCAL]));
if (!local_shutdown_script) {
/* Means the user want to send to default address. */
local_shutdown_script = p2wpkh_for_keyidx(tmpctx, cmd->ld,
channel->final_key_idx);
}
if (!memeq(local_shutdown_script,
tal_count(local_shutdown_script),
channel->shutdown_scriptpubkey[LOCAL],
tal_count(channel->shutdown_scriptpubkey[LOCAL])))
return command_fail(cmd, LIGHTNINGD,
"Channel has already been closing now (in state %s) "
"with to-local destination %s",
channel_state_name(channel),
is_default ?
tal_fmt(tmpctx, "(default) %s",
channel->shutdown_scriptpubkey[LOCAL]) :
(char *)channel->shutdown_scriptpubkey[LOCAL]);
} else
return command_fail(cmd, LIGHTNINGD, "Channel is in state %s",
channel_state_name(channel));
/* Register this command for later handling. */
register_close_command(cmd->ld, cmd, channel,
do_timeout ? timeout : NULL, force);
/* We may set new `channel->shutdown_scriptpubkey[LOCAL]` field. Save it. */
wallet_channel_save(cmd->ld->wallet, channel);
/* Wait until close drops down to chain. */
return command_still_pending(cmd);
}

View File

@ -437,7 +437,7 @@ u8 *towire_channel_dev_memleak(const tal_t *ctx UNNEEDED)
u8 *towire_channel_dev_reenable_commit(const tal_t *ctx UNNEEDED)
{ fprintf(stderr, "towire_channel_dev_reenable_commit called!\n"); abort(); }
/* Generated stub for towire_channel_send_shutdown */
u8 *towire_channel_send_shutdown(const tal_t *ctx UNNEEDED)
u8 *towire_channel_send_shutdown(const tal_t *ctx UNNEEDED, const u8 *shutdown_scriptpubkey UNNEEDED)
{ fprintf(stderr, "towire_channel_send_shutdown called!\n"); abort(); }
/* Generated stub for towire_channel_specific_feerates */
u8 *towire_channel_specific_feerates(const tal_t *ctx UNNEEDED, u32 feerate_base UNNEEDED, u32 feerate_ppm UNNEEDED)
@ -616,6 +616,9 @@ struct command_result *param_bitcoin_address(struct command *cmd UNNEEDED,
const jsmntok_t *tok UNNEEDED,
const u8 **scriptpubkey UNNEEDED)
{ fprintf(stderr, "param_bitcoin_address called!\n"); abort(); }
/* Generated stub for command_check_only */
bool command_check_only(const struct command *cmd UNNEEDED)
{ fprintf(stderr, "command_check_only called!\n"); abort(); }
/* AUTOGENERATED MOCKS END */
#if DEVELOPER

View File

@ -558,7 +558,7 @@ u8 *towire_channel_offer_htlc(const tal_t *ctx UNNEEDED, struct amount_msat amou
u8 *towire_channel_sending_commitsig_reply(const tal_t *ctx UNNEEDED)
{ fprintf(stderr, "towire_channel_sending_commitsig_reply called!\n"); abort(); }
/* Generated stub for towire_channel_send_shutdown */
u8 *towire_channel_send_shutdown(const tal_t *ctx UNNEEDED)
u8 *towire_channel_send_shutdown(const tal_t *ctx UNNEEDED, const u8 *shutdown_scriptpubkey UNNEEDED)
{ fprintf(stderr, "towire_channel_send_shutdown called!\n"); abort(); }
/* Generated stub for towire_channel_specific_feerates */
u8 *towire_channel_specific_feerates(const tal_t *ctx UNNEEDED, u32 feerate_base UNNEEDED, u32 feerate_ppm UNNEEDED)
@ -615,6 +615,15 @@ struct command_result *param_bitcoin_address(struct command *cmd UNNEEDED,
const jsmntok_t *tok UNNEEDED,
const u8 **scriptpubkey UNNEEDED)
{ fprintf(stderr, "param_bitcoin_address called!\n"); abort(); }
/* Generated stub for json_tok_address_scriptpubkey */
enum address_parse_result json_to_address_scriptpubkey(const tal_t *ctx UNNEEDED,
const struct chainparams *chainparams UNNEEDED,
const char *buffer UNNEEDED,
const jsmntok_t *tok UNNEEDED, const u8 **scriptpubkey UNNEEDED)
{ fprintf(stderr, "json_tok_address_scriptpubkey called!\n"); abort(); }
/* Generated stub for command_check_only */
bool command_check_only(const struct command *cmd UNNEEDED)
{ fprintf(stderr, "command_check_only called!\n"); abort(); }
/* AUTOGENERATED MOCKS END */
#if DEVELOPER