diff --git a/channeld/channel_wire.csv b/channeld/channel_wire.csv index 0a56047b9..5744de929 100644 --- a/channeld/channel_wire.csv +++ b/channeld/channel_wire.csv @@ -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 diff --git a/channeld/channeld.c b/channeld/channeld.c index 1632fcc6f..525d01d45 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -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); diff --git a/contrib/pylightning/lightning/lightning.py b/contrib/pylightning/lightning/lightning.py index 4ac87afdd..aa506bb79 100644 --- a/contrib/pylightning/lightning/lightning.py +++ b/contrib/pylightning/lightning/lightning.py @@ -329,31 +329,29 @@ 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, + "destination": destination + } + return self.call("close", payload) - payload = { - "id": peer_id, - "unilateraltimeout": unilateraltimeout - } - return self.call("close", payload) + return _close(peer_id, *args, **kwargs) def connect(self, peer_id, host=None, port=None): """ diff --git a/lightningd/closing_control.c b/lightningd/closing_control.c index dd3d06099..3772f1238 100644 --- a/lightningd/closing_control.c +++ b/lightningd/closing_control.c @@ -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], diff --git a/lightningd/onchain_control.c b/lightningd/onchain_control.c index c65e741ef..740747868 100644 --- a/lightningd/onchain_control.c +++ b/lightningd/onchain_control.c @@ -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, diff --git a/lightningd/peer_control.c b/lightningd/peer_control.c index 8cda06283..556650a4e 100644 --- a/lightningd/peer_control.c +++ b/lightningd/peer_control.c @@ -6,7 +6,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -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,66 +1300,123 @@ 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, + 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)) { + if (json_to_bool(buffer, firsttok, &force)) { + old_style = true; + timeout = tal(cmd, unsigned int); + /* Old default timeout */ - if (!timeout) { - timeout = tal(cmd, unsigned int); + 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; } - do_timeout = true; + /* 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. */ - if (old_timeout) { - *timeout = *old_timeout; - } - if (old_force) { - /* Use old default */ - if (!old_timeout) - *timeout = 30; - force = *old_force; + + /* Old style has lower priority. */ + if (!local_shutdown_script) { + /* Old style. */ + if (old_timeout) { + *timeout = *old_timeout; + } + if (old_force) { + /* Use old default */ + if (!old_timeout) + *timeout = 30; + force = *old_force; + } } /* New style: do_timeout unless it's 0 */ @@ -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); } diff --git a/lightningd/test/run-invoice-select-inchan.c b/lightningd/test/run-invoice-select-inchan.c index e3b5fe43b..2d946a9ef 100644 --- a/lightningd/test/run-invoice-select-inchan.c +++ b/lightningd/test/run-invoice-select-inchan.c @@ -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 diff --git a/wallet/test/run-wallet.c b/wallet/test/run-wallet.c index 00026278f..f705848a0 100644 --- a/wallet/test/run-wallet.c +++ b/wallet/test/run-wallet.c @@ -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