sendpsbt: update channel psbts if this is a channel PSBT.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell
2025-11-18 14:37:04 +10:30
parent 5cbab33dc4
commit ae9ecba375
4 changed files with 113 additions and 2 deletions

View File

@@ -32300,7 +32300,7 @@
"rpc": "sendpsbt",
"title": "Command to finalize, extract and send a partially signed bitcoin transaction (PSBT).",
"description": [
"The **sendpsbt** is a low-level RPC command which sends a fully-signed PSBT."
"The **sendpsbt** is a low-level RPC command which sends a fully-signed PSBT. If the PSBT is the same one promised by a channel (via fundchannel_complete) it will also be associated with that channel and re-transmitted if necessary on restart."
],
"request": {
"required": [

View File

@@ -4,7 +4,7 @@
"rpc": "sendpsbt",
"title": "Command to finalize, extract and send a partially signed bitcoin transaction (PSBT).",
"description": [
"The **sendpsbt** is a low-level RPC command which sends a fully-signed PSBT."
"The **sendpsbt** is a low-level RPC command which sends a fully-signed PSBT. If the PSBT is the same one promised by a channel (via fundchannel_complete) it will also be associated with that channel and re-transmitted if necessary on restart."
],
"request": {
"required": [

View File

@@ -2405,6 +2405,11 @@ static struct command_result *single_splice_signed(struct command *cmd,
SPLICE_INPUT_ERROR,
"Splice failed to convert to v2");
/* Update "funding" psbt now */
tal_free(channel->funding_psbt);
channel->funding_psbt = clone_psbt(channel, psbt);
wallet_channel_save(cmd->ld->wallet, channel);
msg = towire_channeld_splice_signed(tmpctx, psbt, sign_first);
subd_send_msg(channel->owner, take(msg));
if (success)

View File

@@ -1,5 +1,6 @@
#include "config.h"
#include <bitcoin/script.h>
#include <ccan/mem/mem.h>
#include <common/addr.h>
#include <common/base64.h>
#include <common/bech32.h>
@@ -1065,6 +1066,85 @@ static const struct json_command dev_finalizepsbt_command = {
};
AUTODATA(json_command, &dev_finalizepsbt_command);
/* Yuck. */
static const u8 *psbt_input_txid(const struct wally_psbt *psbt, size_t index)
{
if (psbt->version == WALLY_PSBT_VERSION_0)
return psbt->tx->inputs[index].txhash;
return psbt->inputs[index].txhash;
}
static u32 psbt_input_index(const struct wally_psbt *psbt, size_t index)
{
if (psbt->version == WALLY_PSBT_VERSION_0)
return psbt->tx->inputs[index].index;
return psbt->inputs[index].index;
}
static u32 psbt_input_sequence(const struct wally_psbt *psbt, size_t index)
{
if (psbt->version == WALLY_PSBT_VERSION_0)
return psbt->tx->inputs[index].sequence;
return psbt->inputs[index].sequence;
}
static u64 psbt_output_amount(const struct wally_psbt *psbt, size_t index)
{
if (psbt->version == WALLY_PSBT_VERSION_0)
return psbt->tx->outputs[index].satoshi;
return psbt->outputs[index].amount;
}
static size_t psbt_output_scriptlen(const struct wally_psbt *psbt, size_t index)
{
if (psbt->version == WALLY_PSBT_VERSION_0)
return psbt->tx->outputs[index].script_len;
return psbt->outputs[index].script_len;
}
static const u8 *psbt_output_script(const struct wally_psbt *psbt, size_t index)
{
if (psbt->version == WALLY_PSBT_VERSION_0)
return psbt->tx->outputs[index].script;
return psbt->outputs[index].script;
}
/* We consider two PSBTs *equivalent* if they have the same inputs and outputs */
static bool psbt_equivalent(const struct wally_psbt *a,
const struct wally_psbt *b)
{
if (a->num_inputs != b->num_inputs)
return false;
if (a->num_outputs != b->num_outputs)
return false;
for (size_t i = 0; i < a->num_inputs; i++) {
if (!memeq(psbt_input_txid(a, i), WALLY_TXHASH_LEN,
psbt_input_txid(b, i), WALLY_TXHASH_LEN))
return false;
if (psbt_input_index(a, i) != psbt_input_index(b, i))
return false;
if (psbt_input_sequence(a, i) != psbt_input_sequence(b, i))
return false;
}
for (size_t i = 0; i < a->num_outputs; i++) {
size_t a_scriptlen, b_scriptlen;
if (psbt_output_amount(a, i) != psbt_output_amount(b, i))
return false;
a_scriptlen = psbt_output_scriptlen(a, i);
b_scriptlen = psbt_output_scriptlen(b, i);
if (!memeq(psbt_output_script(a, i), a_scriptlen,
psbt_output_script(b, i), b_scriptlen))
return false;
}
return true;
}
static struct command_result *json_sendpsbt(struct command *cmd,
const char *buffer,
const jsmntok_t *obj,
@@ -1075,6 +1155,8 @@ static struct command_result *json_sendpsbt(struct command *cmd,
struct wally_psbt *psbt;
struct lightningd *ld = cmd->ld;
u32 *reserve_blocks;
struct peer *p;
struct peer_node_id_map_iter it;
if (!param_check(cmd, buffer, params,
p_req("psbt", param_psbt, &psbt),
@@ -1109,6 +1191,30 @@ static struct command_result *json_sendpsbt(struct command *cmd,
if (command_check_only(cmd))
return command_check_done(cmd);
/* If this corresponds to one or more channels' PSBT, upgrade
* those to signed versions! */
for (p = peer_node_id_map_first(ld->peers, &it);
p;
p = peer_node_id_map_next(ld->peers, &it)) {
struct channel *c;
list_for_each(&p->channels, c, list) {
if (!c->funding_psbt)
continue;
if (psbt_is_finalized(c->funding_psbt))
continue;
if (!psbt_equivalent(psbt, c->funding_psbt))
continue;
/* Found one! */
tal_free(c->funding_psbt);
c->funding_psbt = clone_psbt(c, sending->psbt);
wallet_channel_save(ld->wallet, c);
log_info(c->log,
"Funding PSBT sent, and stored for rexmit");
}
}
for (size_t i = 0; i < tal_count(sending->utxos); i++) {
if (!wallet_reserve_utxo(ld->wallet, sending->utxos[i],
get_block_height(ld->topology),