mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-01-17 19:03:42 +01:00
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`.
This commit is contained in:
parent
537eeab208
commit
8858ae4f3d
@ -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 = {
|
||||
|
@ -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
|
||||
|
||||
|
@ -69,6 +69,8 @@ c-lightning Documentation
|
||||
lightning-multiwithdraw <lightning-multiwithdraw.7.md>
|
||||
lightning-newaddr <lightning-newaddr.7.md>
|
||||
lightning-openchannel_init <lightning-openchannel_init.7.md>
|
||||
lightning-openchannel_signed <lightning-openchannel_signed.7.md>
|
||||
lightning-openchannel_update <lightning-openchannel_update.7.md>
|
||||
lightning-pay <lightning-pay.7.md>
|
||||
lightning-ping <lightning-ping.7.md>
|
||||
lightning-plugin <lightning-plugin.7.md>
|
||||
|
68
doc/lightning-openchannel_signed.7
generated
Normal file
68
doc/lightning-openchannel_signed.7
generated
Normal file
@ -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<niftynei@gmail.com\fR> is mainly responsible\.
|
||||
|
||||
.SH RESOURCES
|
||||
|
||||
Main web site: \fIhttps://github.com/ElementsProject/lightning\fR
|
||||
|
||||
\" SHA256STAMP:68fb78430a5ee3707fdb1324ba46373dd3dfaf325fe4e1bd3dcdc1589d80435c
|
60
doc/lightning-openchannel_signed.7.md
Normal file
60
doc/lightning-openchannel_signed.7.md
Normal file
@ -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 <<niftynei@gmail.com>> is mainly responsible.
|
||||
|
||||
RESOURCES
|
||||
---------
|
||||
|
||||
Main web site: <https://github.com/ElementsProject/lightning>
|
66
doc/lightning-openchannel_update.7
generated
Normal file
66
doc/lightning-openchannel_update.7
generated
Normal file
@ -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<niftynei@gmail.com\fR> is mainly responsible\.
|
||||
|
||||
.SH RESOURCES
|
||||
|
||||
Main web site: \fIhttps://github.com/ElementsProject/lightning\fR
|
||||
|
||||
\" SHA256STAMP:dcf253e7b1658e71a9721fccee76a6bb07af7579ca4b0413428d2a1e8c9613bc
|
59
doc/lightning-openchannel_update.7.md
Normal file
59
doc/lightning-openchannel_update.7.md
Normal file
@ -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 <<niftynei@gmail.com>> is mainly responsible.
|
||||
|
||||
RESOURCES
|
||||
---------
|
||||
|
||||
Main web site: <https://github.com/ElementsProject/lightning>
|
@ -3,6 +3,7 @@
|
||||
#include "config.h"
|
||||
#include <ccan/list/list.h>
|
||||
#include <common/channel_id.h>
|
||||
#include <common/per_peer_state.h>
|
||||
#include <lightningd/channel_state.h>
|
||||
#include <lightningd/peer_htlcs.h>
|
||||
#include <wallet/wallet.h>
|
||||
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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 *
|
||||
|
Loading…
Reference in New Issue
Block a user