diff --git a/contrib/msggen/msggen/schema.json b/contrib/msggen/msggen/schema.json index 8f83713d92be..65d9964af1dd 100644 --- a/contrib/msggen/msggen/schema.json +++ b/contrib/msggen/msggen/schema.json @@ -21894,7 +21894,11 @@ "value_bool": false, "source": "default", "plugin": "/root/lightning/plugins/cln-xpay", - "dynamic": true + "dynamic": true, + "deprecated": [ + "v26.09", + "v27.03" + ] }, "xpay-slow-mode": { "value_bool": false, diff --git a/doc/developers-guide/deprecated-features.md b/doc/developers-guide/deprecated-features.md index 470fef6f88d9..220d0b742a89 100644 --- a/doc/developers-guide/deprecated-features.md +++ b/doc/developers-guide/deprecated-features.md @@ -31,6 +31,7 @@ privacy: | keysend | Command | v26.06 | v27.03 | Replaced by more powerful `xkeysend`. | | renepay | Command | v26.06 | v27.03 | Use `xpay` instead. | | renepaystatus | Command | v26.06 | v27.03 | Use `xpay` notifications and `listpays` or `listsendpays` instead. | +| xpay-handle-pay | Config | v26.09 | v27.03 | `pay` is being deprecated and `xpay` for all payments is the default. | Inevitably there are features which need to change: either to be generalized, or removed when they can no longer be supported. diff --git a/doc/lightningd-config.5.md b/doc/lightningd-config.5.md index d3a98b1c83c0..e3e08b5b5f23 100644 --- a/doc/lightningd-config.5.md +++ b/doc/lightningd-config.5.md @@ -555,9 +555,9 @@ network. Add a (taproot) fallback address to invoices produced by the `invoice` command, so they invoices can also be paid onchain. -* **xpay-handle-pay**=*BOOL* [plugin `xpay`, *dynamic*] +* **xpay-handle-pay**=*BOOL* [plugin `xpay`, *dynamic*] \(deprecated in v26.09\) - Setting this makes `xpay` intercept simply `pay` commands (default `false`). + Setting this makes `xpay` intercept simply `pay` commands (default `true`). * **xpay-slow-mode**=*BOOL* [plugin `xpay`, *dynamic*] diff --git a/doc/schemas/listconfigs.json b/doc/schemas/listconfigs.json index 2fa9ba4b9b75..eff813e4a99e 100644 --- a/doc/schemas/listconfigs.json +++ b/doc/schemas/listconfigs.json @@ -2191,7 +2191,11 @@ "value_bool": false, "source": "default", "plugin": "/root/lightning/plugins/cln-xpay", - "dynamic": true + "dynamic": true, + "deprecated": [ + "v26.09", + "v27.03" + ] }, "xpay-slow-mode": { "value_bool": false, diff --git a/plugins/libplugin.c b/plugins/libplugin.c index 2bc153f6e8f9..bcd2509eb189 100644 --- a/plugins/libplugin.c +++ b/plugins/libplugin.c @@ -2244,15 +2244,25 @@ static void ld_command_handle(struct plugin *plugin, else val = "true"; - problem = popt->handle(cmd, val, check_only, popt->arg); - if (problem) + if (!deprecated_ok(plugin->deprecated_ok, popt->name, + popt->depr_start, popt->depr_end, + plugin->beglist, NULL, NULL)) { ret = command_fail(cmd, JSONRPC2_INVALID_PARAMS, - "%s", problem); - else { - if (check_only) - ret = command_check_done(cmd); - else - ret = command_finished(cmd, jsonrpc_stream_success(cmd)); + "Configuration %s is deprecated", + popt->name); + } else { + + problem = popt->handle(cmd, val, check_only, popt->arg); + if (problem) + ret = command_fail(cmd, JSONRPC2_INVALID_PARAMS, + "%s", problem); + else { + if (check_only) + ret = command_check_done(cmd); + else + ret = command_finished( + cmd, jsonrpc_stream_success(cmd)); + } } assert(ret == &complete); return; diff --git a/plugins/libplugin.h b/plugins/libplugin.h index 9134d2a28075..789a1542c64e 100644 --- a/plugins/libplugin.h +++ b/plugins/libplugin.h @@ -629,6 +629,9 @@ void *plugin_get_data_(struct plugin *plugin); #define plugin_option_deprecated(name, type, description, depr_start, depr_end, set, jsonfmt, arg) \ plugin_option_((name), (type), (description), (set), (jsonfmt), (arg), false, (depr_start), (depr_end), false, false) +#define plugin_option_deprecated_dynamic(name, type, description, depr_start, depr_end, set, jsonfmt, arg) \ + plugin_option_((name), (type), (description), (set), (jsonfmt), (arg), false, (depr_start), (depr_end), true, false) + #define plugin_option_multi(name, type, description, set, jsonfmt, arg) \ plugin_option_((name), (type), (description), (set), (jsonfmt), (arg), false, NULL, NULL, false, true) diff --git a/plugins/pay.c b/plugins/pay.c index f13d5ff93cce..2a3d0f94ad7a 100644 --- a/plugins/pay.c +++ b/plugins/pay.c @@ -1168,7 +1168,8 @@ static const struct plugin_command commands[] = { }, { "pay", - json_pay + json_pay, + "v26.06", "v27.03", }, }; diff --git a/plugins/xpay/xpay.c b/plugins/xpay/xpay.c index d9a3c297ff8b..644d7b509bcf 100644 --- a/plugins/xpay/xpay.c +++ b/plugins/xpay/xpay.c @@ -3417,9 +3417,14 @@ int main(int argc, char *argv[]) notifications, ARRAY_SIZE(notifications), hooks, ARRAY_SIZE(hooks), outgoing_notifications, ARRAY_SIZE(outgoing_notifications), - plugin_option_dynamic("xpay-handle-pay", "bool", - "Make xpay take over pay commands it can handle.", - bool_option, bool_jsonfmt, &xpay->take_over_pay), + plugin_option_deprecated_dynamic("xpay-handle-pay", + "bool", + "Make xpay take over pay commands it can handle.", + "v26.09", + "v27.03", + bool_option, + NULL, + &xpay->take_over_pay), plugin_option_dynamic("xpay-slow-mode", "bool", "Wait until all parts have completed before returning success or failure", bool_option, bool_jsonfmt, &xpay->slow_mode), diff --git a/tests/test_clnrest.py b/tests/test_clnrest.py index 479ca25a7938..c516371a88f5 100644 --- a/tests/test_clnrest.py +++ b/tests/test_clnrest.py @@ -422,7 +422,8 @@ def test_numeric_msat_notification(node_factory): # create rune authorizing listclnrest-notifications method rune_clnrest_notifications = l2.rpc.createrune(restrictions=[["method=listclnrest-notifications"]])['rune'] http_session.headers.update({"rune": rune_clnrest_notifications}) - notifications = notifications_received_via_websocket(l1, base_url, http_session, 'pay', [inv['bolt11']]) + notifications = notifications_received_via_websocket(l1, base_url, + http_session, 'xpay', [inv['bolt11']]) filtered_notifications = [n for n in notifications if 'invoice_payment' in n] assert isinstance(filtered_notifications[0]['invoice_payment']['msat'], int) @@ -875,7 +876,7 @@ def test_dynamic_path_rune(node_factory): dynamic_res.json()["message"] == "Not permitted: method is not equal to notpay" ) - rune = l1.rpc.createrune(restrictions=[["method=pay"]])["rune"] + rune = l1.rpc.createrune(restrictions=[["method=xpay"]])["rune"] dynamic_res = http_session.post( base_url + "/test/dynamic/clnrest", headers={"Rune": rune}, verify=ca_cert ) diff --git a/tests/test_pay.py b/tests/test_pay.py index 0f357547ae76..3f3831255c88 100644 --- a/tests/test_pay.py +++ b/tests/test_pay.py @@ -24,7 +24,8 @@ @pytest.mark.openchannel('v1') @pytest.mark.openchannel('v2') def test_pay(node_factory): - l1, l2 = node_factory.line_graph(2) + l1, l2 = node_factory.line_graph(2, opts={"allow-deprecated-apis": True, + "xpay-handle-pay": False}) inv = l2.rpc.invoice(123000, 'test_pay', 'description')['bolt11'] before = int(time.time()) @@ -84,8 +85,10 @@ def test_pay(node_factory): assert apys_1[0]['routed_in_msat'] == apys_2[0]['routed_out_msat'] -def test_pay_invstring(node_factory): - l1, l2 = node_factory.line_graph(2, opts={'allow-deprecated-apis': True}) +@pytest.mark.parametrize("xpay_handle_pay", [True, False]) +def test_pay_invstring(node_factory, xpay_handle_pay): + l1, l2 = node_factory.line_graph(2, opts={'allow-deprecated-apis': True, + "xpay-handle-pay": xpay_handle_pay}) l1.rpc.check_request_schemas = False inv = l2.rpc.invoice(123000, 'test_pay_invstring', 'description')['bolt11'] @@ -2381,7 +2384,8 @@ def test_setchannel_routing(node_factory, bitcoind): assert 'warning_capacity' in inv -def test_setchannel_zero(node_factory, bitcoind): +@pytest.mark.parametrize("xpay_handle_pay", [True, False]) +def test_setchannel_zero(node_factory, bitcoind, xpay_handle_pay): # TEST SETUP # # [l1] <--default_fees--> [l2] <--specific_fees--> [l3] @@ -2395,7 +2399,8 @@ def test_setchannel_zero(node_factory, bitcoind): l1, l2, l3 = node_factory.line_graph( 3, announce_channels=True, wait_for_announce=True, - opts={'fee-base': DEF_BASE, 'fee-per-satoshi': DEF_PPM}) + opts={'fee-base': DEF_BASE, 'fee-per-satoshi': DEF_PPM, + "allow-deprecated-apis": True, "xpay-handle-pay": xpay_handle_pay}) # get short channel id for 2->3 scid = l2.get_channel_scid(l3) @@ -3428,13 +3433,16 @@ def test_reject_invalid_payload(node_factory): l2.daemon.wait_for_log(r'Failing HTLC because of an invalid payload') -def test_excluded_adjacent_routehint(node_factory, bitcoind): +@pytest.mark.parametrize("xpay_handle_pay", [True, False]) +def test_excluded_adjacent_routehint(node_factory, bitcoind, xpay_handle_pay): """Test case where we try have a routehint which leads to an adjacent node, but the result exceeds our maxfee; we crashed trying to find what part of the path was most expensive in that case """ - l1, l2, l3 = node_factory.line_graph(3) + l1, l2, l3 = node_factory.line_graph(3, opts={"allow-deprecated-apis": True, + "xpay-handle-pay": + xpay_handle_pay}) # Make sure l2->l3 is usable. wait_for(lambda: 'remote' in only_one(l3.rpc.listpeerchannels()['channels'])['updates']) @@ -3446,7 +3454,10 @@ def test_excluded_adjacent_routehint(node_factory, bitcoind): wait_for(lambda: 'remote' in only_one(l1.rpc.listpeerchannels()['channels'])['updates']) # This will make it reject the routehint. - err = 'Failed: Could not find route without excessive cost' + if xpay_handle_pay: + err = 'Failed: Could not find route without excessive cost' + else: + err = 'Fee exceeds our fee budget: 1msat > 0msat' with pytest.raises(RpcError, match=err): l1.rpc.pay(bolt11=inv['bolt11'], maxfeepercent=0, exemptfee=0) @@ -4837,13 +4848,13 @@ def test_recurrence_expired_offer(node_factory, bitcoind): ret = l1.rpc.fetchinvoice(offer=offer['bolt12'], recurrence_counter=0, recurrence_label='test_recurrence_expired_offer') - l1.rpc.pay(ret['invoice'], label='test_recurrence_expired_offer') + l1.rpc.xpay(ret['invoice'], label='test_recurrence_expired_offer') time.sleep(16) ret = l1.rpc.fetchinvoice(offer=offer['bolt12'], recurrence_counter=1, recurrence_label='test_recurrence_expired_offer') - l1.rpc.pay(ret['invoice'], label='test_recurrence_expired_offer') + l1.rpc.xpay(ret['invoice'], label='test_recurrence_expired_offer') def test_fetchinvoice_autoconnect(node_factory, bitcoind): @@ -4921,7 +4932,8 @@ def test_fetchinvoice_disconnected_reply(node_factory, bitcoind): assert l3.rpc.listpeers(l1.info['id']) == {'peers': []} -def test_pay_blockheight_mismatch(node_factory, bitcoind): +@pytest.mark.parametrize("xpay_handle_pay", [True, False]) +def test_pay_blockheight_mismatch(node_factory, bitcoind, xpay_handle_pay): """Test that we can send a payment even if not caught up with the chain. We removed the requirement for the node to be fully synced up with @@ -4935,7 +4947,11 @@ def test_pay_blockheight_mismatch(node_factory, bitcoind): send, direct, recv = node_factory.line_graph(3, wait_for_announce=True, - opts={'may_reconnect': True}) + opts={'may_reconnect': True, + "allow-deprecated-apis": + True, + "xpay-handle-pay": + xpay_handle_pay}) sync_blockheight(bitcoind, [send, recv]) height = bitcoind.rpc.getblockchaininfo()['blocks'] @@ -5611,7 +5627,8 @@ def test_self_sendpay(node_factory): l1.rpc.sendpay([], inv['payment_hash'], label='selfpay', bolt11=inv['bolt11'], payment_secret=inv['payment_secret'], amount_msat='100000sat') -def test_strip_lightning_suffix_from_inv(node_factory): +@pytest.mark.parametrize("xpay_handle_pay", [True, False]) +def test_strip_lightning_suffix_from_inv(node_factory, xpay_handle_pay): """ Reproducer for [1] that pay an invoice with the `lightning:` prefix and then, will check if core lightning is able to strip it during @@ -5619,7 +5636,8 @@ def test_strip_lightning_suffix_from_inv(node_factory): [1] https://github.com/ElementsProject/lightning/issues/6207 """ - l1, l2 = node_factory.line_graph(2) + l1, l2 = node_factory.line_graph(2, opts={"allow-deprecated-apis": True, + "xpay-handle-pay": xpay_handle_pay}) inv = l2.rpc.invoice(40, "strip-lightning-prefix", "test to be able to strip the `lightning:` prefix.")["bolt11"] wait_for(lambda: only_one(l1.rpc.listpeerchannels(l2.info['id'])['channels'])['state'] == 'CHANNELD_NORMAL') diff --git a/tests/test_splice.py b/tests/test_splice.py index 2c20c2031a9c..8f53c5bd8446 100644 --- a/tests/test_splice.py +++ b/tests/test_splice.py @@ -220,7 +220,7 @@ def test_script_splice_msat(node_factory, bitcoind, chainparams): sent_msats = 1111 # purposely pay in msats inv = l2.rpc.invoice(sent_msats, '1', 'no_1') - l1.rpc.pay(inv['bolt11']) + l1.rpc.xpay(inv['bolt11']) initial_channel_balance -= sent_msats l1.rpc.splice(f"wallet -> {withdraw_amt}; {spliceamt} -> *:?", debug_log=True) @@ -325,7 +325,7 @@ def test_script_splice_msat_roundup(node_factory, bitcoind, chainparams): sent_msats = 1999 # purposely pay in msats inv = l2.rpc.invoice(sent_msats, '1', 'no_1') - l1.rpc.pay(inv['bolt11']) + l1.rpc.xpay(inv['bolt11']) initial_channel_balance -= sent_msats l1.rpc.splice(f"wallet -> {withdraw_amt}; {spliceamt} -> *:?", debug_log=True) @@ -432,12 +432,12 @@ def test_script_two_chan_splice_in(node_factory, bitcoind): # l2 should now have funds on their side to pay l1 inv = l1.rpc.invoice(10000, '1', 'no_1') - l2.rpc.pay(inv['bolt11']) + l2.rpc.xpay(inv['bolt11']) # l2 spliced extra funds into chan with l3 (but l3 still has 0 on their side) # Send a payment l2->l3 just to check for channel stability inv = l3.rpc.invoice(10000, '2', 'no_2') - l2.rpc.pay(inv['bolt11']) + l2.rpc.xpay(inv['bolt11']) @pytest.mark.openchannel('v1') @@ -448,7 +448,7 @@ def test_script_two_chan_splice_out(node_factory, bitcoind): # We need to get funds into l1 -> l2 channel so we can splice it out inv = l2.rpc.invoice(100000000, '1', 'no_1') - l1.rpc.pay(inv['bolt11']) + l1.rpc.xpay(inv['bolt11']) chan_id1 = l2.get_channel_id(l1) chan_id2 = l2.get_channel_id(l3) @@ -472,10 +472,10 @@ def test_script_two_chan_splice_out(node_factory, bitcoind): # no extra funds in channels but do some simple payments to test stability inv = l2.rpc.invoice(10000, '2', 'no_2') - l1.rpc.pay(inv['bolt11']) + l1.rpc.xpay(inv['bolt11']) inv = l3.rpc.invoice(10000, '3', 'no_3') - l2.rpc.pay(inv['bolt11']) + l2.rpc.xpay(inv['bolt11']) @pytest.mark.openchannel('v1') @@ -507,12 +507,12 @@ def test_script_two_chan_splice_inout(node_factory, bitcoind): # l2 should now have funds on their side to pay l1 inv = l1.rpc.invoice(10000, '2', 'no_2') - l2.rpc.pay(inv['bolt11']) + l2.rpc.xpay(inv['bolt11']) # l2 spliced extra funds into chan with l3 (but l3 still has 0 on their side) # Send a payment l2->l3 just to check for channel stability inv = l3.rpc.invoice(10000, '3', 'no_3') - l2.rpc.pay(inv['bolt11']) + l2.rpc.xpay(inv['bolt11']) @pytest.mark.openchannel('v1') diff --git a/tests/test_xpay.py b/tests/test_xpay.py index 4c4cec3f848a..99ebc9776af3 100644 --- a/tests/test_xpay.py +++ b/tests/test_xpay.py @@ -527,7 +527,8 @@ def test_xpay_takeover_null_parms(node_factory, executor): """Test passing through RPC a list of parameters some of which have null json value.""" l1, l2, l3 = node_factory.line_graph( - 3, wait_for_announce=True, opts={"xpay-handle-pay": True} + 3, wait_for_announce=True, opts={"xpay-handle-pay": True, + "allow-deprecated-apis": True} ) # Amount argument is null.