mirror of
https://github.com/ElementsProject/lightning.git
synced 2024-11-19 09:54:16 +01:00
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:
parent
71b606e050
commit
019c052123
@ -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
|
||||
|
|
@ -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);
|
||||
|
@ -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}
|
||||
|
@ -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],
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user