Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion contrib/msggen/msggen/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions doc/developers-guide/deprecated-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
4 changes: 2 additions & 2 deletions doc/lightningd-config.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -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*]

Expand Down
6 changes: 5 additions & 1 deletion doc/schemas/listconfigs.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
26 changes: 18 additions & 8 deletions plugins/libplugin.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions plugins/libplugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
3 changes: 2 additions & 1 deletion plugins/pay.c
Original file line number Diff line number Diff line change
Expand Up @@ -1168,7 +1168,8 @@ static const struct plugin_command commands[] = {
},
{
"pay",
json_pay
json_pay,
"v26.06", "v27.03",
},
};

Expand Down
11 changes: 8 additions & 3 deletions plugins/xpay/xpay.c
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
5 changes: 3 additions & 2 deletions tests/test_clnrest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
)
Expand Down
46 changes: 32 additions & 14 deletions tests/test_pay.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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']
Expand Down Expand Up @@ -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]
Expand All @@ -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)
Expand Down Expand Up @@ -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'])
Expand All @@ -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)

Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand All @@ -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']
Expand Down Expand Up @@ -5611,15 +5627,17 @@ 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:<bolt11|bolt12>`
prefix and then, will check if core lightning is able to strip it during
list `listpays` command.

[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')

Expand Down
18 changes: 9 additions & 9 deletions tests/test_splice.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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')
Expand All @@ -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)
Expand All @@ -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')
Expand Down Expand Up @@ -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')
Expand Down
3 changes: 2 additions & 1 deletion tests/test_xpay.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading