Files
palladum-lightning/tests/test_cln_lsps.py
Peter Neuroth 719fb2ce52 plugin: change method name of lsps-jitchannel
The original method name was lsps-lsps2-invoice but I somehow messed it
up and renamed during a rebase.

Changelog-Changed: lsps-jitchannel is now lsps-lsps2-invoice

Signed-off-by: Peter Neuroth <pet.v.ne@gmail.com>
2025-11-21 13:48:29 +10:30

354 lines
10 KiB
Python

from fixtures import * # noqa: F401,F403
from pyln.testing.utils import RUST
from utils import only_one
import os
import pytest
import unittest
RUST_PROFILE = os.environ.get("RUST_PROFILE", "debug")
def test_lsps_service_disabled(node_factory):
"""By default we disable the LSPS service plugin.
It should only be enabled if we explicitly set the config option
`lsps-service=True`.
"""
l1 = node_factory.get_node(1)
l1.daemon.is_in_log("`lsps-service` not enabled")
@unittest.skipUnless(RUST, "RUST is not enabled")
def test_lsps0_listprotocols(node_factory):
l1, l2 = node_factory.get_nodes(
2,
opts=[
{"experimental-lsps-client": None},
{
"experimental-lsps2-service": None,
"experimental-lsps2-promise-secret": "0" * 64,
},
],
)
# We don't need a channel to query for lsps services
node_factory.join_nodes([l1, l2], fundchannel=False)
res = l1.rpc.lsps_listprotocols(lsp_id=l2.info["id"])
assert res
def test_lsps2_enabled(node_factory):
l1, l2 = node_factory.get_nodes(
2,
opts=[
{"experimental-lsps-client": None},
{
"experimental-lsps2-service": None,
"experimental-lsps2-promise-secret": "0" * 64,
},
],
)
node_factory.join_nodes([l1, l2], fundchannel=False)
res = l1.rpc.lsps_listprotocols(lsp_id=l2.info["id"])
assert res["protocols"] == [2]
def test_lsps2_getinfo(node_factory):
plugin = os.path.join(os.path.dirname(__file__), "plugins/lsps2_policy.py")
l1, l2 = node_factory.get_nodes(
2,
opts=[
{"experimental-lsps-client": None},
{
"experimental-lsps2-service": None,
"experimental-lsps2-promise-secret": "0" * 64,
"plugin": plugin,
},
],
)
node_factory.join_nodes([l1, l2], fundchannel=False)
res = l1.rpc.lsps_lsps2_getinfo(lsp_id=l2.info["id"])
assert res["opening_fee_params_menu"]
def test_lsps2_buy(node_factory):
# We need a policy service to fetch from.
plugin = os.path.join(os.path.dirname(__file__), "plugins/lsps2_policy.py")
l1, l2 = node_factory.get_nodes(
2,
opts=[
{"experimental-lsps-client": None},
{
"experimental-lsps2-service": None,
"experimental-lsps2-promise-secret": "0" * 64,
"plugin": plugin,
},
],
)
# We don't need a channel to query for lsps services
node_factory.join_nodes([l1, l2], fundchannel=False)
res = l1.rpc.lsps_lsps2_getinfo(lsp_id=l2.info["id"])
params = res["opening_fee_params_menu"][0]
res = l1.rpc.lsps_lsps2_buy(lsp_id=l2.info["id"], opening_fee_params=params)
assert res
def test_lsps2_buyjitchannel_no_mpp_var_invoice(node_factory, bitcoind):
"""Tests the creation of a "Just-In-Time-Channel" (jit-channel).
At the beginning we have the following situation where l2 acts as the LSP
(LSP)
l1 l2----l3
l1 now wants to get a channel from l2 via the lsps2 jit-channel protocol:
- l1 requests a new jit channel form l2
- l1 creates an invoice based on the opening fee parameters it got from l2
- l3 pays the invoice
- l2 opens a channel to l1 and forwards the payment (deducted by a fee)
eventualy this will result in the following situation
(LSP)
l1----l2----l3
"""
# We need a policy service to fetch from.
plugin = os.path.join(os.path.dirname(__file__), "plugins/lsps2_policy.py")
l1, l2, l3 = node_factory.get_nodes(
3,
opts=[
{"experimental-lsps-client": None},
{
"experimental-lsps2-service": None,
"experimental-lsps2-promise-secret": "0" * 64,
"plugin": plugin,
"fee-base": 0, # We are going to deduct our fee anyways,
"fee-per-satoshi": 0, # We are going to deduct our fee anyways,
},
{},
],
)
# Give the LSP some funds to open jit-channels
l2.fundwallet(1_000_000)
node_factory.join_nodes([l3, l2], fundchannel=True, wait_for_announce=True)
node_factory.join_nodes([l1, l2], fundchannel=False)
chanid = only_one(l3.rpc.listpeerchannels(l2.info["id"])["channels"])[
"short_channel_id"
]
inv = l1.rpc.lsps_lsps2_invoice(
lsp_id=l2.info["id"],
amount_msat="any",
description="lsp-jit-channel-0",
label="lsp-jit-channel-0",
)
assert inv
dec = l3.rpc.decode(inv["bolt11"])
assert dec
routehint = only_one(only_one(dec["routes"]))
amt = 10000000
route = [
{"amount_msat": amt, "id": l2.info["id"], "delay": 14, "channel": chanid},
{
"amount_msat": amt,
"id": l1.info["id"],
"delay": 8,
"channel": routehint["short_channel_id"],
},
]
l3.rpc.sendpay(
route,
dec["payment_hash"],
payment_secret=inv["payment_secret"],
bolt11=inv["bolt11"],
partid=0,
)
res = l3.rpc.waitsendpay(dec["payment_hash"])
assert res["payment_preimage"]
# l1 should have gotten a jit-channel.
chs = l1.rpc.listpeerchannels()["channels"]
assert len(chs) == 1
# Check that the client cleaned up after themselves.
assert l1.rpc.listdatastore(["lsps"]) == {"datastore": []}
def test_lsps2_buyjitchannel_mpp_fixed_invoice(node_factory, bitcoind):
"""Tests the creation of a "Just-In-Time-Channel" (jit-channel).
At the beginning we have the following situation where l2 acts as the LSP
(LSP)
l1 l2----l3
l1 now wants to get a channel from l2 via the lsps2 jit-channel protocol:
- l1 requests a new jit channel form l2
- l1 creates an invoice based on the opening fee parameters it got from l2
- l3 pays the invoice
- l2 opens a channel to l1 and forwards the payment (deducted by a fee)
eventualy this will result in the following situation
(LSP)
l1----l2----l3
"""
# A mock for lsps2 mpp payments, contains the policy plugin as well.
plugin = os.path.join(os.path.dirname(__file__), "plugins/lsps2_service_mock.py")
l1, l2, l3 = node_factory.get_nodes(
3,
opts=[
{"experimental-lsps-client": None},
{
"experimental-lsps2-service": None,
"experimental-lsps2-promise-secret": "0" * 64,
"plugin": plugin,
"fee-base": 0, # We are going to deduct our fee anyways,
"fee-per-satoshi": 0, # We are going to deduct our fee anyways,
},
{},
],
)
# Give the LSP some funds to open jit-channels
l2.fundwallet(1_000_000)
node_factory.join_nodes([l3, l2], fundchannel=True, wait_for_announce=True)
node_factory.join_nodes([l1, l2], fundchannel=False)
chanid = only_one(l3.rpc.listpeerchannels(l2.info["id"])["channels"])[
"short_channel_id"
]
amt = 10_000_000
inv = l1.rpc.lsps_lsps2_invoice(
lsp_id=l2.info["id"],
amount_msat=f"{amt}msat",
description="lsp-jit-channel-0",
label="lsp-jit-channel-0",
)
dec = l3.rpc.decode(inv["bolt11"])
l2.rpc.setuplsps2service(
peer_id=l1.info["id"], channel_cap=100_000, opening_fee_msat=1000_000
)
routehint = only_one(only_one(dec["routes"]))
parts = 10
route_part = [
{
"amount_msat": amt // parts,
"id": l2.info["id"],
"delay": routehint["cltv_expiry_delta"] + 6,
"channel": chanid,
},
{
"amount_msat": amt // parts,
"id": l1.info["id"],
"delay": 6,
"channel": routehint["short_channel_id"],
},
]
# MPP-payment of fixed amount
for partid in range(1, parts + 1):
r = l3.rpc.sendpay(
route_part,
dec["payment_hash"],
payment_secret=inv["payment_secret"],
bolt11=inv["bolt11"],
amount_msat=f"{amt}msat",
groupid=1,
partid=partid,
)
assert r
res = l3.rpc.waitsendpay(dec["payment_hash"], partid=parts, groupid=1)
assert res["payment_preimage"]
# l1 should have gotten a jit-channel.
chs = l1.rpc.listpeerchannels()["channels"]
assert len(chs) == 1
# Check that the client cleaned up after themselves.
assert l1.rpc.listdatastore("lsps") == {"datastore": []}
def test_lsps2_non_approved_zero_conf(node_factory, bitcoind):
"""Checks that we don't allow zerof_conf channels from an LSP if we did
not approve it first.
"""
# We need a policy service to fetch from.
plugin = os.path.join(os.path.dirname(__file__), "plugins/lsps2_policy.py")
l1, l2, l3 = node_factory.get_nodes(
3,
opts=[
{"experimental-lsps-client": None},
{
"experimental-lsps2-service": None,
"experimental-lsps2-promise-secret": "0" * 64,
"plugin": plugin,
"fee-base": 0, # We are going to deduct our fee anyways,
"fee-per-satoshi": 0, # We are going to deduct our fee anyways,
},
{"disable-mpp": None},
],
)
# Give the LSP some funds to open jit-channels
l2.fundwallet(1_000_000)
node_factory.join_nodes([l3, l2], fundchannel=True, wait_for_announce=True)
node_factory.join_nodes([l1, l2], fundchannel=False)
fee_opt = l1.rpc.lsps_lsps2_getinfo(lsp_id=l2.info["id"])[
"opening_fee_params_menu"
][0]
buy_res = l1.rpc.lsps_lsps2_buy(lsp_id=l2.info["id"], opening_fee_params=fee_opt)
hint = [
[
{
"id": l2.info["id"],
"short_channel_id": buy_res["jit_channel_scid"],
"fee_base_msat": 0,
"fee_proportional_millionths": 0,
"cltv_expiry_delta": buy_res["lsp_cltv_expiry_delta"],
}
]
]
bolt11 = l1.dev_invoice(
amount_msat="any",
description="lsp-invoice-1",
label="lsp-invoice-1",
dev_routes=hint,
)["bolt11"]
with pytest.raises(ValueError):
l3.rpc.pay(bolt11, amount_msat=10000000)
# l1 shouldn't have a new channel.
chs = l1.rpc.listpeerchannels()["channels"]
assert len(chs) == 0