keysend: deprecate in favor of `xkeysend`.
What changed, and why it matters
This commit simply marks the old `keysend` RPC command as deprecated and tells users to switch to a newer command called `xkeysend`. It updates documentation, schemas, and test settings so that tests continue to work while the old command is still available. There is no security bug being fixed here.
No security action required. Users and integrators should plan migration from `keysend` to `xkeysend` before v27.03, when `keysend` is scheduled for removal.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change deprecates the keysend plugin command in favor of xkeysend. It adds deprecation metadata to the plugin command registration, inserts deprecation warnings into the JSON schema and generated schema, updates the deprecated-features developer guide, and adjusts tests to enable allow-deprecated-apis wherever they still exercise keysend. One test is switched from keysend to xkeysend. No vulnerability is patched; this is a routine API lifecycle change.
Changed components
plugins/keysend.cdoc/schemas/keysend.jsoncontrib/msggen/msggen/schema.jsondoc/developers-guide/deprecated-features.mdtests/test_cln_rs.pytests/test_misc.pytests/test_opening.pytests/test_pay.pytests/test_plugin.pyInspect captured patch +49 / −20
diff --git a/contrib/msggen/msggen/schema.json b/contrib/msggen/msggen/schema.json
index 8c86eeba..d65eaba7 100644
--- a/contrib/msggen/msggen/schema.json
+++ b/contrib/msggen/msggen/schema.json
@@ -17609,6 +17609,8 @@
"rpc": "keysend",
"title": "Send funds to a node without an invoice",
"description": [
+ "WARNING: deprecated: use *xkeysend* which uses askrene for better route calculation (and learns from previous payments).",
+ "",
"The **keysend** RPC command attempts to find a route to the given destination, and send the specified amount to it. Unlike the `pay` RPC command the `keysend` command does not require an invoice, instead it uses the `destination` node ID, and `amount` to find a route to the specified node.",
"",
"In order for the destination to be able to claim the payment, the `payment_key` is randomly generated by the sender and included in the encrypted payload for the destination. As a consequence there is not proof-of-payment, like there is with an invoice where the `payment_key` is generated on the destination, and the only way sender could have it is by sending a payment. Please ensure that this matches your use-case when using `keysend`.",
@@ -17826,6 +17828,7 @@
"Christian Decker [decker@blockstream.com](mailto:decker@blockstream.com) is mainly responsible."
],
"see_also": [
+ "lightning-xkeysend(7)",
"lightning-listpays(7)",
"lightning-decode(7)",
"lightning-listinvoices(7)",
diff --git a/doc/developers-guide/deprecated-features.md b/doc/developers-guide/deprecated-features.md
index 89f75a4c..e20885f0 100644
--- a/doc/developers-guide/deprecated-features.md
+++ b/doc/developers-guide/deprecated-features.md
@@ -30,6 +30,7 @@ privacy:
| pay | Command | v26.06 | v27.03 | Use the more sophisticated and effective "xpay" command instead (since v24.11). |
| paystatus | Command | v26.06 | v27.03 | Uses internal pay structures, doesn't work with xpay, doesn't work across restarts. |
| getroute | Command | v26.06 | v27.03 | Less flexible than `getroutes` which takes an actual fee budget and can do multiple paths at once. |
+| keysend | Command | v26.06 | v27.03 | Replaced by more powerful `xkeysend`. |
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/schemas/keysend.json b/doc/schemas/keysend.json
index fdb6615c..13d34a3b 100644
--- a/doc/schemas/keysend.json
+++ b/doc/schemas/keysend.json
@@ -4,6 +4,8 @@
"rpc": "keysend",
"title": "Send funds to a node without an invoice",
"description": [
+ "WARNING: deprecated: use *xkeysend* which uses askrene for better route calculation (and learns from previous payments).",
+ "",
"The **keysend** RPC command attempts to find a route to the given destination, and send the specified amount to it. Unlike the `pay` RPC command the `keysend` command does not require an invoice, instead it uses the `destination` node ID, and `amount` to find a route to the specified node.",
"",
"In order for the destination to be able to claim the payment, the `payment_key` is randomly generated by the sender and included in the encrypted payload for the destination. As a consequence there is not proof-of-payment, like there is with an invoice where the `payment_key` is generated on the destination, and the only way sender could have it is by sending a payment. Please ensure that this matches your use-case when using `keysend`.",
@@ -221,6 +223,7 @@
"Christian Decker [decker@blockstream.com](mailto:decker@blockstream.com) is mainly responsible."
],
"see_also": [
+ "lightning-xkeysend(7)",
"lightning-listpays(7)",
"lightning-decode(7)",
"lightning-listinvoices(7)",
diff --git a/plugins/keysend.c b/plugins/keysend.c
index 92246bec..99495373 100644
--- a/plugins/keysend.c
+++ b/plugins/keysend.c
@@ -296,7 +296,8 @@ static struct command_result *json_keysend(struct command *cmd, const char *buf,
static const struct plugin_command commands[] = {
{
"keysend",
- json_keysend
+ json_keysend,
+ "v26.06", "v27.03",
},
};
diff --git a/tests/test_cln_rs.py b/tests/test_cln_rs.py
index dc6083c0..ea76002c 100644
--- a/tests/test_cln_rs.py
+++ b/tests/test_cln_rs.py
@@ -328,6 +328,7 @@ def test_grpc_keysend_routehint(bitcoind, node_factory):
l1, l2, l3 = node_factory.line_graph(
3,
announce_channels=True, # Do not enforce scid-alias
+ opts={'allow-deprecated-apis': True}
)
bitcoind.generate_block(3)
sync_blockheight(bitcoind, [l1, l2, l3])
diff --git a/tests/test_misc.py b/tests/test_misc.py
index ab04418f..09a0974a 100644
--- a/tests/test_misc.py
+++ b/tests/test_misc.py
@@ -4865,7 +4865,8 @@ def test_preapprove(node_factory, bitcoind, preapprove):
@pytest.mark.parametrize("xkeysend", [False, True])
def test_preapprove_use(node_factory, bitcoind, xkeysend):
"""Test preapprove calls implicitly made by pay and keysend"""
- l1, l2 = node_factory.line_graph(2, opts=[{}, {'dev-hsmd-fail-preapprove': None}])
+ l1, l2 = node_factory.line_graph(2, opts=[{}, {'dev-hsmd-fail-preapprove': None,
+ 'allow-deprecated-apis': True}])
# Create some balance, make sure it's entirely settled.
l1.pay(l2, 200000000)
diff --git a/tests/test_opening.py b/tests/test_opening.py
index a61cba10..918e37bb 100644
--- a/tests/test_opening.py
+++ b/tests/test_opening.py
@@ -2143,7 +2143,6 @@ def test_zeroreserve(node_factory, bitcoind):
# Now do some drain tests on c1, as that should be drainable
# completely by l2 being the fundee
- l1.rpc.keysend(l2.info['id'], 10 * 7) # Something above dust for sure
l1.rpc.xkeysend(l2.info['id'], 10 * 7) # Something above dust for sure
l2.drain(l1)
diff --git a/tests/test_pay.py b/tests/test_pay.py
index 5059b0d0..eb52b860 100644
--- a/tests/test_pay.py
+++ b/tests/test_pay.py
@@ -3451,10 +3451,14 @@ def test_excluded_adjacent_routehint(node_factory, bitcoind):
@pytest.mark.parametrize("keysendcmd", ["keysend", "xkeysend"])
def test_keysend(node_factory, keysendcmd):
amt = 10000
+ if keysendcmd == 'keysend':
+ opts = {'allow-deprecated-apis': True}
+ else:
+ opts = {}
l1, l2, l3, l4 = node_factory.line_graph(
4,
wait_for_announce=True,
- opts=[{}, {}, {}, {'disable-plugin': 'keysend'}]
+ opts=[opts, {}, opts, {'disable-plugin': 'keysend'}]
)
if keysendcmd == 'xkeysend':
@@ -3508,19 +3512,24 @@ def test_keysend_strip_tlvs(node_factory, keysendcmd):
"""Use the extratlvs option to deliver a message with sphinx' TLV type, which keysend strips.
"""
amt = 10**7
+ opts = [
+ {
+ # Not needed, just for listconfigs test.
+ 'accept-htlc-tlv-type': [133773310, 99990],
+ "plugin": os.path.join(os.path.dirname(__file__), "plugins/sphinx-receiver.py"),
+ },
+ {
+ "plugin": os.path.join(os.path.dirname(__file__), "plugins/sphinx-receiver.py"),
+ },
+ ]
+ if keysendcmd == 'keysend':
+ opts[0]['allow-deprecated-apis'] = True
+ opts[1]['allow-deprecated-apis'] = True
+
l1, l2 = node_factory.line_graph(
2,
wait_for_announce=True,
- opts=[
- {
- # Not needed, just for listconfigs test.
- 'accept-htlc-tlv-type': [133773310, 99990],
- "plugin": os.path.join(os.path.dirname(__file__), "plugins/sphinx-receiver.py"),
- },
- {
- "plugin": os.path.join(os.path.dirname(__file__), "plugins/sphinx-receiver.py"),
- },
- ]
+ opts=opts,
)
if keysendcmd == 'xkeysend':
@@ -3583,7 +3592,7 @@ def test_keysend_routehint(node_factory):
"""Test whether we can deliver a keysend by adding a routehint on the cli
"""
amt = 10000
- l1, l2 = node_factory.line_graph(2, wait_for_announce=True)
+ l1, l2 = node_factory.line_graph(2, wait_for_announce=True, opts={'allow-deprecated-apis': True})
l3 = node_factory.get_node()
l2.connect(l3)
l2.fundchannel(l3, announce_channel=False)
@@ -3684,10 +3693,13 @@ def test_xkeysend_layer(node_factory):
@pytest.mark.parametrize("keysendcmd", ["keysend", "xkeysend"])
def test_keysend_maxfee(node_factory, keysendcmd):
+ opts = [{}, {'fee-base': 50, 'fee-per-satoshi': 0}, {}]
+ if keysendcmd == 'keysend':
+ opts[0]['allow-deprecated-apis'] = True
l1, l2, l3 = node_factory.line_graph(
3,
wait_for_announce=True,
- opts=[{}, {'fee-base': 50, 'fee-per-satoshi': 0}, {}]
+ opts=opts,
)
if keysendcmd == 'keysend':
@@ -3719,7 +3731,11 @@ def test_keysend_description_size_limit(node_factory, keysendcmd):
See common/bolt11.h: BOLT11_FIELD_BYTE_LIMIT.
"""
- l1, l2 = node_factory.line_graph(2, wait_for_announce=True)
+ if keysendcmd == 'keysend':
+ opts = {'allow-deprecated-apis': True}
+ else:
+ opts = {}
+ l1, l2 = node_factory.line_graph(2, wait_for_announce=True, opts=opts)
amt = 10000
prefix = 'keysend: '
base_len = len(prefix)
@@ -4083,7 +4099,11 @@ def test_listpay_result_with_paymod(node_factory, bitcoind, keysendcmd):
"""
amount_sat = 10 ** 6
- l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True)
+ if keysendcmd == 'keysend':
+ opts = {'allow-deprecated-apis': True}
+ else:
+ opts = {}
+ l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True, opts=opts)
invl2 = l2.rpc.invoice(amount_sat * 2, "inv_l2", "inv_l2")
l1.rpc.pay(invl2['bolt11'])
diff --git a/tests/test_plugin.py b/tests/test_plugin.py
index 367b5bd0..87708885 100644
--- a/tests/test_plugin.py
+++ b/tests/test_plugin.py
@@ -450,13 +450,13 @@ def test_pay_plugin(node_factory):
def test_keysend_plugin(node_factory):
- l1, l2 = node_factory.line_graph(2)
+ l1, l2 = node_factory.line_graph(2, opts={'allow-deprecated-apis': True})
with pytest.raises(RpcError, match=r'missing required parameter'):
l1.rpc.call('keysend')
# Make sure usage messages are present.
- msg = 'keysend destination amount_msat [label] [maxfeepercent] [retry_for] '\
+ msg = 'keysend (DEPRECATED!) destination amount_msat [label] [maxfeepercent] [retry_for] '\
'[maxdelay] [exemptfee] [extratlvs] [routehints] [maxfee]'
# We run with --developer:
msg += ' [dev_use_shadow]'
Why this scored 20/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.