From 8858ae4f3d55e870f5f57b8000357fd3e09def15 Mon Sep 17 00:00:00 2001 From: niftynei Date: Thu, 10 Sep 2020 14:43:40 -0500 Subject: [PATCH] df-open: commands to update a PSBT or submit a signed PSBT `openchannel_signed` and `openchannel_update` which allow a user to continue a openchannel or kick off the completion of a openchannel. `openchannel_update` should be called until it returns with `commitments_secured`. --- contrib/pyln-client/pyln/client/lightning.py | 30 ++++ doc/Makefile | 4 +- doc/index.rst | 2 + doc/lightning-openchannel_signed.7 | 68 +++++++++ doc/lightning-openchannel_signed.7.md | 60 ++++++++ doc/lightning-openchannel_update.7 | 66 +++++++++ doc/lightning-openchannel_update.7.md | 59 ++++++++ lightningd/channel.h | 8 + lightningd/dual_open_control.c | 145 +++++++++++++++++++ lightningd/opening_common.h | 4 + 10 files changed, 445 insertions(+), 1 deletion(-) create mode 100644 doc/lightning-openchannel_signed.7 create mode 100644 doc/lightning-openchannel_signed.7.md create mode 100644 doc/lightning-openchannel_update.7 create mode 100644 doc/lightning-openchannel_update.7.md diff --git a/contrib/pyln-client/pyln/client/lightning.py b/contrib/pyln-client/pyln/client/lightning.py index 3afe2d3f8..7028c412e 100644 --- a/contrib/pyln-client/pyln/client/lightning.py +++ b/contrib/pyln-client/pyln/client/lightning.py @@ -957,6 +957,36 @@ class LightningRpc(UnixDomainSocketRpc): } return self.call("pay", payload) + def openchannel_init(self, node_id, channel_amount, psbt, feerate=None, funding_feerate=None, announce=True, close_to=None, *args, **kwargs): + """Initiate an openchannel with a peer """ + payload = { + "id": node_id, + "amount": channel_amount, + "initialpsbt": psbt, + "commitment_feerate": feerate, + "funding_feerate": funding_feerate, + "announce": announce, + "close_to": close_to, + } + return self.call("openchannel_init", payload) + + def openchannel_signed(self, channel_id, signed_psbt, *args, **kwargs): + """ Send the funding transaction signatures to the peer, finish + the channel open """ + payload = { + "channel_id": channel_id, + "signed_psbt": signed_psbt, + } + return self.call("openchannel_signed", payload) + + def openchannel_update(self, channel_id, psbt, *args, **kwargs): + """Update an openchannel with a peer """ + payload = { + "channel_id": channel_id, + "psbt": psbt, + } + return self.call("openchannel_update", payload) + def paystatus(self, bolt11=None): """Detail status of attempts to pay {bolt11} or any.""" payload = { diff --git a/doc/Makefile b/doc/Makefile index 19fe2ddae..db06325b5 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -41,6 +41,8 @@ MANPAGES := doc/lightning-cli.1 \ doc/lightning-multiwithdraw.7 \ doc/lightning-newaddr.7 \ doc/lightning-openchannel_init.7 \ + doc/lightning-openchannel_signed.7 \ + doc/lightning-openchannel_update.7 \ doc/lightning-pay.7 \ doc/lightning-plugin.7 \ doc/lightning-reserveinputs.7 \ @@ -67,7 +69,7 @@ MANPAGES := doc/lightning-cli.1 \ doc/lightning-listnodes.7 \ doc/lightning-listconfigs.7 \ doc/lightning-help.7 \ - doc/lightning-getlog.7 + doc/lightning-getlog.7 doc-all: $(MANPAGES) doc/index.rst diff --git a/doc/index.rst b/doc/index.rst index 523dfe799..d234a6560 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -69,6 +69,8 @@ c-lightning Documentation lightning-multiwithdraw lightning-newaddr lightning-openchannel_init + lightning-openchannel_signed + lightning-openchannel_update lightning-pay lightning-ping lightning-plugin diff --git a/doc/lightning-openchannel_signed.7 b/doc/lightning-openchannel_signed.7 new file mode 100644 index 000000000..7e5d4c7b7 --- /dev/null +++ b/doc/lightning-openchannel_signed.7 @@ -0,0 +1,68 @@ +.TH "LIGHTNING-OPENCHANNEL_SIGNED" "7" "" "" "lightning-openchannel_signed" +.SH NAME +lightning-openchannel_signed - Command to conclude a channel open +.SH SYNOPSIS + +\fBopenchannel_signed\fR \fIid\fR \fIsigned_psbt\fR + +.SH DESCRIPTION + +\fBopenchannel_signed\fR is a low level RPC command which concludes a channel +open with the specified peer\. It uses the v2 openchannel protocol, which +allows for interactive transaction construction\. + + +This command should be called after \fBopenchannel_update\fR returns +\fIcommitments_secured\fR \fBtrue\fR\. + + +This command will broadcast the finalized funding transaction, +if we receive valid signatures from the peer\. + + +\fIid\fR is the node id of the remote peer\. + + +\fIsigned_psbt\fR is the PSBT returned from \fBopenchannel_update\fR (where +\fIcommitments_secured\fR was true) with partial signatures or finalized +witness stacks included for every input that we contributed to the +PSBT\. + +.SH RETURN VALUE + +On success, returns the \fIchannel_id\fR for this channel; hex \fItx\fR of the +published funding transaction; and \fItxid\fR of the funding transaction\. + + +On error, the returned object will contain \fBcode\fR and \fBmessage\fR properties, +with \fBcode\fR being one of the following: + +.RS +.IP \[bu] +-32602: If the given parameters are wrong\. +.IP \[bu] +-1: Catchall nonspecific error\. +.IP \[bu] +303: Funding transaction broadcast failed\. +.IP \[bu] +306: Unknown peer id\. +.IP \[bu] +309: PSBT missing required fields\. + +.RE +.SH SEE ALSO + +lightning-openchannel_\fBupdate\fR(7), lightning-openchannel_\fBsigned\fR(7), +lightning-fundchannel_\fBstart\fR(7), lightning-fundchannel_\fBcomplete\fR(7), +\fBlightning-fundchannel\fR(7), \fBlightning-fundpsbt\fR(7), \fBlightning-utxopsbt\fR(7), +\fBlightning-multifundchannel\fR(7) + +.SH AUTHOR + +@niftynei \fI is mainly responsible\. + +.SH RESOURCES + +Main web site: \fIhttps://github.com/ElementsProject/lightning\fR + +\" SHA256STAMP:68fb78430a5ee3707fdb1324ba46373dd3dfaf325fe4e1bd3dcdc1589d80435c diff --git a/doc/lightning-openchannel_signed.7.md b/doc/lightning-openchannel_signed.7.md new file mode 100644 index 000000000..e975a3b2e --- /dev/null +++ b/doc/lightning-openchannel_signed.7.md @@ -0,0 +1,60 @@ +lightning-openchannel\_signed -- Command to conclude a channel open +=================================================================== + +SYNOPSIS +-------- + +**openchannel_signed** *id* *signed_psbt* + +DESCRIPTION +----------- + +`openchannel_signed` is a low level RPC command which concludes a channel +open with the specified peer. It uses the v2 openchannel protocol, which +allows for interactive transaction construction. + +This command should be called after `openchannel_update` returns +*commitments_secured* `true`. + +This command will broadcast the finalized funding transaction, +if we receive valid signatures from the peer. + +*id* is the node id of the remote peer. + +*signed_psbt* is the PSBT returned from `openchannel_update` (where +*commitments_secured* was true) with partial signatures or finalized +witness stacks included for every input that we contributed to the +PSBT. + +RETURN VALUE +------------ + +On success, returns the *channel_id* for this channel; hex *tx* of the +published funding transaction; and *txid* of the funding transaction. + +On error, the returned object will contain `code` and `message` properties, +with `code` being one of the following: + +- -32602: If the given parameters are wrong. +- -1: Catchall nonspecific error. +- 303: Funding transaction broadcast failed. +- 306: Unknown peer id. +- 309: PSBT missing required fields. + +SEE ALSO +-------- + +lightning-openchannel\_update(7), lightning-openchannel\_signed(7), +lightning-fundchannel\_start(7), lightning-fundchannel\_complete(7), +lightning-fundchannel(7), lightning-fundpsbt(7), lightning-utxopsbt(7), +lightning-multifundchannel(7) + +AUTHOR +------ + +@niftynei <> is mainly responsible. + +RESOURCES +--------- + +Main web site: diff --git a/doc/lightning-openchannel_update.7 b/doc/lightning-openchannel_update.7 new file mode 100644 index 000000000..456033fbe --- /dev/null +++ b/doc/lightning-openchannel_update.7 @@ -0,0 +1,66 @@ +.TH "LIGHTNING-OPENCHANNEL_UPDATE" "7" "" "" "lightning-openchannel_update" +.SH NAME +lightning-openchannel_update - Command to update a collab channel open +.SH SYNOPSIS + +\fBopenchannel_update\fR \fIid\fR \fIpsbt\fR + +.SH DESCRIPTION + +\fBopenchannel_update\fR is a low level RPC command which continues an open +channel with peer, as specified by \fIid\fR\. An updated \fIpsbt\fR is passed in; any +changes from the PSBT last returned (either from \fBopenchannel_init\fR or +a previous call to \fBopenchannel_update\fR) will be communicated to the peer\. + + +Must be called after \fBopenchannel_init\fR and before \fBopenchannel_signed\fR\. + + +Must be called until \fIcommitments_secured\fR is returned as true, at which point +\fBopenchannel_signed\fR should be called with a signed version of the PSBT +returned by the last call to \fBopenchannel_update\fR\. + + +\fIid\fR is the node id of the remote peer\. + + +\fIpsbt\fR is the updated PSBT to be sent to the peer\. May be identical to +the PSBT last returned by either \fBopenchannel_init\fR or \fBopenchannel_update\fR\. + +.SH RETURN VALUE + +On success, returns the \fIchannel_id\fR for this channel; an updated, potentially +complete \fIpsbt\fR for this channel's funding transaction; and the flag +\fIcommitments_secured\fR, which indicates the completeness of the returned \fIpsbt\fR\. +If \fIcommitments_secured\fR is true, caller should proceed with signing the +returned PSBT and calling \fBopenchannel_signed\fR to complete the channel open\. + +.RS +.IP \[bu] +-32602: If the given parameters are wrong\. +.IP \[bu] +-1: Catchall nonspecific error\. +.IP \[bu] +305: Peer is not connected\. +.IP \[bu] +306: Unknown peer id\. +.IP \[bu] +309: PSBT missing required fields + +.RE +.SH SEE ALSO + +lightning-openchannel_\fBupdate\fR(7), lightning-openchannel_\fBsigned\fR(7), +lightning-fundchannel_\fBstart\fR(7), lightning-fundchannel_\fBcomplete\fR(7), +\fBlightning-fundchannel\fR(7), \fBlightning-fundpsbt\fR(7), \fBlightning-utxopsbt\fR(7), +\fBlightning-multifundchannel\fR(7) + +.SH AUTHOR + +@niftynei \fI is mainly responsible\. + +.SH RESOURCES + +Main web site: \fIhttps://github.com/ElementsProject/lightning\fR + +\" SHA256STAMP:dcf253e7b1658e71a9721fccee76a6bb07af7579ca4b0413428d2a1e8c9613bc diff --git a/doc/lightning-openchannel_update.7.md b/doc/lightning-openchannel_update.7.md new file mode 100644 index 000000000..0b0546a56 --- /dev/null +++ b/doc/lightning-openchannel_update.7.md @@ -0,0 +1,59 @@ +lightning-openchannel\_update -- Command to update a collab channel open +======================================================================== + +SYNOPSIS +-------- + +**openchannel_update** *id* *psbt* + +DESCRIPTION +----------- + +`openchannel_update` is a low level RPC command which continues an open +channel with peer, as specified by *id*. An updated *psbt* is passed in; any +changes from the PSBT last returned (either from `openchannel_init` or +a previous call to `openchannel_update`) will be communicated to the peer. + +Must be called after `openchannel_init` and before `openchannel_signed`. + +Must be called until *commitments_secured* is returned as true, at which point +`openchannel_signed` should be called with a signed version of the PSBT +returned by the last call to `openchannel_update`. + +*id* is the node id of the remote peer. + +*psbt* is the updated PSBT to be sent to the peer. May be identical to +the PSBT last returned by either `openchannel_init` or `openchannel_update`. + +RETURN VALUE +------------ + +On success, returns the *channel_id* for this channel; an updated, potentially +complete *psbt* for this channel's funding transaction; and the flag +*commitments_secured*, which indicates the completeness of the returned *psbt*. +If *commitments_secured* is true, caller should proceed with signing the +returned PSBT and calling `openchannel_signed` to complete the channel open. + +- -32602: If the given parameters are wrong. +- -1: Catchall nonspecific error. +- 305: Peer is not connected. +- 306: Unknown peer id. +- 309: PSBT missing required fields + +SEE ALSO +-------- + +lightning-openchannel\_update(7), lightning-openchannel\_signed(7), +lightning-fundchannel\_start(7), lightning-fundchannel\_complete(7), +lightning-fundchannel(7), lightning-fundpsbt(7), lightning-utxopsbt(7), +lightning-multifundchannel(7) + +AUTHOR +------ + +@niftynei <> is mainly responsible. + +RESOURCES +--------- + +Main web site: diff --git a/lightningd/channel.h b/lightningd/channel.h index a5a041308..a94cc6cb5 100644 --- a/lightningd/channel.h +++ b/lightningd/channel.h @@ -3,6 +3,7 @@ #include "config.h" #include #include +#include #include #include #include @@ -146,6 +147,13 @@ struct channel { /* Our position in the round-robin list. */ u64 rr_number; + + /* PSBT, for v2 channels. Saved until it's sent */ + const struct wally_psbt *psbt; + + /* Stashed pps, saved until channeld is started. + * Needed only for v2 channel open flow */ + struct per_peer_state *pps; }; struct channel *new_channel(struct peer *peer, u64 dbid, diff --git a/lightningd/dual_open_control.c b/lightningd/dual_open_control.c index 9433aa723..8cba835d1 100644 --- a/lightningd/dual_open_control.c +++ b/lightningd/dual_open_control.c @@ -703,6 +703,7 @@ static void opener_psbt_changed(struct subd *dualopend, json_add_bool(response, "commitments_secured", false); uc->fc->inflight = true; + uc->fc->cmd = NULL; was_pending(command_success(cmd, response)); } @@ -918,6 +919,7 @@ static void opener_commit_received(struct subd *dualopend, goto failed; } + channel->pps = tal_steal(channel, pps); if (pbase) wallet_penalty_base_add(ld->wallet, channel->dbid, pbase); @@ -1004,6 +1006,128 @@ static void accepter_got_offer(struct subd *dualopend, plugin_hook_call_openchannel2(dualopend->ld, payload); } +static struct command_result *json_open_channel_signed(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) +{ + struct wally_psbt *psbt; + struct node_id *id; + struct peer *peer; + struct channel *channel; + struct bitcoin_txid txid; + + if (!param(cmd, buffer, params, + p_req("id", param_node_id, &id), + p_req("signed_psbt", param_psbt, &psbt), + NULL)) + return command_param_failed(); + + peer = peer_by_id(cmd->ld, id); + if (!peer) + return command_fail(cmd, FUNDING_UNKNOWN_PEER, "Unknown peer"); + + channel = peer_active_channel(peer); + if (!channel) + return command_fail(cmd, LIGHTNINGD, + "Peer has no active channel"); + + if (!channel->pps) + return command_fail(cmd, LIGHTNINGD, + "Missing per-peer-state for channel, " + "are you in the right state to call " + "this method?"); + + if (channel->psbt) + return command_fail(cmd, LIGHTNINGD, + "Already have a finalized PSBT for " + "this channel"); + + /* Verify that the psbt's txid matches that of the + * funding txid for this channel */ + psbt_txid(NULL, psbt, &txid, NULL); + if (!bitcoin_txid_eq(&txid, &channel->funding_txid)) + return command_fail(cmd, FUNDING_PSBT_INVALID, + "Txid for passed in PSBT does not match" + " funding txid for channel. Expected %s, " + "received %s", + type_to_string(tmpctx, struct bitcoin_txid, + &channel->funding_txid), + type_to_string(tmpctx, struct bitcoin_txid, + &txid)); + + + /* Go ahead and try to finalize things, or what we can */ + psbt_finalize(psbt); + + /* Check that all of *our* outputs are finalized */ + if (!psbt_side_finalized(cmd->ld->log, psbt, LOCAL)) + return command_fail(cmd, FUNDING_PSBT_INVALID, + "Local PSBT input(s) not finalized"); + + channel_watch_funding(cmd->ld, channel); + + register_open_command(cmd->ld, cmd, channel); + peer_start_channeld(channel, channel->pps, + NULL, psbt, false); + channel->pps = tal_free(channel->pps); + + return command_still_pending(cmd); +} + +static struct command_result *json_open_channel_update(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) +{ + struct wally_psbt *psbt; + struct node_id *id; + struct peer *peer; + struct channel *channel; + struct channel_id chan_id_unused; + u8 *msg; + + if (!param(cmd, buffer, params, + p_req("id", param_node_id, &id), + p_req("psbt", param_psbt, &psbt), + NULL)) + return command_param_failed(); + + peer = peer_by_id(cmd->ld, id); + if (!peer) + return command_fail(cmd, FUNDING_UNKNOWN_PEER, "Unknown peer"); + + channel = peer_active_channel(peer); + if (channel) + return command_fail(cmd, LIGHTNINGD, "Peer already %s", + channel_state_name(channel)); + + if (!peer->uncommitted_channel) + return command_fail(cmd, FUNDING_PEER_NOT_CONNECTED, + "Peer not connected"); + + if (!peer->uncommitted_channel->fc || !peer->uncommitted_channel->fc->inflight) + return command_fail(cmd, LIGHTNINGD, "No channel funding in progress"); + + if (peer->uncommitted_channel->fc->cmd) + return command_fail(cmd, LIGHTNINGD, "Channel funding in progress"); + + /* Add serials to PSBT */ + psbt_add_serials(psbt, LOCAL); + if (!psbt_has_required_fields(psbt)) + return command_fail(cmd, FUNDING_PSBT_INVALID, + "PSBT is missing required fields %s", + type_to_string(tmpctx, struct wally_psbt, psbt)); + + peer->uncommitted_channel->fc->cmd = cmd; + + memset(&chan_id_unused, 0, sizeof(chan_id_unused)); + msg = towire_dual_open_psbt_changed(NULL, &chan_id_unused, psbt); + subd_send_msg(peer->uncommitted_channel->open_daemon, take(msg)); + return command_still_pending(cmd); +} + + static struct command_result *json_open_channel_init(struct command *cmd, const char *buffer, const jsmntok_t *obj UNNEEDED, @@ -1231,7 +1355,28 @@ static const struct json_command open_channel_init_command = { "Init an open channel to {id} with {initialpsbt} for {amount} satoshis. " "Returns updated {psbt} with (partial) contributions from peer" }; + +static const struct json_command open_channel_update_command = { + "openchannel_update", + "channels", + json_open_channel_update, + "Update {channel_id} with {psbt}. " + "Returns updated {psbt} with (partial) contributions from peer. " + "If {commitments_secured} is true, next call should be to openchannel_signed" +}; + +static const struct json_command open_channel_signed_command = { + "openchannel_signed", + "channels", + json_open_channel_signed, + "Finish opening {channel_id} with {signed_psbt}. " +}; + +#if EXPERIMENTAL_FEATURES AUTODATA(json_command, &open_channel_init_command); +AUTODATA(json_command, &open_channel_update_command); +AUTODATA(json_command, &open_channel_signed_command); +#endif /* EXPERIMENTAL_FEATURES */ void peer_start_dualopend(struct peer *peer, struct per_peer_state *pps, diff --git a/lightningd/opening_common.h b/lightningd/opening_common.h index 88cc45b8d..0f804d71d 100644 --- a/lightningd/opening_common.h +++ b/lightningd/opening_common.h @@ -74,6 +74,10 @@ struct funding_channel { /* Any commands trying to cancel us. */ struct command **cancels; + + /* Place to stash the per-peer-state while we wait + * for them to get back to us with signatures */ + struct per_peer_state *pps; }; struct uncommitted_channel *