diff --git a/channeld/channeld.c b/channeld/channeld.c index 9c637d5d8..4d76d19ab 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -57,7 +57,8 @@ #define VALID_STFU_MESSAGE(msg) \ ((msg) == WIRE_SPLICE || \ - (msg) == WIRE_SPLICE_ACK) + (msg) == WIRE_SPLICE_ACK || \ + (msg) == WIRE_TX_ABORT) #define SAT_MIN(a, b) (amount_sat_less((a), (b)) ? (a) : (b)) @@ -3969,7 +3970,8 @@ static void splice_initiator_user_finalized(struct peer *peer) outmsg = towire_channeld_splice_confirmed_update(NULL, new_inflight->psbt, - true); + true, + !sign_first); wire_sync_write(MASTER_FD, take(outmsg)); } @@ -4042,7 +4044,7 @@ static void splice_initiator_user_update(struct peer *peer, const u8 *inmsg) /* Peer may have modified our PSBT so we return it to the user here */ outmsg = towire_channeld_splice_confirmed_update(NULL, ictx->current_psbt, - false); + false, false); wire_sync_write(MASTER_FD, take(outmsg)); } @@ -4147,6 +4149,7 @@ static void handle_splice_stfu_success(struct peer *peer) static void handle_splice_init(struct peer *peer, const u8 *inmsg) { u8 *msg; + bool skip_stfu; /* Can't start a splice with another splice still active */ if (peer->splicing) { @@ -4163,23 +4166,32 @@ static void handle_splice_init(struct peer *peer, const u8 *inmsg) &peer->splicing->current_psbt, &peer->splicing->opener_relative, &peer->splicing->feerate_per_kw, - &peer->splicing->force_feerate)) + &peer->splicing->force_feerate, + &skip_stfu)) master_badmsg(WIRE_CHANNELD_SPLICE_INIT, inmsg); - if (peer->want_stfu) { + if (!skip_stfu && peer->want_stfu) { msg = towire_channeld_splice_state_error(NULL, "Can't begin a" " splice while waiting" " for STFU."); wire_sync_write(MASTER_FD, take(msg)); return; } - if (is_stfu_active(peer)) { + if (!skip_stfu && is_stfu_active(peer)) { msg = towire_channeld_splice_state_error(NULL, "Can't begin a" " splice while" " currently in STFU"); wire_sync_write(MASTER_FD, take(msg)); return; } + if (skip_stfu && !is_stfu_active(peer)) { + msg = towire_channeld_splice_state_error(NULL, "Can't begin a" + " splice with" + " skip_stfu if not" + " already in STFU"); + wire_sync_write(MASTER_FD, take(msg)); + return; + } if (peer->splicing->mode) { msg = towire_channeld_splice_state_error(NULL, "Can't begin a" " splice while already" @@ -4199,16 +4211,74 @@ static void handle_splice_init(struct peer *peer, const u8 *inmsg) return; } - status_debug("Getting handle_splice_init psbt version %d", peer->splicing->current_psbt->version); + status_debug("Getting handle_splice_init psbt version %d", + peer->splicing->current_psbt->version); - peer->on_stfu_success = handle_splice_stfu_success; + if (skip_stfu) { + handle_splice_stfu_success(peer); + } else { + peer->on_stfu_success = handle_splice_stfu_success; + + /* First things first we must STFU the channel */ + peer->stfu_initiator = LOCAL; + peer->want_stfu = true; + maybe_send_stfu(peer); + } +} + +static void handle_stfu_req_success(struct peer *peer) +{ + struct amount_msat available_funds = peer->channel->view->owed[LOCAL]; + /* DTODO: Subtract reserve requirment from available_funds? */ + wire_sync_write(MASTER_FD, + take(towire_channeld_confirmed_stfu(NULL, + available_funds))); +} + +static void handle_stfu_req(struct peer *peer, const u8 *inmsg) +{ + u8 *msg; + + if (!fromwire_channeld_stfu(inmsg)) + master_badmsg(WIRE_CHANNELD_STFU, inmsg); + + if (peer->splicing) { + msg = towire_channeld_splice_state_error(NULL, "Can't start" + " stfu when a splice" + " is active"); + wire_sync_write(MASTER_FD, take(msg)); + return; + } + if (peer->want_stfu) { + msg = towire_channeld_splice_state_error(NULL, "Can't stfu" + " splice while waiting" + " for STFU."); + wire_sync_write(MASTER_FD, take(msg)); + return; + } + if (is_stfu_active(peer)) { + msg = towire_channeld_splice_state_error(NULL, "Can't stfu" + " splice while" + " currently in STFU"); + wire_sync_write(MASTER_FD, take(msg)); + return; + } + + peer->on_stfu_success = handle_stfu_req_success; - /* First things first we must STFU the channel */ peer->stfu_initiator = LOCAL; peer->want_stfu = true; maybe_send_stfu(peer); } +static void handle_abort_req(struct peer *peer, const u8 *inmsg) +{ + if (!fromwire_channeld_abort(inmsg)) + master_badmsg(WIRE_CHANNELD_ABORT, inmsg); + + splice_abort(peer, "requested by user"); +} + static void peer_in(struct peer *peer, const u8 *msg) { enum peer_wire type = fromwire_peektype(msg); @@ -4309,6 +4379,9 @@ static void peer_in(struct peer *peer, const u8 *msg) case WIRE_SPLICE_LOCKED: handle_peer_splice_locked(peer, msg); return; + case WIRE_TX_ABORT: + check_tx_abort(peer, msg); + return; case WIRE_INIT: case WIRE_OPEN_CHANNEL: case WIRE_ACCEPT_CHANNEL: @@ -4320,7 +4393,6 @@ static void peer_in(struct peer *peer, const u8 *msg) case WIRE_TX_ADD_OUTPUT: case WIRE_TX_REMOVE_OUTPUT: case WIRE_TX_COMPLETE: - case WIRE_TX_ABORT: case WIRE_OPEN_CHANNEL2: case WIRE_ACCEPT_CHANNEL2: case WIRE_TX_SIGNATURES: @@ -5768,6 +5840,12 @@ static void req_in(struct peer *peer, const u8 *msg) case WIRE_CHANNELD_SPLICE_SIGNED: splice_initiator_user_signed(peer, msg); return; + case WIRE_CHANNELD_STFU: + handle_stfu_req(peer, msg); + return; + case WIRE_CHANNELD_ABORT: + handle_abort_req(peer, msg); + return; case WIRE_CHANNELD_SPLICE_CONFIRMED_INIT: case WIRE_CHANNELD_SPLICE_CONFIRMED_SIGNED: case WIRE_CHANNELD_SPLICE_SENDING_SIGS: @@ -5778,6 +5856,7 @@ static void req_in(struct peer *peer, const u8 *msg) case WIRE_CHANNELD_SPLICE_FUNDING_ERROR: case WIRE_CHANNELD_SPLICE_ABORT: check_tx_abort(peer, msg); + case WIRE_CHANNELD_CONFIRMED_STFU: break; case WIRE_CHANNELD_DEV_REENABLE_COMMIT: if (peer->developer) { diff --git a/channeld/channeld_wire.csv b/channeld/channeld_wire.csv index c521901b0..592a8b65a 100644 --- a/channeld/channeld_wire.csv +++ b/channeld/channeld_wire.csv @@ -210,6 +210,7 @@ msgdata,channeld_splice_init,psbt,wally_psbt, msgdata,channeld_splice_init,relative_amount,s64, msgdata,channeld_splice_init,feerate_per_kw,u32, msgdata,channeld_splice_init,force_feerate,bool, +msgdata,channeld_splice_init,skip_stfu,bool, # channeld->master: hello, I started a channel splice open msgtype,channeld_splice_confirmed_init,7205 @@ -223,6 +224,7 @@ msgdata,channeld_splice_update,psbt,wally_psbt, msgtype,channeld_splice_confirmed_update,7207 msgdata,channeld_splice_confirmed_update,psbt,wally_psbt, msgdata,channeld_splice_confirmed_update,commitments_secured,bool, +msgdata,channeld_splice_confirmed_update,signatures_secured,bool, # channeld->master: Lookup a transaction msgtype,channeld_splice_lookup_tx,7208 @@ -287,6 +289,16 @@ msgdata,channeld_splice_abort,did_i_initiate,bool, msgdata,channeld_splice_abort,inflight_outpoint,?bitcoin_outpoint, msgdata,channeld_splice_abort,reason,?wirestring, +# master->channeld: Please enter stfu mode +msgtype,channeld_stfu,7224 + +# channeld->master: Entered stfu result +msgtype,channeld_confirmed_stfu,7225 +msgdata,channeld_confirmed_stfu,available_funds,amount_msat, + +# master->channeld: Please enter perform tx_abort +msgtype,channeld_abort,7226 + # Tell peer to shut down channel. msgtype,channeld_send_shutdown,1023 msgdata,channeld_send_shutdown,final_index,?u32, diff --git a/lightningd/channel_control.c b/lightningd/channel_control.c index 1483eb8f8..8e23a6138 100644 --- a/lightningd/channel_control.c +++ b/lightningd/channel_control.c @@ -31,6 +31,12 @@ #include #include +struct stfu_result +{ + struct channel_id channel_id; + struct amount_msat available_funds; +}; + struct splice_command { /* Inside struct lightningd splice_commands. */ struct list_node list; @@ -38,6 +44,11 @@ struct splice_command { struct command *cmd; /* Channel being spliced. */ struct channel *channel; + /* For multi-channel commands: remaining channels awaiting response. + * Allocated on ld -- free when finished. */ + struct channel_id **channel_ids; + /* For multi-channel stfu command: the pending result */ + struct stfu_result **results; }; void channel_update_feerates(struct lightningd *ld, const struct channel *channel) @@ -414,16 +425,15 @@ static void handle_splice_confirmed_update(struct lightningd *ld, bool commitments_secured, signatures_secured; if (!fromwire_channeld_splice_confirmed_update(tmpctx, - msg, - &psbt, - &commitments_secured)) { + msg, + &psbt, + &commitments_secured, + &signatures_secured)) { channel_internal_error(channel, "bad splice_confirmed_update %s", tal_hex(channel, msg)); return; } - /* FIXME! */ - signatures_secured = commitments_secured; cc = splice_command_for_chan(ld, channel); if (!cc) { @@ -479,6 +489,7 @@ struct send_splice_info const struct bitcoin_tx *final_tx; u32 output_index; const char *err_msg; + const struct wally_psbt *psbt; }; static void handle_tx_broadcast(struct send_splice_info *info) @@ -506,6 +517,7 @@ static void handle_tx_broadcast(struct send_splice_info *info) json_add_hex(response, "tx", tx_bytes, tal_bytelen(tx_bytes)); json_add_txid(response, "txid", &txid); json_add_u32(response, "outnum", info->output_index); + json_add_psbt(response, "psbt", info->psbt); was_pending(command_success(info->cc->cmd, response)); } @@ -571,7 +583,8 @@ static void send_splice_tx_done(struct bitcoind *bitcoind UNUSED, static void send_splice_tx(struct channel *channel, const struct bitcoin_tx *tx, struct splice_command *cc, - u32 output_index) + u32 output_index, + struct wally_psbt *psbt) { struct lightningd *ld = channel->peer->ld; u8* tx_bytes = linearize_tx(tmpctx, tx); @@ -588,6 +601,7 @@ static void send_splice_tx(struct channel *channel, info->final_tx = tal_steal(info, tx); info->output_index = output_index; info->err_msg = NULL; + info->psbt = psbt; bitcoind_sendrawtx(ld->topology->bitcoind, ld->topology->bitcoind, @@ -637,7 +651,7 @@ static void handle_splice_confirmed_signed(struct lightningd *ld, cc = splice_command_for_chan(ld, channel); - send_splice_tx(channel, tx, cc, output_index); + send_splice_tx(channel, tx, cc, output_index, inflight->funding_psbt); } static enum watch_result splice_depth_cb(struct lightningd *ld, @@ -1424,6 +1438,75 @@ static void handle_local_anchors(struct channel *channel, const u8 *msg) } } +/* Channeld sends us this in response to a user's `stfu` request */ +static void handle_confirmed_stfu(struct lightningd *ld, + struct channel *channel, + const u8 *msg) +{ + struct splice_command *cc; + struct amount_msat available_funds; + struct stfu_result *stfu_result; + + if (!fromwire_channeld_confirmed_stfu(msg, + &available_funds)) { + channel_internal_error(channel, + "bad confirmed_stfu %s", + tal_hex(channel, msg)); + return; + } + + cc = splice_command_for_chan(ld, channel); + if (!cc) { + channel_internal_error(channel, "confirmed_stfu" + " received without an active command %s", + tal_hex(channel, msg)); + return; + } + + log_info(channel->log, "lightningd got confirmed stfu from channeld," + " channel_id count: %zu", tal_count(cc->channel_ids)); + + for (size_t i = 0; i < tal_count(cc->channel_ids); i++) { + if (channel_id_eq(cc->channel_ids[i], &channel->cid)) { + stfu_result = tal(cc->results, struct stfu_result); + stfu_result->channel_id = channel->cid; + stfu_result->available_funds = available_funds; + + tal_arr_expand(&cc->results, stfu_result); + tal_arr_remove(&cc->channel_ids, i); + + log_info(channel->log, "lightningd found channel_id in command and removed it"); + break; + } + } + + log_info(channel->log, "Finished processing confirmed stfu," + " channel_id count: %zu", tal_count(cc->channel_ids)); + + /* Once we run out of pending stfu requests we return to user */ + if (tal_count(cc->channel_ids)) + return; + + struct json_stream *response = json_stream_success(cc->cmd); + + json_array_start(response, "channels"); + for (size_t i = 0; i < tal_count(cc->results); i++) { + json_object_start(response, NULL); + json_add_channel_id(response, "channel_id", + &cc->results[i]->channel_id); + json_add_amount_msat(response, "available_msat", + cc->results[i]->available_funds); + json_object_end(response); + } + json_array_end(response); + + /* channel_ids and results are free'd when the last stfu is finished */ + tal_free(cc->channel_ids); + tal_free(cc->results); + + was_pending(command_success(cc->cmd, response)); +} + static unsigned channel_msg(struct subd *sd, const u8 *msg, const int *fds) { enum channeld_wire t = fromwire_peektype(msg); @@ -1501,6 +1584,9 @@ static unsigned channel_msg(struct subd *sd, const u8 *msg, const int *fds) case WIRE_CHANNELD_GOT_SPLICE_LOCKED: handle_peer_splice_locked(sd->channel, msg); break; + case WIRE_CHANNELD_CONFIRMED_STFU: + handle_confirmed_stfu(sd->ld, sd->channel, msg); + break; case WIRE_CHANNELD_UPGRADED: handle_channel_upgrade(sd->channel, msg); break; @@ -1530,7 +1616,9 @@ static unsigned channel_msg(struct subd *sd, const u8 *msg, const int *fds) case WIRE_CHANNELD_SPLICE_UPDATE: case WIRE_CHANNELD_SPLICE_LOOKUP_TX_RESULT: case WIRE_CHANNELD_SPLICE_SIGNED: + case WIRE_CHANNELD_STFU: case WIRE_CHANNELD_DEV_QUIESCE_REPLY: + case WIRE_CHANNELD_ABORT: break; } @@ -2108,7 +2196,7 @@ static struct command_result *json_splice_init(struct command *cmd, struct wally_psbt *initialpsbt; s64 *relative_amount; u32 *feerate_per_kw; - bool *force_feerate; + bool *force_feerate, *skip_stfu; u8 *msg; if (!param_check(cmd, buffer, params, @@ -2117,6 +2205,7 @@ static struct command_result *json_splice_init(struct command *cmd, p_opt("initialpsbt", param_psbt, &initialpsbt), p_opt("feerate_per_kw", param_feerate, &feerate_per_kw), p_opt_def("force_feerate", param_bool, &force_feerate, false), + p_opt_def("skip_stfu", param_bool, &skip_stfu, false), NULL)) return command_param_failed(); @@ -2151,9 +2240,12 @@ static struct command_result *json_splice_init(struct command *cmd, cc->cmd = cmd; cc->channel = channel; + cc->channel_ids = NULL; + cc->results = NULL; msg = towire_channeld_splice_init(NULL, initialpsbt, *relative_amount, - *feerate_per_kw, *force_feerate); + *feerate_per_kw, *force_feerate, + *skip_stfu); subd_send_msg(channel->owner, take(msg)); return command_still_pending(cmd); @@ -2194,6 +2286,8 @@ static struct command_result *json_splice_update(struct command *cmd, cc->cmd = cmd; cc->channel = channel; + cc->channel_ids = NULL; + cc->results = NULL; subd_send_msg(channel->owner, take(towire_channeld_splice_update(NULL, psbt))); @@ -2222,6 +2316,8 @@ static struct command_result *single_splice_signed(struct command *cmd, cc->cmd = cmd; cc->channel = channel; + cc->channel_ids = NULL; + cc->results = NULL; msg = towire_channeld_splice_signed(tmpctx, psbt, sign_first); subd_send_msg(channel->owner, take(msg)); @@ -2253,13 +2349,13 @@ static struct command_result *json_splice_signed(struct command *cmd, return command_fail(cmd, SPLICE_INPUT_ERROR, "PSBT failed to validate."); - if (command_check_only(cmd)) - return command_check_done(cmd); - /* If a single channel is specified, we do that and finish. */ - if (channel) + if (channel) { + if (command_check_only(cmd)) + return command_check_done(cmd); return single_splice_signed(cmd, channel, psbt, *sign_first, NULL); + } if (!psbt_get_channel_ids(tmpctx, psbt, &channel_ids)) return command_fail(cmd, SPLICE_INPUT_ERROR, @@ -2274,6 +2370,9 @@ static struct command_result *json_splice_signed(struct command *cmd, return result; } + if (command_check_only(cmd)) + return command_check_done(cmd); + /* Now execute the splice event for each channel */ /* TODO: We need to intelligently choose the order of channel to splice, * store the signatures received on each run, and pass them to the next @@ -2291,6 +2390,114 @@ static struct command_result *json_splice_signed(struct command *cmd, return result; } +static struct command_result *json_stfu_channels(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) +{ + struct channel *channel, **channels; + struct channel_id **channel_ids; + const jsmntok_t *channel_ids_tok, *channel_id_tok; + struct command_result *result; + struct splice_command *cc; + struct stfu_result **stfu_result; + size_t i; + + if (!param_check(cmd, buffer, params, + p_opt("channel_ids", param_array, &channel_ids_tok), + NULL)) + return command_param_failed(); + + channels = tal_arr(cmd, struct channel*, 0); + json_for_each_arr(i, channel_id_tok, channel_ids_tok) { + result = param_channel_for_splice(cmd, NULL, buffer, channel_id_tok, + &channel); + if (result) + return result; + if (splice_command_for_chan(cmd->ld, channel)) + return command_fail(cmd, + SPLICE_BUSY_ERROR, + "Currently waiting on previous" + " splice command to finish."); + + tal_arr_expand(&channels, channel); + } + + if (!tal_count(channels)) + return command_fail_badparam(cmd, "channel_ids", buffer, + channel_ids_tok, + "Must specify a channel"); + + if (command_check_only(cmd)) + return command_check_done(cmd); + + /* Next we split into multiple `stfu` commands. The final command to + * return will handle free'ing `stfu_result` and `channel_ids` */ + stfu_result = tal_arr(NULL, struct stfu_result*, 0); + channel_ids = tal_arr(NULL, struct channel_id*, tal_count(channels)); + + for (i = 0; i < tal_count(channels); i++) { + channel = channels[i]; + + channel_ids[i] = tal(channel_ids, struct channel_id); + *channel_ids[i] = channel->cid; + + cc = tal(cmd, struct splice_command); + + list_add_tail(&cmd->ld->splice_commands, &cc->list); + tal_add_destructor(cc, destroy_splice_command); + + cc->cmd = cmd; + cc->channel = channel; + cc->channel_ids = channel_ids; + cc->results = stfu_result; + + subd_send_msg(channel->owner, take(towire_channeld_stfu(NULL))); + } + + return command_still_pending(cmd); +} + +static struct command_result *json_abort_channels(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) +{ + struct channel **channels; + struct channel *channel; + const jsmntok_t *channel_ids_tok, *channel_id_tok; + struct command_result *result; + size_t i; + + if (!param_check(cmd, buffer, params, + p_opt("channel_ids", param_array, &channel_ids_tok), + NULL)) + return command_param_failed(); + + channels = tal_arr(cmd, struct channel*, 0); + json_for_each_arr(i, channel_id_tok, channel_ids_tok) { + result = param_channel_for_splice(cmd, NULL, buffer, channel_id_tok, + &channel); + if (result) + return result; + tal_arr_expand(&channels, channel); + } + + if (!tal_count(channels)) + return command_fail_badparam(cmd, "channel_ids", buffer, + channel_ids_tok, + "Must specify a channel"); + + if (command_check_only(cmd)) + return command_check_done(cmd); + + for (i = 0; i < tal_count(channels); i++) + subd_send_msg(channels[i]->owner, + take(towire_channeld_abort(NULL))); + + return command_success(cmd, json_stream_success(cmd)); +} + static const struct json_command splice_init_command = { "splice_init", json_splice_init, @@ -2309,6 +2516,18 @@ static const struct json_command splice_signed_command = { }; AUTODATA(json_command, &splice_signed_command); +static const struct json_command stfu_channels_command = { + "stfu_channels", + json_stfu_channels, +}; +AUTODATA(json_command, &stfu_channels_command); + +static const struct json_command abort_channels_command = { + "abort_channels", + json_abort_channels, +}; +AUTODATA(json_command, &abort_channels_command); + static struct command_result *json_dev_feerate(struct command *cmd, const char *buffer, const jsmntok_t *obj UNNEEDED, diff --git a/tests/plugins/channeld_fakenet.c b/tests/plugins/channeld_fakenet.c index c9e4f52ec..8b7db6605 100644 --- a/tests/plugins/channeld_fakenet.c +++ b/tests/plugins/channeld_fakenet.c @@ -1221,6 +1221,9 @@ static struct io_plan *recv_req(struct io_conn *conn, case WIRE_CHANNELD_SPLICE_FEERATE_ERROR: case WIRE_CHANNELD_SPLICE_FUNDING_ERROR: case WIRE_CHANNELD_SPLICE_ABORT: + case WIRE_CHANNELD_STFU: + case WIRE_CHANNELD_CONFIRMED_STFU: + case WIRE_CHANNELD_ABORT: /* Not supported */ case WIRE_CHANNELD_DEV_REENABLE_COMMIT: case WIRE_CHANNELD_DEV_QUIESCE: