diff --git a/contrib/pyln-client/pyln/client/lightning.py b/contrib/pyln-client/pyln/client/lightning.py index a18e5a066814..732a2dcfeb31 100644 --- a/contrib/pyln-client/pyln/client/lightning.py +++ b/contrib/pyln-client/pyln/client/lightning.py @@ -1019,7 +1019,7 @@ def pay(self, bolt11, msatoshi=None, label=None, riskfactor=None, } return self.call("pay", payload) - def queryrates(self, node_id, channel_amount, request_amt, commitment_feerate=None, funding_feerate=None): + def dev_queryrates(self, node_id, channel_amount, request_amt, commitment_feerate=None, funding_feerate=None): """Ask a peer how much they'd charge for a given liquidity amount """ payload = { "id": node_id, @@ -1028,7 +1028,7 @@ def queryrates(self, node_id, channel_amount, request_amt, commitment_feerate=No "commitment_feerate": commitment_feerate, "funding_feerate": funding_feerate, } - return self.call("queryrates", payload) + return self.call("dev-queryrates", 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): """Initiate an openchannel with a peer """ diff --git a/contrib/startup_regtest.sh b/contrib/startup_regtest.sh index 996e20fe9dfe..280ef85f95ad 100755 --- a/contrib/startup_regtest.sh +++ b/contrib/startup_regtest.sh @@ -109,7 +109,7 @@ start_nodes() { # Start the lightning nodes test -f "/tmp/l$i-$network/lightningd-$network.pid" || \ - "$LIGHTNINGD" "--lightning-dir=/tmp/l$i-$network" --daemon + "$LIGHTNINGD" "--lightning-dir=/tmp/l$i-$network" & # shellcheck disable=SC2139 disable=SC2086 alias l$i-cli="$LCLI --lightning-dir=/tmp/l$i-$network" # shellcheck disable=SC2139 disable=SC2086 diff --git a/lightningd/dual_open_control.c b/lightningd/dual_open_control.c index f2a866194d43..3cc9b48fdae9 100644 --- a/lightningd/dual_open_control.c +++ b/lightningd/dual_open_control.c @@ -2490,106 +2490,6 @@ static struct command_result *init_set_feerate(struct command *cmd, return NULL; } -static struct command_result *json_queryrates(struct command *cmd, - const char *buffer, - const jsmntok_t *obj UNNEEDED, - const jsmntok_t *params) -{ - struct node_id *id; - struct peer *peer; - struct channel *channel; - u32 *feerate_per_kw_funding; - u32 *feerate_per_kw; - struct amount_sat *amount, *request_amt; - struct wally_psbt *psbt; - struct open_attempt *oa; - u8 *msg; - struct command_result *res; - - if (!param(cmd, buffer, params, - p_req("id", param_node_id, &id), - p_req("amount", param_sat, &amount), - p_req("request_amt", param_sat, &request_amt), - p_opt("commitment_feerate", param_feerate, &feerate_per_kw), - p_opt("funding_feerate", param_feerate, &feerate_per_kw_funding), - NULL)) - return command_param_failed(); - - res = init_set_feerate(cmd, &feerate_per_kw, &feerate_per_kw_funding); - if (res) - return res; - - peer = peer_by_id(cmd->ld, id); - if (!peer) { - return command_fail(cmd, FUNDING_UNKNOWN_PEER, "Unknown peer"); - } - - /* We can't query rates for a peer we have a channel with */ - channel = peer_active_channel(peer); - if (channel) - return command_fail(cmd, LIGHTNINGD, "Peer in state %s," - " can't query peer's rates if already" - " have a channel", - channel_state_name(channel)); - - channel = peer_unsaved_channel(peer); - if (!channel || !channel->owner) - return command_fail(cmd, FUNDING_PEER_NOT_CONNECTED, - "Peer not connected"); - if (channel->open_attempt - || !list_empty(&channel->inflights)) - return command_fail(cmd, FUNDING_STATE_INVALID, - "Channel funding in-progress. %s", - channel_state_name(channel)); - - if (!feature_negotiated(cmd->ld->our_features, - peer->their_features, - OPT_DUAL_FUND)) { - return command_fail(cmd, FUNDING_V2_NOT_SUPPORTED, - "v2 openchannel not supported " - "by peer, can't query rates"); - } - - /* BOLT #2: - * - if both nodes advertised `option_support_large_channel`: - * - MAY set `funding_satoshis` greater than or equal to 2^24 satoshi. - * - otherwise: - * - MUST set `funding_satoshis` to less than 2^24 satoshi. - */ - if (!feature_negotiated(cmd->ld->our_features, - peer->their_features, OPT_LARGE_CHANNELS) - && amount_sat_greater(*amount, chainparams->max_funding)) - return command_fail(cmd, FUND_MAX_EXCEEDED, - "Amount exceeded %s", - type_to_string(tmpctx, struct amount_sat, - &chainparams->max_funding)); - - /* Get a new open_attempt going, keeps us from re-initing - * while looking */ - channel->opener = LOCAL; - channel->open_attempt = oa = new_channel_open_attempt(channel); - channel->channel_flags = OUR_CHANNEL_FLAGS; - oa->funding = *amount; - oa->cmd = cmd; - /* empty psbt to start */ - psbt = create_psbt(tmpctx, 0, 0, 0); - - msg = towire_dualopend_opener_init(NULL, - psbt, *amount, - oa->our_upfront_shutdown_script, - *feerate_per_kw, - *feerate_per_kw_funding, - channel->channel_flags, - *request_amt, - get_block_height(cmd->ld->topology), - true, - NULL); - - subd_send_msg(channel->owner, take(msg)); - return command_still_pending(cmd); - -} - static struct command_result *json_openchannel_init(struct command *cmd, const char *buffer, const jsmntok_t *obj UNNEEDED, @@ -3120,14 +3020,118 @@ static unsigned int dual_opend_msg(struct subd *dualopend, return 0; } +#if DEVELOPER +static struct command_result *json_queryrates(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) +{ + struct node_id *id; + struct peer *peer; + struct channel *channel; + u32 *feerate_per_kw_funding; + u32 *feerate_per_kw; + struct amount_sat *amount, *request_amt; + struct wally_psbt *psbt; + struct open_attempt *oa; + u8 *msg; + struct command_result *res; + + if (!param(cmd, buffer, params, + p_req("id", param_node_id, &id), + p_req("amount", param_sat, &amount), + p_req("request_amt", param_sat, &request_amt), + p_opt("commitment_feerate", param_feerate, &feerate_per_kw), + p_opt("funding_feerate", param_feerate, &feerate_per_kw_funding), + NULL)) + return command_param_failed(); + + res = init_set_feerate(cmd, &feerate_per_kw, &feerate_per_kw_funding); + if (res) + return res; + + peer = peer_by_id(cmd->ld, id); + if (!peer) { + return command_fail(cmd, FUNDING_UNKNOWN_PEER, "Unknown peer"); + } + + /* We can't query rates for a peer we have a channel with */ + channel = peer_active_channel(peer); + if (channel) + return command_fail(cmd, LIGHTNINGD, "Peer in state %s," + " can't query peer's rates if already" + " have a channel", + channel_state_name(channel)); + + channel = peer_unsaved_channel(peer); + if (!channel || !channel->owner) + return command_fail(cmd, FUNDING_PEER_NOT_CONNECTED, + "Peer not connected"); + if (channel->open_attempt + || !list_empty(&channel->inflights)) + return command_fail(cmd, FUNDING_STATE_INVALID, + "Channel funding in-progress. %s", + channel_state_name(channel)); + + if (!feature_negotiated(cmd->ld->our_features, + peer->their_features, + OPT_DUAL_FUND)) { + return command_fail(cmd, FUNDING_V2_NOT_SUPPORTED, + "v2 openchannel not supported " + "by peer, can't query rates"); + } + + /* BOLT #2: + * - if both nodes advertised `option_support_large_channel`: + * - MAY set `funding_satoshis` greater than or equal to 2^24 satoshi. + * - otherwise: + * - MUST set `funding_satoshis` to less than 2^24 satoshi. + */ + if (!feature_negotiated(cmd->ld->our_features, + peer->their_features, OPT_LARGE_CHANNELS) + && amount_sat_greater(*amount, chainparams->max_funding)) + return command_fail(cmd, FUND_MAX_EXCEEDED, + "Amount exceeded %s", + type_to_string(tmpctx, struct amount_sat, + &chainparams->max_funding)); + + /* Get a new open_attempt going, keeps us from re-initing + * while looking */ + channel->opener = LOCAL; + channel->open_attempt = oa = new_channel_open_attempt(channel); + channel->channel_flags = OUR_CHANNEL_FLAGS; + oa->funding = *amount; + oa->cmd = cmd; + /* empty psbt to start */ + psbt = create_psbt(tmpctx, 0, 0, 0); + + msg = towire_dualopend_opener_init(NULL, + psbt, *amount, + oa->our_upfront_shutdown_script, + *feerate_per_kw, + *feerate_per_kw_funding, + channel->channel_flags, + *request_amt, + get_block_height(cmd->ld->topology), + true, + NULL); + + subd_send_msg(channel->owner, take(msg)); + return command_still_pending(cmd); + +} + static const struct json_command queryrates_command = { - "queryrates", + "dev-queryrates", "channels", json_queryrates, "Ask a peer what their contribution and liquidity rates are" " for the given {amount} and {requested_amt}" }; +AUTODATA(json_command, &queryrates_command); +#endif /* DEVELOPER */ + static const struct json_command openchannel_init_command = { "openchannel_init", "channels", @@ -3166,7 +3170,6 @@ static const struct json_command openchannel_abort_command = { "Abort {channel_id}'s open. Usable while `commitment_signed=false`." }; -AUTODATA(json_command, &queryrates_command); AUTODATA(json_command, &openchannel_init_command); AUTODATA(json_command, &openchannel_update_command); AUTODATA(json_command, &openchannel_signed_command); diff --git a/tests/test_closing.py b/tests/test_closing.py index 101d0963ab0c..31f5afb1f8d8 100644 --- a/tests/test_closing.py +++ b/tests/test_closing.py @@ -729,6 +729,7 @@ def calc_lease_fee(amt, feerate, rates): @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') @pytest.mark.openchannel('v2') @pytest.mark.slow_test +@pytest.mark.developer("requres 'dev-queryrates'") def test_channel_lease_falls_behind(node_factory, bitcoind): ''' If our peer falls too far behind/doesn't send us an update for @@ -745,7 +746,7 @@ def test_channel_lease_falls_behind(node_factory, bitcoind): l2.fundwallet(20000000) l1.rpc.connect(l2.info['id'], 'localhost', l2.port) - rates = l1.rpc.queryrates(l2.info['id'], amount, amount) + rates = l1.rpc.dev_queryrates(l2.info['id'], amount, amount) l1.daemon.wait_for_log('disconnect') l1.rpc.connect(l2.info['id'], 'localhost', l2.port) # l1 leases a channel from l2 @@ -768,6 +769,7 @@ def test_channel_lease_falls_behind(node_factory, bitcoind): @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') @pytest.mark.openchannel('v2') +@pytest.mark.developer("requres 'dev-queryrates'") @pytest.mark.slow_test def test_channel_lease_post_expiry(node_factory, bitcoind): @@ -868,7 +870,7 @@ def test_channel_lease_unilat_closes(node_factory, bitcoind): l3.fundwallet(20000000) l1.rpc.connect(l2.info['id'], 'localhost', l2.port) - rates = l1.rpc.queryrates(l2.info['id'], amount, amount) + rates = l1.rpc.dev_queryrates(l2.info['id'], amount, amount) l1.daemon.wait_for_log('disconnect') l1.rpc.connect(l2.info['id'], 'localhost', l2.port) # l1 leases a channel from l2 @@ -878,7 +880,7 @@ def test_channel_lease_unilat_closes(node_factory, bitcoind): # l2 leases a channel from l3 l2.rpc.connect(l3.info['id'], 'localhost', l3.port) - rates = l2.rpc.queryrates(l3.info['id'], amount, amount) + rates = l2.rpc.dev_queryrates(l3.info['id'], amount, amount) l3.daemon.wait_for_log('disconnect') l2.rpc.connect(l3.info['id'], 'localhost', l3.port) l2.rpc.fundchannel(l3.info['id'], amount, request_amt=amount, @@ -949,6 +951,7 @@ def test_channel_lease_unilat_closes(node_factory, bitcoind): @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') @pytest.mark.openchannel('v2') @unittest.skipIf(os.getenv('TEST_DB_PROVIDER', 'sqlite3') != 'sqlite3', "Makes use of the sqlite3 db") +@pytest.mark.developer("requres 'dev-queryrates'") def test_channel_lease_lessor_cheat(node_factory, bitcoind, chainparams): ''' Check that lessee can recover funds if lessor cheats @@ -966,7 +969,7 @@ def test_channel_lease_lessor_cheat(node_factory, bitcoind, chainparams): l2.fundwallet(20000000) l1.rpc.connect(l2.info['id'], 'localhost', l2.port) - rates = l1.rpc.queryrates(l2.info['id'], amount, amount) + rates = l1.rpc.dev_queryrates(l2.info['id'], amount, amount) l1.daemon.wait_for_log('disconnect') l1.rpc.connect(l2.info['id'], 'localhost', l2.port) # l1 leases a channel from l2 @@ -1021,6 +1024,7 @@ def test_channel_lease_lessor_cheat(node_factory, bitcoind, chainparams): @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') @pytest.mark.openchannel('v2') @unittest.skipIf(os.getenv('TEST_DB_PROVIDER', 'sqlite3') != 'sqlite3', "Makes use of the sqlite3 db") +@pytest.mark.developer("requres 'dev-queryrates'") def test_channel_lease_lessee_cheat(node_factory, bitcoind, chainparams): ''' Check that lessor can recover funds if lessee cheats @@ -1038,7 +1042,7 @@ def test_channel_lease_lessee_cheat(node_factory, bitcoind, chainparams): l2.fundwallet(20000000) l1.rpc.connect(l2.info['id'], 'localhost', l2.port) - rates = l1.rpc.queryrates(l2.info['id'], amount, amount) + rates = l1.rpc.dev_queryrates(l2.info['id'], amount, amount) l1.daemon.wait_for_log('disconnect') l1.rpc.connect(l2.info['id'], 'localhost', l2.port) # l1 leases a channel from l2 diff --git a/tests/test_opening.py b/tests/test_opening.py index 3a9d8550dd83..83d2b6b9399f 100644 --- a/tests/test_opening.py +++ b/tests/test_opening.py @@ -17,6 +17,7 @@ def find_next_feerate(node, peer): @unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need') @pytest.mark.openchannel('v2') +@pytest.mark.developer("requres 'dev-queryrates'") def test_queryrates(node_factory, bitcoind): l1, l2 = node_factory.get_nodes(2) @@ -27,7 +28,7 @@ def test_queryrates(node_factory, bitcoind): l1.rpc.connect(l2.info['id'], 'localhost', l2.port) with pytest.raises(RpcError, match=r'not advertising liquidity'): - l1.rpc.queryrates(l2.info['id'], amount, amount * 10) + l1.rpc.dev_queryrates(l2.info['id'], amount, amount * 10) l2.rpc.call('funderupdate', {'policy': 'match', 'policy_mod': 100, @@ -40,7 +41,7 @@ def test_queryrates(node_factory, bitcoind): 'channel_fee_max_proportional_thousandths': 101}) l1.rpc.connect(l2.info['id'], 'localhost', l2.port) - result = l1.rpc.queryrates(l2.info['id'], amount, amount) + result = l1.rpc.dev_queryrates(l2.info['id'], amount, amount) assert result['our_funding_msat'] == Millisatoshi(amount * 1000) assert result['their_funding_msat'] == Millisatoshi(amount * 1000) assert result['funding_weight'] == 1000