splice: Bulk channel stfu and abort RPC

The ability to stfu channels in bulk is required to do complex multi channel operations. When stfu’ing in this manner, the available funds at the moment of stfu is returned to the user.

In order to cancel the stfu we also add a bulk tx_abort command.

Changelog-Added: `stfu_channels` and `abort_channels` are added for bulk multi-channel splice commands. These allow the user to pause (and resume) multiple channels in place.
This commit is contained in:
Dusty Daemon 2024-11-11 11:47:37 +10:30 committed by Rusty Russell
parent d60e9f342b
commit 879d1191e8
4 changed files with 336 additions and 23 deletions

View file

@ -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) {

View file

@ -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,

Can't render this file because it has a wrong number of fields in line 16.

View file

@ -31,6 +31,12 @@
#include <wally_bip32.h>
#include <wally_psbt.h>
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,

View file

@ -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: