diff --git a/contrib/msggen/msggen/schema.json b/contrib/msggen/msggen/schema.json index 69c33acf4aa8..71ec0c3ec63a 100644 --- a/contrib/msggen/msggen/schema.json +++ b/contrib/msggen/msggen/schema.json @@ -13029,6 +13029,13 @@ "description": [ "Transaction to use for funding (does not need to be signed but must be otherwise complete)." ] + }, + "withhold": { + "type": "boolean", + "added": "v25.12", + "description": [ + "Mark this channel 'withheld' so we know we haven't broadcast the funding transaction. If the channel is closed before we call `sendpsbt` on this psbt, it will simply be closed immediately." + ] } } }, @@ -17285,6 +17292,8 @@ "total_htlcs_sent", "funding_txid", "funding_outnum", + "funding_psbt", + "funding_withheld", "leased", "final_to_us_msat", "min_to_us_msat", @@ -17430,6 +17439,20 @@ "The 0-based output number of the funding transaction which opens the channel." ] }, + "funding_psbt": { + "type": "string", + "added": "v25.12", + "description": [ + "The PSBT (may be non-final or unsigned) we should use to open the channel, if any" + ] + }, + "funding_withheld": { + "type": "boolean", + "added": "v25.12", + "description": [ + "True if we have not broadcast the funding transaction (see fundchannel_complete)." + ] + }, "leased": { "type": "boolean", "description": [ @@ -22987,6 +23010,7 @@ "type": "object", "additionalProperties": false, "required": [ + "psbt", "local_funds_msat", "remote_funds_msat" ], @@ -23020,6 +23044,20 @@ "description": [ "Amount we were paid by peer at open." ] + }, + "psbt": { + "type": "string", + "added": "v25.12", + "description": [ + "The PSBT (may be non-final or unsigned) we should use to open the channel, if any. This is initially from `fundchannel_complete`, but will be updated with if `sendpsbt` is called with an updated PSBT." + ] + }, + "withheld": { + "type": "boolean", + "added": "v25.12", + "description": [ + "True if `fundchannel_complete` told us it will not broadcast the funding transaction (so we know not to bother with any other onchain transactions in the case of this channel). This is set to false if `sendpsbt` is send on the above PSBT." + ] } } }, @@ -30987,7 +31025,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": [ diff --git a/contrib/pyln-client/pyln/client/lightning.py b/contrib/pyln-client/pyln/client/lightning.py index 1706a4727e3b..3ec01f64f76d 100644 --- a/contrib/pyln-client/pyln/client/lightning.py +++ b/contrib/pyln-client/pyln/client/lightning.py @@ -844,13 +844,14 @@ def fundchannel_cancel(self, node_id): } return self.call("fundchannel_cancel", payload) - def fundchannel_complete(self, node_id, psbt): + def fundchannel_complete(self, node_id, psbt, withhold=True): """ Complete channel establishment with {id}, using {psbt}. """ payload = { "id": node_id, "psbt": psbt, + "withhold": withhold, } return self.call("fundchannel_complete", payload) diff --git a/doc/schemas/fundchannel_complete.json b/doc/schemas/fundchannel_complete.json index d064026b1d6c..02921ea189bf 100644 --- a/doc/schemas/fundchannel_complete.json +++ b/doc/schemas/fundchannel_complete.json @@ -26,6 +26,13 @@ "description": [ "Transaction to use for funding (does not need to be signed but must be otherwise complete)." ] + }, + "withhold": { + "type": "boolean", + "added": "v25.12", + "description": [ + "Mark this channel 'withheld' so we know we haven't broadcast the funding transaction. If the channel is closed before we call `sendpsbt` on this psbt, it will simply be closed immediately." + ] } } }, diff --git a/doc/schemas/listclosedchannels.json b/doc/schemas/listclosedchannels.json index becf033e692d..637addb79b37 100644 --- a/doc/schemas/listclosedchannels.json +++ b/doc/schemas/listclosedchannels.json @@ -43,6 +43,8 @@ "total_htlcs_sent", "funding_txid", "funding_outnum", + "funding_psbt", + "funding_withheld", "leased", "final_to_us_msat", "min_to_us_msat", @@ -188,6 +190,20 @@ "The 0-based output number of the funding transaction which opens the channel." ] }, + "funding_psbt": { + "type": "string", + "added": "v25.12", + "description": [ + "The PSBT (may be non-final or unsigned) we should use to open the channel, if any" + ] + }, + "funding_withheld": { + "type": "boolean", + "added": "v25.12", + "description": [ + "True if we have not broadcast the funding transaction (see fundchannel_complete)." + ] + }, "leased": { "type": "boolean", "description": [ diff --git a/doc/schemas/listpeerchannels.json b/doc/schemas/listpeerchannels.json index 16844c0e7665..a3b57a90cbda 100644 --- a/doc/schemas/listpeerchannels.json +++ b/doc/schemas/listpeerchannels.json @@ -469,6 +469,7 @@ "type": "object", "additionalProperties": false, "required": [ + "psbt", "local_funds_msat", "remote_funds_msat" ], @@ -502,6 +503,20 @@ "description": [ "Amount we were paid by peer at open." ] + }, + "psbt": { + "type": "string", + "added": "v25.12", + "description": [ + "The PSBT (may be non-final or unsigned) we should use to open the channel, if any. This is initially from `fundchannel_complete`, but will be updated with if `sendpsbt` is called with an updated PSBT." + ] + }, + "withheld": { + "type": "boolean", + "added": "v25.12", + "description": [ + "True if `fundchannel_complete` told us it will not broadcast the funding transaction (so we know not to bother with any other onchain transactions in the case of this channel). This is set to false if `sendpsbt` is send on the above PSBT." + ] } } }, diff --git a/doc/schemas/sendpsbt.json b/doc/schemas/sendpsbt.json index fa1c07d77351..5409a3b9ad48 100644 --- a/doc/schemas/sendpsbt.json +++ b/doc/schemas/sendpsbt.json @@ -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": [ diff --git a/lightningd/bitcoind.h b/lightningd/bitcoind.h index cf92e907513d..870ffd118e0a 100644 --- a/lightningd/bitcoind.h +++ b/lightningd/bitcoind.h @@ -78,7 +78,7 @@ void bitcoind_sendrawtx_(const tal_t *ctx, const char *id_prefix TAKES, const char *hextx, bool allowhighfees, - void (*cb)(struct bitcoind *bitcoind, + void (*cb)(struct bitcoind *, bool success, const char *msg, void *), void *arg); #define bitcoind_sendrawtx(ctx, bitcoind_, id_prefix, hextx, allowhighfees, cb, arg) \ diff --git a/lightningd/channel.c b/lightningd/channel.c index 354249a4c24c..c7b1963affb8 100644 --- a/lightningd/channel.c +++ b/lightningd/channel.c @@ -427,6 +427,8 @@ struct channel *new_unsaved_channel(struct peer *peer, /* channel->channel_gossip gets populated once we know if it's public. */ channel->channel_gossip = NULL; channel->forgets = tal_arr(channel, struct command *, 0); + channel->funding_psbt = NULL; + channel->withheld = false; list_add_tail(&peer->channels, &channel->list); channel->rr_number = peer->ld->rr_counter++; tal_add_destructor(channel, destroy_channel); @@ -548,7 +550,9 @@ struct channel *new_channel(struct peer *peer, u64 dbid, struct peer_update *peer_update STEALS, u64 last_stable_connection, const struct channel_stats *stats, - struct channel_state_change **state_changes STEALS) + struct channel_state_change **state_changes STEALS, + const struct wally_psbt *funding_psbt STEALS, + bool withheld) { struct channel *channel = tal(peer->ld, struct channel); struct amount_msat htlc_min, htlc_max; @@ -748,7 +752,8 @@ struct channel *new_channel(struct peer *peer, u64 dbid, channel->error = towire_errorfmt(peer->ld, &channel->cid, "We can't be together anymore."); - + channel->funding_psbt = tal_steal(channel, funding_psbt); + channel->withheld = withheld; return channel; } @@ -1099,12 +1104,24 @@ static void channel_fail_perm(struct channel *channel, channel_set_owner(channel, NULL); - if (channel_state_wants_onchain_fail(channel->state)) + if (channel->withheld) { + channel_set_state(channel, + channel->state, + CLOSED, + reason, + why); + } else if (channel_state_wants_onchain_fail(channel->state)) { channel_set_state(channel, channel->state, AWAITING_UNILATERAL, reason, why); + } + + if (channel_state_open_uncommitted(channel->state)) { + delete_channel(channel, false); + return; + } /* Drop non-cooperatively (unilateral) to chain. If we detect * the close from the blockchain, then we can observe @@ -1112,8 +1129,6 @@ static void channel_fail_perm(struct channel *channel, * it doesn't stand a chance anyway. */ drop_to_chain(ld, channel, false, spent_by); - if (channel_state_open_uncommitted(channel->state)) - delete_channel(channel, false); } void channel_fail_permanent(struct channel *channel, diff --git a/lightningd/channel.h b/lightningd/channel.h index af2f18a54b8a..150c7d748a57 100644 --- a/lightningd/channel.h +++ b/lightningd/channel.h @@ -359,6 +359,12 @@ struct channel { /* Our change history. */ struct channel_state_change **state_changes; + + /* Unsigned PSBT if we initiated the open channel */ + const struct wally_psbt *funding_psbt; + + /* Are we not broadcasting the open tx? */ + bool withheld; }; /* Is channel owned (and should be talking to peer) */ @@ -444,7 +450,9 @@ struct channel *new_channel(struct peer *peer, u64 dbid, struct peer_update *peer_update STEALS, u64 last_stable_connection, const struct channel_stats *stats, - struct channel_state_change **state_changes STEALS); + struct channel_state_change **state_changes STEALS, + const struct wally_psbt *funding_psbt STEALS, + bool withheld); /* new_inflight - Create a new channel_inflight for a channel */ struct channel_inflight *new_inflight(struct channel *channel, diff --git a/lightningd/channel_control.c b/lightningd/channel_control.c index a72f959837ae..6ffe98f4d8f4 100644 --- a/lightningd/channel_control.c +++ b/lightningd/channel_control.c @@ -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) diff --git a/lightningd/channel_gossip.c b/lightningd/channel_gossip.c index d4153b207ad4..9587b4addb5c 100644 --- a/lightningd/channel_gossip.c +++ b/lightningd/channel_gossip.c @@ -95,6 +95,8 @@ static struct state_transition allowed_transitions[] = { "Channel usable (zeroconf) but no scid yet" }, { CGOSSIP_WAITING_FOR_SCID, CGOSSIP_CHANNEL_DEAD, "Zeroconf channel closed before funding tx mined" }, + { CGOSSIP_WAITING_FOR_SCID, CGOSSIP_CHANNEL_UNANNOUNCED_DYING, + "Zeroconf channel closing mutually before funding tx" }, { CGOSSIP_WAITING_FOR_USABLE, CGOSSIP_WAITING_FOR_MATCHING_PEER_SIGS, "Channel mined, but we haven't got matching announcment sigs from peer" }, { CGOSSIP_WAITING_FOR_USABLE, CGOSSIP_WAITING_FOR_ANNOUNCE_DEPTH, diff --git a/lightningd/closed_channel.c b/lightningd/closed_channel.c index dd85f4fc2542..1d88cebc5fad 100644 --- a/lightningd/closed_channel.c +++ b/lightningd/closed_channel.c @@ -64,6 +64,9 @@ static void json_add_closed_channel(struct json_stream *response, } else if (!amount_msat_is_zero(channel->push)) json_add_amount_msat(response, "funding_pushed_msat", channel->push); + if (channel->funding_psbt) + json_add_psbt(response, "funding_psbt", channel->funding_psbt); + json_add_bool(response, "funding_withheld", channel->withheld); json_add_amount_sat_msat(response, "total_msat", channel->funding_sats); json_add_amount_msat(response, "final_to_us_msat", channel->our_msat); diff --git a/lightningd/closed_channel.h b/lightningd/closed_channel.h index fc8382d0f142..68822bc507b2 100644 --- a/lightningd/closed_channel.h +++ b/lightningd/closed_channel.h @@ -30,6 +30,8 @@ struct closed_channel { u64 last_stable_connection; /* NULL for older closed channels */ const struct shachain *their_shachain; + const struct wally_psbt *funding_psbt; + bool withheld; }; static inline const struct channel_id *keyof_closed_channel(const struct closed_channel *cc) diff --git a/lightningd/closing_control.c b/lightningd/closing_control.c index 36fe90f696bb..8b761f27240d 100644 --- a/lightningd/closing_control.c +++ b/lightningd/closing_control.c @@ -54,13 +54,18 @@ static void resolve_one_close_command(struct close_command *cc, bool cooperative, const struct bitcoin_tx **close_txs) { - assert(tal_count(close_txs)); struct json_stream *result = json_stream_success(cc->cmd); - const struct bitcoin_tx *close_tx = close_txs[tal_count(close_txs) - 1]; + const struct bitcoin_tx *close_tx; - if (command_deprecated_out_ok(cc->cmd, "tx", "v24.11", "v25.12")) + /* Withheld funding channels can have no close_txs! */ + if (tal_count(close_txs) != 0) + close_tx = close_txs[tal_count(close_txs) - 1]; + else + close_tx = NULL; + + if (close_tx && command_deprecated_out_ok(cc->cmd, "tx", "v24.11", "v25.12")) json_add_tx(result, "tx", close_tx); - if (!invalid_last_tx(close_tx)) { + if (close_tx && !invalid_last_tx(close_tx)) { struct bitcoin_txid txid; bitcoin_txid(close_tx, &txid); if (command_deprecated_out_ok(cc->cmd, "txid", "v24.11", "v25.12")) diff --git a/lightningd/dual_open_control.c b/lightningd/dual_open_control.c index 3ad5cfb1dbb7..f5806a891f88 100644 --- a/lightningd/dual_open_control.c +++ b/lightningd/dual_open_control.c @@ -1521,6 +1521,8 @@ wallet_commit_channel(struct lightningd *ld, channel->lease_chan_max_ppt = lease_chan_max_ppt; channel->htlc_minimum_msat = channel_info->their_config.htlc_minimum; channel->htlc_maximum_msat = htlc_max_possible_send(channel); + /* Filled in when we have PSBT for inflight */ + channel->funding_psbt = NULL; /* Now we finally put it in the database. */ wallet_channel_insert(ld->wallet, channel); @@ -2793,6 +2795,11 @@ json_openchannel_signed(struct command *cmd, wallet_inflight_save(cmd->ld->wallet, inflight); watch_opening_inflight(cmd->ld, inflight); + /* Channel's funding psbt also updated now */ + tal_free(channel->funding_psbt); + channel->funding_psbt = clone_psbt(channel, inflight->funding_psbt); + wallet_channel_save(cmd->ld->wallet, channel); + /* Only after we've updated/saved our psbt do we check * for peer connected */ if (!channel->owner) diff --git a/lightningd/lightningd.c b/lightningd/lightningd.c index 1c3fb7e4d77a..e854d7efd7ee 100644 --- a/lightningd/lightningd.c +++ b/lightningd/lightningd.c @@ -1449,9 +1449,10 @@ int main(int argc, char *argv[]) plugin_hook_call_recover(ld, NULL, payload); } - /*~ If we have channels closing, make sure we re-xmit the last + /*~ If we have channels closing or opening, make sure we re-xmit the last * transaction, in case bitcoind lost it. */ db_begin_transaction(ld->wallet->db); + resend_opening_transactions(ld); resend_closing_transactions(ld); db_commit_transaction(ld->wallet->db); diff --git a/lightningd/opening_common.h b/lightningd/opening_common.h index 8b75b56a022f..855797ef6cef 100644 --- a/lightningd/opening_common.h +++ b/lightningd/opening_common.h @@ -103,6 +103,12 @@ struct funding_channel { /* Place to stash the per-peer-state while we wait * for them to get back to us with signatures */ struct peer_fd *peer_fd; + + /* Were we the one to publish the commitment/splicing tx? */ + const struct wally_psbt *funding_psbt; + + /* Were we told to withhold the commitment tx? */ + bool withheld; }; struct uncommitted_channel *new_uncommitted_channel(struct peer *peer); diff --git a/lightningd/opening_control.c b/lightningd/opening_control.c index 7c12a0ffd551..aca07139fba2 100644 --- a/lightningd/opening_control.c +++ b/lightningd/opening_control.c @@ -98,7 +98,9 @@ wallet_commit_channel(struct lightningd *ld, u32 feerate, const u8 *our_upfront_shutdown_script, const u8 *remote_upfront_shutdown_script, - const struct channel_type *type) + const struct channel_type *type, + const struct wally_psbt *funding_psbt, + bool withheld) { struct channel *channel; struct amount_msat our_msat; @@ -237,7 +239,9 @@ wallet_commit_channel(struct lightningd *ld, NULL, 0, &zero_channel_stats, - tal_arr(NULL, struct channel_state_change *, 0)); + tal_arr(NULL, struct channel_state_change *, 0), + funding_psbt, + withheld); /* Now we finally put it in the database. */ wallet_channel_insert(ld->wallet, channel); @@ -445,7 +449,9 @@ static void opening_funder_finished(struct subd *openingd, const u8 *resp, feerate, fc->our_upfront_shutdown_script, remote_upfront_shutdown_script, - type); + type, + fc->funding_psbt, + fc->withheld); if (!channel) { was_pending(command_fail(fc->cmd, LIGHTNINGD, "Key generation failure")); @@ -548,7 +554,9 @@ static void opening_fundee_finished(struct subd *openingd, feerate, local_upfront_shutdown_script, remote_upfront_shutdown_script, - type); + type, + NULL, + false); if (!channel) { uncommitted_channel_disconnect(uc, LOG_BROKEN, "Commit channel failed"); @@ -1017,10 +1025,12 @@ static struct command_result *json_fundchannel_complete(struct command *cmd, struct wally_psbt *funding_psbt; u32 *funding_txout_num = NULL; struct funding_channel *fc; + bool *withhold; if (!param_check(cmd, buffer, params, p_req("id", param_node_id, &id), p_req("psbt", param_psbt, &funding_psbt), + p_opt_def("withhold", param_bool, &withhold, false), NULL)) return command_param_failed(); @@ -1092,6 +1102,9 @@ static struct command_result *json_fundchannel_complete(struct command *cmd, if (command_check_only(cmd)) return command_check_done(cmd); + fc->funding_psbt = tal_steal(fc, funding_psbt); + fc->withheld = *withhold; + /* Set the cmd to this new cmd */ peer->uncommitted_channel->fc->cmd = cmd; msg = towire_openingd_funder_complete(NULL, @@ -1639,7 +1652,9 @@ static struct channel *stub_chan(struct command *cmd, NULL, 0, &zero_channel_stats, - tal_arr(NULL, struct channel_state_change *, 0)); + tal_arr(NULL, struct channel_state_change *, 0), + NULL, + false); /* We don't want to gossip about this, ever. */ channel->channel_gossip = tal_free(channel->channel_gossip); diff --git a/lightningd/peer_control.c b/lightningd/peer_control.c index 35b88089991f..d4f8d79ba1af 100644 --- a/lightningd/peer_control.c +++ b/lightningd/peer_control.c @@ -371,6 +371,17 @@ void drop_to_chain(struct lightningd *ld, struct channel *channel, struct channel_inflight *inflight; const char *cmd_id; + /* If we withheld the funding tx, we simply close */ + if (channel->withheld) { + log_info(channel->log, + "Withheld channel: not sending a close transaction"); + resolve_close_command(ld, channel, cooperative, + tal_arr(tmpctx, const struct bitcoin_tx *, 0)); + free_htlcs(ld, channel); + delete_channel(channel, false); + return; + } + /* If we're not already (e.g. close before channel fully open), * make sure we're watching for the funding spend */ if (!channel->funding_spend_watch) { @@ -501,6 +512,47 @@ void resend_closing_transactions(struct lightningd *ld) } } +static void resend_funding_done(struct bitcoind *bitcoind, + bool success, + const char *msg, + struct channel *channel) +{ + if (success) + log_info(channel->log, "Successfully rexmitted funding tx"); + else + log_unusual(channel->log, "Failed to re-transmit funding tx: %s", msg); +} + +void resend_opening_transactions(struct lightningd *ld) +{ + struct peer *peer; + struct channel *channel; + struct peer_node_id_map_iter it; + + for (peer = peer_node_id_map_first(ld->peers, &it); + peer; + peer = peer_node_id_map_next(ld->peers, &it)) { + list_for_each(&peer->channels, channel, list) { + struct wally_tx *wtx; + if (channel_state_uncommitted(channel->state)) + continue; + if (!channel->funding_psbt || channel->withheld) + continue; + if (channel->depth != 0) + continue; + wtx = psbt_final_tx(tmpctx, channel->funding_psbt); + if (!wtx) + continue; + bitcoind_sendrawtx(channel, + ld->topology->bitcoind, + NULL, + tal_hex(tmpctx, + linearize_wtx(tmpctx, wtx)), + false, resend_funding_done, channel); + } + } +} + void channel_errmsg(struct channel *channel, struct peer_fd *peer_fd, const char *desc, @@ -1137,6 +1189,9 @@ static void NON_NULL_ARGS(1, 2, 4, 5) json_add_channel(struct command *cmd, channel->push); } + if (channel->funding_psbt) + json_add_psbt(response, "psbt", channel->funding_psbt); + json_add_bool(response, "withheld", channel->withheld); json_object_end(response); if (!amount_sat_to_msat(&funding_msat, channel->funding_sats)) { diff --git a/lightningd/peer_control.h b/lightningd/peer_control.h index 2a28183fdace..d52c6e936986 100644 --- a/lightningd/peer_control.h +++ b/lightningd/peer_control.h @@ -119,6 +119,9 @@ void peer_set_dbid(struct peer *peer, u64 dbid); /* At startup, re-send any transactions we want bitcoind to have */ void resend_closing_transactions(struct lightningd *ld); +/* At startup, re-send any funding transactions we want bitcoind to have */ +void resend_opening_transactions(struct lightningd *ld); + /* Initiate the close of a channel, maybe broadcast. If we've seen a * unilateral close, pass it here (means we don't need to broadcast * our own, or any anchors). */ diff --git a/lightningd/test/run-find_my_abspath.c b/lightningd/test/run-find_my_abspath.c index 80702af24345..86668a342773 100644 --- a/lightningd/test/run-find_my_abspath.c +++ b/lightningd/test/run-find_my_abspath.c @@ -228,6 +228,9 @@ bool pubkey_from_node_id(struct pubkey *key UNNEEDED, const struct node_id *id U /* Generated stub for resend_closing_transactions */ void resend_closing_transactions(struct lightningd *ld UNNEEDED) { fprintf(stderr, "resend_closing_transactions called!\n"); abort(); } +/* Generated stub for resend_opening_transactions */ +void resend_opening_transactions(struct lightningd *ld UNNEEDED) +{ fprintf(stderr, "resend_opening_transactions called!\n"); abort(); } /* Generated stub for runes_early_init */ struct runes *runes_early_init(struct lightningd *ld UNNEEDED) { fprintf(stderr, "runes_early_init called!\n"); abort(); } diff --git a/lightningd/test/run-invoice-select-inchan.c b/lightningd/test/run-invoice-select-inchan.c index fc7c642496ab..b03e85240de4 100644 --- a/lightningd/test/run-invoice-select-inchan.c +++ b/lightningd/test/run-invoice-select-inchan.c @@ -26,6 +26,16 @@ void bitcoind_getutxout_(const tal_t *ctx UNNEEDED, void *) UNNEEDED, void *arg UNNEEDED) { fprintf(stderr, "bitcoind_getutxout_ called!\n"); abort(); } +/* Generated stub for bitcoind_sendrawtx_ */ +void bitcoind_sendrawtx_(const tal_t *ctx UNNEEDED, + struct bitcoind *bitcoind UNNEEDED, + const char *id_prefix TAKES UNNEEDED, + const char *hextx UNNEEDED, + bool allowhighfees UNNEEDED, + void (*cb)(struct bitcoind * UNNEEDED, + bool success UNNEEDED, const char *msg UNNEEDED, void *) UNNEEDED, + void *arg UNNEEDED) +{ fprintf(stderr, "bitcoind_sendrawtx_ called!\n"); abort(); } /* Generated stub for bolt11_decode */ struct bolt11 *bolt11_decode(const tal_t *ctx UNNEEDED, const char *str UNNEEDED, const struct feature_set *our_features UNNEEDED, @@ -305,6 +315,9 @@ void force_peer_disconnect(struct lightningd *ld UNNEEDED, const struct peer *peer UNNEEDED, const char *why UNNEEDED) { fprintf(stderr, "force_peer_disconnect called!\n"); abort(); } +/* Generated stub for free_htlcs */ +void free_htlcs(struct lightningd *ld UNNEEDED, const struct channel *channel UNNEEDED) +{ fprintf(stderr, "free_htlcs called!\n"); abort(); } /* Generated stub for fromwire_bigsize */ bigsize_t fromwire_bigsize(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) { fprintf(stderr, "fromwire_bigsize called!\n"); abort(); } @@ -573,6 +586,11 @@ void json_add_num(struct json_stream *result UNNEEDED, const char *fieldname UNN void json_add_preimage(struct json_stream *result UNNEEDED, const char *fieldname UNNEEDED, const struct preimage *preimage UNNEEDED) { fprintf(stderr, "json_add_preimage called!\n"); abort(); } +/* Generated stub for json_add_psbt */ +void json_add_psbt(struct json_stream *stream UNNEEDED, + const char *fieldname UNNEEDED, + const struct wally_psbt *psbt UNNEEDED) +{ fprintf(stderr, "json_add_psbt called!\n"); abort(); } /* Generated stub for json_add_s64 */ void json_add_s64(struct json_stream *result UNNEEDED, const char *fieldname UNNEEDED, int64_t value UNNEEDED) diff --git a/plugins/spender/main.c b/plugins/spender/main.c index 0f4151876ceb..a94e7859c520 100644 --- a/plugins/spender/main.c +++ b/plugins/spender/main.c @@ -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; } diff --git a/plugins/spender/openchannel.c b/plugins/spender/openchannel.c index 90c5cdb28a46..15a6c2ddb2e2 100644 --- a/plugins/spender/openchannel.c +++ b/plugins/spender/openchannel.c @@ -1033,10 +1033,100 @@ 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; + bool withheld; + + if (json_scan(tmpctx, buf, t, "{state:%,channel_id:%,funding:{withheld:%,psbt:%}}", + JSON_SCAN_TAL(tmpctx, json_strdup, &state), + JSON_SCAN(json_tok_channel_id, &cid), + JSON_SCAN(json_to_bool, &withheld), + JSON_SCAN_TAL(tmpctx, json_to_psbt, &psbt)) != NULL) + continue; + + if (!streq(state, "CHANNELD_AWAITING_LOCKIN") + && !streq(state, "DUALOPEND_AWAITING_LOCKIN")) + continue; + + if (withheld) + 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[] = { diff --git a/plugins/spender/openchannel.h b/plugins/spender/openchannel.h index bd2957b14095..3dfea2ca59ed 100644 --- a/plugins/spender/openchannel.h +++ b/plugins/spender/openchannel.h @@ -4,6 +4,7 @@ #include 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 * diff --git a/tests/plugins/stop_sendpsbt.py b/tests/plugins/stop_sendpsbt.py new file mode 100755 index 000000000000..bd7ac093b995 --- /dev/null +++ b/tests/plugins/stop_sendpsbt.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +""" +This plugin is used to shutdown a node before processing the sendpsbt command +""" +from pyln.client import Plugin +import os, signal + +plugin = Plugin() + + +@plugin.hook("rpc_command") +def on_rpc_command(plugin, rpc_command, **kwargs): + request = rpc_command + if request["method"] == "sendpsbt": + os.kill(os.getppid(), signal.SIGKILL) + + return {"result": "continue"} + + +plugin.run() diff --git a/tests/test_opening.py b/tests/test_opening.py index e533d1f873bf..1359b21bdc91 100644 --- a/tests/test_opening.py +++ b/tests/test_opening.py @@ -2825,3 +2825,113 @@ def test_opening_below_min_capacity_sat(bitcoind, node_factory): # But we shouldn't have bothered l2 assert not l2.daemon.is_in_log('peer_in WIRE_ERROR') + + +@pytest.mark.openchannel('v1') +@pytest.mark.openchannel('v2') +def test_opening_crash(bitcoind, node_factory): + """Stop transmission of initial funding tx, check it eventually opens""" + l1, l2 = node_factory.get_nodes(2) + + def censoring_sendrawtx(r): + return {'id': r['id'], 'result': {}} + + l1.daemon.rpcproxy.mock_rpc('sendrawtransaction', censoring_sendrawtx) + l2.daemon.rpcproxy.mock_rpc('sendrawtransaction', censoring_sendrawtx) + l1.fundwallet(3_000_000) + l1.connect(l2) + txid = l1.rpc.fundchannel(l2.info['id'], "2000000sat")['txid'] + + l1.stop() + l1.daemon.rpcproxy.mock_rpc('sendrawtransaction', None) + l1.start() + + bitcoind.generate_block(1, wait_for_mempool=txid) + + +@pytest.mark.openchannel('v1') +@pytest.mark.openchannel('v2') +def test_sendpsbt_crash(bitcoind, node_factory): + """Stop sendpsbt, check it eventually opens""" + plugin_path = Path(__file__).parent / "plugins" / "stop_sendpsbt.py" + l1, l2 = node_factory.get_nodes(2, opts=[{"plugin": plugin_path, 'may_fail': True}, {}]) + + l1.fundwallet(3_000_000) + l1.connect(l2) + + # signpsbt kills l1. + with pytest.raises(RpcError, match=r'Connection to RPC server lost.'): + l1.rpc.fundchannel(l2.info['id'], "2000000sat") + + 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') + + +@pytest.mark.parametrize("stay_withheld", [True, False]) +@pytest.mark.parametrize("mutual_close", [True, False]) +def test_zeroconf_withhold(node_factory, bitcoind, stay_withheld, mutual_close): + plugin_path = Path(__file__).parent / "plugins" / "zeroconf-selective.py" + + l1, l2 = node_factory.get_nodes(2, opts=[{'may_reconnect': True}, + { + 'plugin': str(plugin_path), + 'zeroconf_allow': '0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518', + 'may_reconnect': True + }]) + # Try to open a mindepth=0 channel + l1.fundwallet(10**7) + + l1.connect(l2) + amount = 1000000 + funding_addr = l1.rpc.fundchannel_start(l2.info['id'], f"{amount}sat", mindepth=0)['funding_address'] + + # Create the funding transaction + psbt = l1.rpc.fundpsbt(amount, "1000perkw", 1000, excess_as_change=True)['psbt'] + psbt = l1.rpc.addpsbtoutput(1000000, psbt, destination=funding_addr)['psbt'] + + # Be sure fundchannel_complete is successful + assert l1.rpc.fundchannel_complete(l2.info['id'], psbt, withhold=True)['commitments_secured'] + + # It's withheld. + assert only_one(l1.rpc.listpeerchannels()['channels'])['funding']['withheld'] is True + + # We can use the channel. + l1.rpc.xpay(l2.rpc.invoice(100, "test_zeroconf_withhold", "test_zeroconf_withhold")['bolt11']) + + # But mempool is empty! No funding tx! + assert bitcoind.rpc.getrawmempool() == [] + + # Restarting doesn't make it transmit! + l1.restart() + assert bitcoind.rpc.getrawmempool() == [] + + if mutual_close: + l1.connect(l2) + + if not stay_withheld: + # sendpsbt marks it as no longer withheld. + l1.rpc.sendpsbt(l1.rpc.signpsbt(psbt)['signed_psbt']) + assert only_one(l1.rpc.listpeerchannels()['channels'])['funding']['withheld'] is False + assert l1.daemon.is_in_log(r'Funding PSBT sent, and stored for rexmit \(was withheld\)') + wait_for(lambda: len(bitcoind.rpc.getrawmempool()) == 1) + + ret = l1.rpc.close(l2.info['id'], unilateraltimeout=10) + if stay_withheld: + assert ret['txs'] == [] + assert ret['txids'] == [] + assert bitcoind.rpc.getrawmempool() == [] + else: + assert len(ret['txs']) == 1 + assert len(ret['txids']) == 1 + wait_for(lambda: len(bitcoind.rpc.getrawmempool()) == 2) + + # If withheld, it's moved to closed immediately. + if stay_withheld: + assert l1.rpc.listpeerchannels()['channels'] == [] + assert only_one(l1.rpc.listclosedchannels()['closedchannels'])['funding_withheld'] is True + else: + wait_for(lambda: only_one(l1.rpc.listpeerchannels()['channels'])['state'] == 'CLOSINGD_COMPLETE') + diff --git a/wallet/db.c b/wallet/db.c index 6057952dd9c1..1acd8b6c49c1 100644 --- a/wallet/db.c +++ b/wallet/db.c @@ -1093,6 +1093,7 @@ static struct migration dbmigrations[] = { /* We do a lookup before each append, to avoid duplicates */ {SQL("CREATE INDEX chain_moves_utxo_idx ON chain_moves (utxo)"), NULL}, {NULL, migrate_from_account_db}, + {SQL("ALTER TABLE channels ADD withheld INTEGER DEFAULT 0;"), NULL}, }; /** diff --git a/wallet/test/run-db.c b/wallet/test/run-db.c index 106cd38026ae..e462c1f2c0ac 100644 --- a/wallet/test/run-db.c +++ b/wallet/test/run-db.c @@ -254,7 +254,9 @@ struct channel *new_channel(struct peer *peer UNNEEDED, u64 dbid UNNEEDED, struct peer_update *peer_update STEALS UNNEEDED, u64 last_stable_connection UNNEEDED, const struct channel_stats *stats UNNEEDED, - struct channel_state_change **state_changes STEALS UNNEEDED) + struct channel_state_change **state_changes STEALS UNNEEDED, + const struct wally_psbt *funding_psbt STEALS UNNEEDED, + bool withheld UNNEEDED) { fprintf(stderr, "new_channel called!\n"); abort(); } /* Generated stub for new_channel_coin_mvt_general */ struct channel_coin_mvt *new_channel_coin_mvt_general(const tal_t *ctx UNNEEDED, diff --git a/wallet/test/run-wallet.c b/wallet/test/run-wallet.c index c1cd3b066f25..6f3eec098e39 100644 --- a/wallet/test/run-wallet.c +++ b/wallet/test/run-wallet.c @@ -66,6 +66,16 @@ void bitcoind_getutxout_(const tal_t *ctx UNNEEDED, void *) UNNEEDED, void *arg UNNEEDED) { fprintf(stderr, "bitcoind_getutxout_ called!\n"); abort(); } +/* Generated stub for bitcoind_sendrawtx_ */ +void bitcoind_sendrawtx_(const tal_t *ctx UNNEEDED, + struct bitcoind *bitcoind UNNEEDED, + const char *id_prefix TAKES UNNEEDED, + const char *hextx UNNEEDED, + bool allowhighfees UNNEEDED, + void (*cb)(struct bitcoind * UNNEEDED, + bool success UNNEEDED, const char *msg UNNEEDED, void *) UNNEEDED, + void *arg UNNEEDED) +{ fprintf(stderr, "bitcoind_sendrawtx_ called!\n"); abort(); } /* Generated stub for blinding_hash_e_and_ss */ void blinding_hash_e_and_ss(const struct pubkey *e UNNEEDED, const struct secret *ss UNNEEDED, @@ -505,6 +515,11 @@ void json_add_num(struct json_stream *result UNNEEDED, const char *fieldname UNN void json_add_preimage(struct json_stream *result UNNEEDED, const char *fieldname UNNEEDED, const struct preimage *preimage UNNEEDED) { fprintf(stderr, "json_add_preimage called!\n"); abort(); } +/* Generated stub for json_add_psbt */ +void json_add_psbt(struct json_stream *stream UNNEEDED, + const char *fieldname UNNEEDED, + const struct wally_psbt *psbt UNNEEDED) +{ fprintf(stderr, "json_add_psbt called!\n"); abort(); } /* Generated stub for json_add_pubkey */ void json_add_pubkey(struct json_stream *response UNNEEDED, const char *fieldname UNNEEDED, @@ -2114,7 +2129,9 @@ static bool test_channel_inflight_crud(struct lightningd *ld, const tal_t *ctx) NULL, 0, stats, - tal_arr(NULL, struct channel_state_change *, 0)); + tal_arr(NULL, struct channel_state_change *, 0), + NULL, + false); db_begin_transaction(w->db); CHECK(!wallet_err); wallet_channel_insert(w, chan); diff --git a/wallet/wallet.c b/wallet/wallet.c index ddc933d70ced..90dab307a72d 100644 --- a/wallet/wallet.c +++ b/wallet/wallet.c @@ -1844,6 +1844,7 @@ static struct channel *wallet_stmt2channel(struct wallet *w, struct db_stmt *stm struct peer_update *remote_update; struct channel_stats stats; struct channel_state_change **state_changes; + struct wally_psbt *funding_psbt; peer_dbid = db_col_u64(stmt, "peer_id"); peer = find_peer_by_dbid(w->ld, peer_dbid); @@ -2048,6 +2049,11 @@ static struct channel *wallet_stmt2channel(struct wallet *w, struct db_stmt *stm &stats.out_msatoshi_fulfilled, AMOUNT_MSAT(0)); + if (!db_col_is_null(stmt, "funding_psbt")) + funding_psbt = db_col_psbt(tmpctx, stmt, "funding_psbt"); + else + funding_psbt = NULL; + /* Stolen by new_channel */ state_changes = wallet_state_change_get(NULL, w, db_col_u64(stmt, "id")); chan = new_channel(peer, db_col_u64(stmt, "id"), @@ -2115,7 +2121,9 @@ static struct channel *wallet_stmt2channel(struct wallet *w, struct db_stmt *stm remote_update, db_col_u64(stmt, "last_stable_connection"), &stats, - state_changes); + state_changes, + funding_psbt, + db_col_int(stmt, "withheld")); if (!wallet_channel_load_inflights(w, chan)) { tal_free(chan); @@ -2169,6 +2177,11 @@ static struct closed_channel *wallet_stmt2closed_channel(const tal_t *ctx, cc->their_shachain = tal_dup(cc, struct shachain, &wshachain.chain); else cc->their_shachain = NULL; + if (!db_col_is_null(stmt, "funding_psbt")) + cc->funding_psbt = db_col_psbt(cc, stmt, "funding_psbt"); + else + cc->funding_psbt = NULL; + cc->withheld = db_col_int(stmt, "withheld"); return cc; } @@ -2204,6 +2217,8 @@ void wallet_load_closed_channels(struct wallet *w, ", lease_commit_sig" ", last_stable_connection" ", shachain_remote_id" + ", funding_psbt" + ", withheld" " FROM channels" " LEFT JOIN peers p ON p.id = peer_id" " WHERE state = ?;")); @@ -2249,6 +2264,8 @@ void wallet_load_one_closed_channel(struct wallet *w, ", lease_commit_sig" ", last_stable_connection" ", shachain_remote_id" + ", funding_psbt" + ", withheld" " FROM channels" " LEFT JOIN peers p ON p.id = peer_id" " WHERE channels.id = ?;")); @@ -2368,6 +2385,8 @@ static bool wallet_channels_load_active(struct wallet *w) ", out_msatoshi_offered" ", out_msatoshi_fulfilled" ", close_attempt_height" + ", funding_psbt" + ", withheld" " FROM channels" " WHERE state != ?;")); //? 0 db_bind_int(stmt, CLOSED); @@ -2619,7 +2638,9 @@ void wallet_channel_save(struct wallet *w, struct channel *chan) " remote_htlc_maximum_msat=?," " last_stable_connection=?," " require_confirm_inputs_remote=?," - " close_attempt_height=?" + " close_attempt_height=?," + " funding_psbt=?," + " withheld=?" " WHERE id=?")); db_bind_u64(stmt, chan->their_shachain.id); if (chan->scid) @@ -2717,6 +2738,11 @@ void wallet_channel_save(struct wallet *w, struct channel *chan) db_bind_int(stmt, chan->req_confirmed_ins[REMOTE]); db_bind_int(stmt, chan->close_attempt_height); + if (chan->funding_psbt) + db_bind_psbt(stmt, chan->funding_psbt); + else + db_bind_null(stmt); + db_bind_int(stmt, chan->withheld); db_bind_u64(stmt, chan->dbid); db_exec_prepared_v2(take(stmt)); diff --git a/wallet/walletrpc.c b/wallet/walletrpc.c index 677db8bab66f..8d77b25eb17d 100644 --- a/wallet/walletrpc.c +++ b/wallet/walletrpc.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -1035,6 +1036,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, @@ -1045,6 +1125,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), @@ -1079,6 +1161,35 @@ 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) { + bool was_withheld; + + 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); + was_withheld = c->withheld; + c->withheld = false; + wallet_channel_save(ld->wallet, c); + log_info(c->log, + "Funding PSBT sent, and stored for rexmit%s", + was_withheld ? " (was withheld)" : ""); + } + } + 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),