fundchannel_start / openchannel_init: add a channel_type parameter to force channel type.

And add request schemas for openchannel_init and fundchannel_start.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Changelog-Added: JSON-RPC: `fundchannel_start` and `openchannel_init` now take an optional `channel_type` parameter.
This commit is contained in:
Rusty Russell 2024-01-29 10:04:17 +10:30
parent a943a53658
commit e749aebbff
13 changed files with 260 additions and 30 deletions

View File

@ -787,7 +787,7 @@ class LightningRpc(UnixDomainSocketRpc):
return self.call("fundchannel", payload)
def fundchannel_start(self, node_id, amount, feerate=None, announce=True,
close_to=None, mindepth: Optional[int] = None):
close_to=None, mindepth: Optional[int] = None, channel_type=None):
"""
Start channel funding with {id} for {amount} satoshis
with feerate of {feerate} (uses default feerate if unset).
@ -804,6 +804,7 @@ class LightningRpc(UnixDomainSocketRpc):
"announce": announce,
"close_to": close_to,
"mindepth": mindepth,
"channel_type": channel_type,
}
return self.call("fundchannel_start", payload)
@ -1104,7 +1105,7 @@ 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, request_amt=None, *args, **kwargs):
def openchannel_init(self, node_id, channel_amount, psbt, feerate=None, funding_feerate=None, announce=True, close_to=None, request_amt=None, channel_type=None):
"""Initiate an openchannel with a peer """
payload = {
"id": node_id,
@ -1115,6 +1116,7 @@ class LightningRpc(UnixDomainSocketRpc):
"announce": announce,
"close_to": close_to,
"request_amt": request_amt,
"channel_type": channel_type,
}
return self.call("openchannel_init", payload)

View File

@ -4,7 +4,7 @@ lightning-fundchannel\_start -- Command for initiating channel establishment for
SYNOPSIS
--------
**fundchannel\_start** *id* *amount* [*feerate* *announce* *close\_to* *push\_msat*]
**fundchannel\_start** *id* *amount* [*feerate*] [*announce*] [*close\_to*] [*push\_msat*] [*channel\_type*]
DESCRIPTION
-----------
@ -34,6 +34,21 @@ open. Note that this is a gift to the peer -- these satoshis are
added to the initial balance of the peer at channel start and are largely
unrecoverable once pushed.
*channel\_type* *(added v24.02)* is an array of bit numbers, representing the explicit
channel type to request. BOLT 2 defines the following value types:
```
The currently defined basic types are:
- no features (no bits set) `[]`
- `option_static_remotekey` (`[12]`)
- `option_anchor_outputs` and `option_static_remotekey` (`[20, 12]`)
- `option_anchors_zero_fee_htlc_tx` and `option_static_remotekey` ([22, 12])
Each basic type has the following variations allowed:
- `option_scid_alias` ([46])
- `option_zeroconf` ([50])
```
Note that the funding transaction MUST NOT be broadcast until after
channel establishment has been successfully completed by running
`fundchannel_complete`, as the commitment transactions for this channel

View File

@ -4,7 +4,7 @@ lightning-openchannel\_init -- Command to initiate a channel to a peer
SYNOPSIS
--------
**openchannel\_init** *id* *amount* *initalpsbt* [*commitment\_feerate*] [*funding\_feerate*] [*announce*] [*close\_to*] [*request\_amt*] [*compact\_lease*]
**openchannel\_init** *id* *amount* *initalpsbt* [*commitment\_feerate*] [*funding\_feerate*] [*announce*] [*close\_to*] [*request\_amt*] [*compact\_lease*] [*channel\_type*]
DESCRIPTION
-----------
@ -47,6 +47,19 @@ much liquidity into the channel. Must also pass in *compact\_lease*.
channel lease terms. If the peer's terms don't match this set, we will
fail to open the channel.
*channel\_type* *(added v24.02)* is an array of bit numbers, representing the explicit
channel type to request. BOLT 2 defines the following value types:
The currently defined basic types are:
- no features (no bits set) `[]`
- `option_static_remotekey` (`[12]`)
- `option_anchor_outputs` and `option_static_remotekey` (`[20, 12]`)
- `option_anchors_zero_fee_htlc_tx` and `option_static_remotekey` ([22, 12])
Each basic type has the following variations allowed:
- `option_scid_alias` ([46])
- `option_zeroconf` ([50])
```
RETURN VALUE
------------

View File

@ -0,0 +1,43 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"additionalProperties": false,
"required": [
"id",
"amount"
],
"properties": {
"id": {
"type": "pubkey"
},
"amount": {
"type": "msat"
},
"feerate": {
"type": "feerate"
},
"announce": {
"type": "boolean"
},
"close_to": {
"type": "hex"
},
"push_msat": {
"type": "msat"
},
"mindepth": {
"type": "u32"
},
"reserve": {
"type": "msat"
},
"channel_type": {
"type": "array",
"description": "Each bit set in this channel_type",
"items": {
"type": "u32",
"description": "Bit number"
}
}
}
}

View File

@ -0,0 +1,47 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"additionalProperties": false,
"required": [
"id",
"amount",
"initialpsbt"
],
"properties": {
"id": {
"type": "pubkey"
},
"amount": {
"type": "msat"
},
"initialpsbt": {
"type": "string"
},
"commitment_feerate": {
"type": "feerate"
},
"funding_feerate": {
"type": "feerate"
},
"announce": {
"type": "boolean"
},
"close_to": {
"type": "hex"
},
"request_amt": {
"type": "msat"
},
"compact_lease": {
"type": "hex"
},
"channel_type": {
"type": "array",
"description": "Each bit set in this channel_type",
"items": {
"type": "u32",
"description": "Bit number"
}
}
}
}

View File

@ -113,6 +113,7 @@ LIGHTNINGD_COMMON_OBJS := \
common/invoice_path_id.o \
common/key_derive.o \
common/keyset.o \
common/json_channel_type.o \
common/json_filter.o \
common/json_param.o \
common/json_parse.o \

View File

@ -9,6 +9,7 @@
#include <ccan/mem/mem.h>
#include <ccan/tal/str/str.h>
#include <common/blockheight_states.h>
#include <common/json_channel_type.h>
#include <common/json_command.h>
#include <common/json_param.h>
#include <common/psbt_open.h>
@ -3031,6 +3032,7 @@ static struct command_result *json_openchannel_init(struct command *cmd,
struct open_attempt *oa;
struct lease_rates *rates;
struct command_result *res;
struct channel_type *ctype;
int fds[2];
if (!param_check(cmd, buffer, params,
@ -3043,6 +3045,7 @@ static struct command_result *json_openchannel_init(struct command *cmd,
p_opt("close_to", param_bitcoin_address, &our_upfront_shutdown_script),
p_opt_def("request_amt", param_sat, &request_amt, AMOUNT_SAT(0)),
p_opt("compact_lease", param_lease_hex, &rates),
p_opt("channel_type", param_channel_type, &ctype),
NULL))
return command_param_failed();
@ -3103,6 +3106,14 @@ static struct command_result *json_openchannel_init(struct command *cmd,
"by peer");
}
if (ctype &&
!channel_type_accept(tmpctx,
ctype->features,
cmd->ld->our_features)) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"channel_type not supported");
}
/* BOLT #2:
* - if both nodes advertised `option_support_large_channel`:
* - MAY set `funding_satoshis` greater than or equal to 2^24 satoshi.
@ -3194,6 +3205,7 @@ static struct command_result *json_openchannel_init(struct command *cmd,
NULL : request_amt,
get_block_height(cmd->ld->topology),
false,
ctype,
rates);
/* Start dualopend! */
@ -3765,7 +3777,7 @@ static struct command_result *json_queryrates(struct command *cmd,
NULL : request_amt,
get_block_height(cmd->ld->topology),
true,
NULL);
NULL, NULL);
if (socketpair(AF_LOCAL, SOCK_STREAM, 0, fds) != 0) {
return command_fail(cmd, FUND_MAX_EXCEEDED,

View File

@ -8,6 +8,7 @@
#include <common/blockheight_states.h>
#include <common/configdir.h>
#include <common/fee_states.h>
#include <common/json_channel_type.h>
#include <common/json_command.h>
#include <common/json_param.h>
#include <common/memleak.h>
@ -1148,6 +1149,7 @@ static struct command_result *json_fundchannel_start(struct command *cmd,
struct amount_msat *push_msat;
u32 *upfront_shutdown_script_wallet_index;
struct channel_id tmp_channel_id;
struct channel_type *ctype;
fc->cmd = cmd;
fc->cancels = tal_arr(fc, struct command *, 0);
@ -1164,9 +1166,18 @@ static struct command_result *json_fundchannel_start(struct command *cmd,
p_opt("push_msat", param_msat, &push_msat),
p_opt_def("mindepth", param_u32, &mindepth, cmd->ld->config.anchor_confirms),
p_opt("reserve", param_sat, &reserve),
p_opt("channel_type", param_channel_type, &ctype),
NULL))
return command_param_failed();
if (ctype &&
!channel_type_accept(tmpctx,
ctype->features,
cmd->ld->our_features)) {
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"channel_type not supported");
}
if (push_msat && amount_msat_greater_sat(*push_msat, *amount))
return command_fail(cmd, FUND_CANNOT_AFFORD,
"Requested to push_msat of %s is greater than "
@ -1307,7 +1318,8 @@ static struct command_result *json_fundchannel_start(struct command *cmd,
unilateral_feerate(cmd->ld->topology, true),
&tmp_channel_id,
fc->channel_flags,
fc->uc->reserve);
fc->uc->reserve,
ctype);
if (socketpair(AF_LOCAL, SOCK_STREAM, 0, fds) != 0) {
return command_fail(cmd, FUND_MAX_EXCEEDED,

View File

@ -2952,6 +2952,7 @@ static void opener_start(struct state *state, u8 *msg)
struct amount_sat *requested_lease;
size_t locktime;
u32 nonanchor_feerate, anchor_feerate;
struct channel_type *ctype;
if (!fromwire_dualopend_opener_init(state, msg,
&tx_state->psbt,
@ -2965,6 +2966,7 @@ static void opener_start(struct state *state, u8 *msg)
&requested_lease,
&tx_state->blockheight,
&dry_run,
&ctype,
&expected_rates))
master_badmsg(WIRE_DUALOPEND_OPENER_INIT, msg);
@ -2981,9 +2983,13 @@ static void opener_start(struct state *state, u8 *msg)
* - SHOULD NOT set it to a type containing a feature which was not
* negotiated.
*/
state->channel_type = default_channel_type(state,
if (ctype) {
state->channel_type = ctype;
} else {
state->channel_type = default_channel_type(state,
state->our_features,
state->their_features);
}
open_tlv->channel_type = state->channel_type->features;
/* Given channel type, which feerate do we use? */

View File

@ -202,6 +202,7 @@ msgdata,dualopend_opener_init,channel_flags,u8,
msgdata,dualopend_opener_init,requested_sats,?amount_sat,
msgdata,dualopend_opener_init,blockheight,u32,
msgdata,dualopend_opener_init,dry_run,bool,
msgdata,dualopend_opener_init,channel_type,?channel_type,
# must go last because embedded tu32
msgdata,dualopend_opener_init,expected_rates,?lease_rates,

Can't render this file because it has a wrong number of fields in line 16.

View File

@ -301,7 +301,8 @@ static bool intuit_scid_alias_type(struct state *state, u8 channel_flags,
/* We start the 'open a channel' negotation with the supplied peer, but
* stop when we get to the part where we need the funding txid */
static u8 *funder_channel_start(struct state *state, u8 channel_flags,
u32 nonanchor_feerate, u32 anchor_feerate)
u32 nonanchor_feerate, u32 anchor_feerate,
const struct channel_type *ctype)
{
u8 *msg;
u8 *funding_output_script;
@ -330,23 +331,27 @@ static u8 *funder_channel_start(struct state *state, u8 channel_flags,
state->our_features,
state->their_features);
state->channel_type = default_channel_type(state,
state->our_features,
state->their_features);
if (ctype) {
state->channel_type = channel_type_dup(state, ctype);
} else {
state->channel_type = default_channel_type(state,
state->our_features,
state->their_features);
/* Spec says we should use the option_scid_alias variation if we
* want them to *only* use the scid_alias (which we do for unannounced
* channels!).
*
* But:
* 1. We didn't accept this in CLN prior to v23.05.
* 2. LND won't accept that without OPT_ANCHORS_ZERO_FEE_HTLC_TX.
*
* So we keep it off for now, until anchors merge.
*/
if (channel_type_has(state->channel_type, OPT_ANCHORS_ZERO_FEE_HTLC_TX)) {
if (!(channel_flags & CHANNEL_FLAGS_ANNOUNCE_CHANNEL))
channel_type_set_scid_alias(state->channel_type);
/* Spec says we should use the option_scid_alias variation if we
* want them to *only* use the scid_alias (which we do for unannounced
* channels!).
*
* But:
* 1. We didn't accept this in CLN prior to v23.05.
* 2. LND won't accept that without OPT_ANCHORS_ZERO_FEE_HTLC_TX.
*
* So we keep it off for now, until anchors merge.
*/
if (channel_type_has(state->channel_type, OPT_ANCHORS_ZERO_FEE_HTLC_TX)) {
if (!(channel_flags & CHANNEL_FLAGS_ANNOUNCE_CHANNEL))
channel_type_set_scid_alias(state->channel_type);
}
}
/* Which feerate do we use? (We can lowball fees if using anchors!) */
@ -447,7 +452,22 @@ static u8 *funder_channel_start(struct state *state, u8 channel_flags,
* `open_channel`, and they are not equal types:
* - MUST fail the channel.
*/
if (accept_tlvs->channel_type) {
/* Simple case: caller specified, don't allow any variants */
if (ctype) {
if (!accept_tlvs->channel_type) {
negotiation_failed(state,
"They replied without a channel_type");
return NULL;
}
if (!featurebits_eq(accept_tlvs->channel_type, state->channel_type->features)) {
negotiation_failed(state,
"Return unoffered channel_type: %s",
fmt_featurebits(tmpctx,
accept_tlvs->channel_type));
return NULL;
}
} else if (accept_tlvs->channel_type) {
/* Except that v23.05 could set OPT_SCID_ALIAS in reply! */
struct channel_type *atype;
@ -569,7 +589,7 @@ static u8 *funder_channel_start(struct state *state, u8 channel_flags,
"We negotiated option_zeroconf, using our minimum_depth=%d",
state->minimum_depth);
/* We set this now to show we're zeroconf */
if (their_mindepth == 0)
if (their_mindepth == 0 && !ctype)
channel_type_set_zeroconf(state->channel_type);
} else {
state->minimum_depth = their_mindepth;
@ -581,8 +601,8 @@ static u8 *funder_channel_start(struct state *state, u8 channel_flags,
tal_hex(tmpctx, funding_output_script));
/* Backwards/cross compat hack */
if (intuit_scid_alias_type(state, channel_flags,
accept_tlvs->channel_type != NULL)) {
if (!ctype && intuit_scid_alias_type(state, channel_flags,
accept_tlvs->channel_type != NULL)) {
channel_type_set_scid_alias(state->channel_type);
}
@ -1437,6 +1457,7 @@ static u8 *handle_master_in(struct state *state)
struct bitcoin_txid funding_txid;
u16 funding_txout;
u32 nonanchor_feerate, anchor_feerate;
struct channel_type *ctype;
switch (t) {
case WIRE_OPENINGD_FUNDER_START:
@ -1449,9 +1470,11 @@ static u8 *handle_master_in(struct state *state)
&anchor_feerate,
&state->channel_id,
&channel_flags,
&state->reserve))
&state->reserve,
&ctype))
master_badmsg(WIRE_OPENINGD_FUNDER_START, msg);
msg = funder_channel_start(state, channel_flags, nonanchor_feerate, anchor_feerate);
msg = funder_channel_start(state, channel_flags, nonanchor_feerate, anchor_feerate, ctype);
tal_free(ctype);
/* We want to keep openingd alive, since we're not done yet */
if (msg)

View File

@ -87,6 +87,7 @@ msgdata,openingd_funder_start,anchor_feerate_per_kw,u32,
msgdata,openingd_funder_start,temporary_channel_id,channel_id,
msgdata,openingd_funder_start,channel_flags,u8,
msgdata,openingd_funder_start,reserve,?amount_sat,
msgdata,openingd_funder_start,channel_type,?channel_type,
# openingd->master: send back output script for 2-of-2 funding output
msgtype,openingd_funder_start_reply,6102

1 #include <bitcoin/chainparams.h>
87 msgdata,openingd_funder_start_reply,script_len,u8, msgtype,openingd_funder_start_reply,6102
88 msgdata,openingd_funder_start_reply,scriptpubkey,u8,script_len msgdata,openingd_funder_start_reply,script_len,u8,
89 msgdata,openingd_funder_start_reply,upfront_shutdown_negotiated,bool, msgdata,openingd_funder_start_reply,scriptpubkey,u8,script_len
90 msgdata,openingd_funder_start_reply,upfront_shutdown_negotiated,bool,
91 msgdata,openingd_funder_start_reply,channel_type,channel_type,
92 # master->openingd: complete channel establishment for a funding
93 # tx that will be paid for by an external wallet

View File

@ -4,6 +4,7 @@ from pyln.client import RpcError, Millisatoshi
from utils import (
only_one, wait_for, sync_blockheight, first_channel_id, calc_lease_fee, check_coin_moves
)
from pyln.testing.utils import FUNDAMOUNT
from pathlib import Path
from pprint import pprint
@ -2581,3 +2582,56 @@ def test_fundchannel_utxo_too_small(bitcoind, node_factory):
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
with pytest.raises(RpcError, match=r'Could not afford 100000sat using all 0 available UTXOs'):
l1.rpc.fundchannel(l2.info['id'], 100000, 10000)
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_opening_explicit_channel_type(node_factory):
plugin_path = Path(__file__).parent / "plugins" / "zeroconf-selective.py"
l1, l2, l3, l4 = node_factory.get_nodes(4,
opts=[{'experimental-dual-fund': None,
'experimental-anchors': None},
{'experimental-anchors': None,
'plugin': str(plugin_path),
'zeroconf-allow': '0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518'},
{'experimental-dual-fund': None,
'experimental-anchors': None},
{'experimental-anchors': None}])
l1.fundwallet(FUNDAMOUNT)
l1.connect(l2)
l1.connect(l3)
l1.connect(l4)
STATIC_REMOTEKEY = 12
ANCHORS_OLD = 20
ANCHORS_ZERO_FEE_HTLC_TX = 22
ZEROCONF = 50
for zeroconf in ([], [ZEROCONF]):
for ctype in ([],
[STATIC_REMOTEKEY],
[ANCHORS_ZERO_FEE_HTLC_TX, STATIC_REMOTEKEY]):
l1.rpc.fundchannel_start(l2.info['id'], FUNDAMOUNT,
channel_type=ctype + zeroconf)
l1.rpc.fundchannel_cancel(l2.info['id'])
# FIXME: Check type is actually correct!
# Zeroconf is refused to l4.
for ctype in ([],
[STATIC_REMOTEKEY],
[ANCHORS_ZERO_FEE_HTLC_TX, STATIC_REMOTEKEY]):
with pytest.raises(RpcError, match=r'not on our allowlist'):
l1.rpc.fundchannel_start(l4.info['id'], FUNDAMOUNT,
channel_type=ctype + [ZEROCONF])
psbt = l1.rpc.fundpsbt(FUNDAMOUNT - 1000, '253perkw', 250, reserve=0)['psbt']
for ctype in ([], [12], [22, 12]):
cid = l1.rpc.openchannel_init(l3.info['id'], FUNDAMOUNT - 1000, psbt, channel_type=ctype)['channel_id']
l1.rpc.openchannel_abort(cid)
# Old anchors not supported for new channels
with pytest.raises(RpcError, match=r'channel_type not supported'):
l1.rpc.fundchannel_start(l2.info['id'], FUNDAMOUNT, channel_type=[STATIC_REMOTEKEY, ANCHORS_OLD])
with pytest.raises(RpcError, match=r'channel_type not supported'):
l1.rpc.openchannel_init(l3.info['id'], FUNDAMOUNT - 1000, psbt, channel_type=[STATIC_REMOTEKEY, ANCHORS_OLD])