spender: look for unsigned PSBT on awaiting channels on startup, and re-send.

This covers the other corner case, where we crash before actually
signing and sending the PSBT.  We can spot this because the channel is
in AWAITING_LOCKIN and we have a PSBT, but it's not signed yet.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell
2025-11-18 14:41:04 +10:30
parent 606aad07ed
commit df5f38dbc1
4 changed files with 91 additions and 4 deletions

View File

@@ -11,7 +11,7 @@
static
const char *spender_init(struct command *init_cmd, const char *b, const jsmntok_t *t)
{
openchannel_init(init_cmd->plugin, b, t);
openchannel_init(init_cmd, b, t);
/* whatever_init(p, b, t); */
return NULL;
}

View File

@@ -1034,10 +1034,95 @@ openchannel_init_dest(struct multifundchannel_destination *dest)
return send_outreq(req);
}
void openchannel_init(struct plugin *p, const char *b, const jsmntok_t *t)
static struct command_result *psbt_error(struct command *aux_cmd,
const char *methodname,
const char *buf,
const jsmntok_t *result,
struct channel_id *cid)
{
plugin_log(aux_cmd->plugin, LOG_UNUSUAL,
"Failed %s for waiting channel %s: %.*s",
methodname,
fmt_channel_id(tmpctx, cid),
json_tok_full_len(result),
json_tok_full(buf, result));
return aux_command_done(aux_cmd);
}
static struct command_result *sendpsbt_done(struct command *aux_cmd,
const char *methodname,
const char *buf,
const jsmntok_t *result,
struct channel_id *cid)
{
plugin_log(aux_cmd->plugin, LOG_INFORM,
"Signed and sent psbt for waiting channel %s",
fmt_channel_id(tmpctx, cid));
return aux_command_done(aux_cmd);
}
static struct command_result *signpsbt_done(struct command *aux_cmd,
const char *methodname,
const char *buf,
const jsmntok_t *result,
struct channel_id *cid)
{
const jsmntok_t *psbttok = json_get_member(buf, result, "signed_psbt");
struct wally_psbt *psbt = json_to_psbt(tmpctx, buf, psbttok);
struct out_req *req;
req = jsonrpc_request_start(aux_cmd, "sendpsbt",
sendpsbt_done, psbt_error,
cid);
json_add_psbt(req->js, "psbt", psbt);
return send_outreq(req);
}
/* If there are any channels with unsigned PSBTs in AWAITING_LOCKIN,
* sign them now (assume we crashed) */
static void list_awaiting_channels(struct command *init_cmd)
{
const char *buf;
size_t i;
const jsmntok_t *resp, *t, *channels;
resp = jsonrpc_request_sync(tmpctx, init_cmd,
"listpeerchannels",
NULL, &buf);
channels = json_get_member(buf, resp, "channels");
json_for_each_arr(i, t, channels) {
struct out_req *req;
const char *state;
struct channel_id cid;
struct command *aux_cmd;
struct wally_psbt *psbt;
if (json_scan(tmpctx, buf, t, "{state:%,channel_id:%,funding:{psbt:%}}",
JSON_SCAN_TAL(tmpctx, json_strdup, &state),
JSON_SCAN(json_tok_channel_id, &cid),
JSON_SCAN_TAL(tmpctx, json_to_psbt, &psbt)) != NULL)
continue;
if (!streq(state, "CHANNELD_AWAITING_LOCKIN")
&& !streq(state, "DUALOPEND_AWAITING_LOCKIN"))
continue;
/* Don't do this sync, as it can reasonably fail! */
aux_cmd = aux_command(init_cmd);
req = jsonrpc_request_start(aux_cmd, "signpsbt",
signpsbt_done, psbt_error,
tal_dup(aux_cmd, struct channel_id, &cid));
json_add_psbt(req->js, "psbt", psbt);
send_outreq(req);
}
}
void openchannel_init(struct command *init_cmd, const char *b, const jsmntok_t *t)
{
/* Initialize our list! */
list_head_init(&mfc_commands);
list_awaiting_channels(init_cmd);
}
const struct plugin_notification openchannel_notifs[] = {

View File

@@ -4,6 +4,7 @@
#include <ccan/tal/tal.h>
struct wally_psbt;
struct command;
extern const struct plugin_notification openchannel_notifs[];
extern const size_t num_openchannel_notifs;
@@ -15,7 +16,7 @@ void register_mfc(struct multifundchannel_command *mfc);
struct command_result *
openchannel_init_dest(struct multifundchannel_destination *dest);
void openchannel_init(struct plugin *p, const char *b,
void openchannel_init(struct command *init_cmd, const char *b,
const jsmntok_t *t);
struct command_result *

View File

@@ -2849,7 +2849,6 @@ def test_opening_crash(bitcoind, node_factory):
bitcoind.generate_block(1, wait_for_mempool=txid)
@pytest.mark.xfail(strict=True)
@pytest.mark.openchannel('v1')
def test_sendpsbt_crash(bitcoind, node_factory):
"""Stop sendpsbt, check it eventually opens"""
@@ -2866,3 +2865,5 @@ def test_sendpsbt_crash(bitcoind, node_factory):
del l1.daemon.opts['plugin']
l1.start()
bitcoind.generate_block(1, wait_for_mempool=1)
assert l1.daemon.is_in_log('Signed and sent psbt for waiting channel')