354 lines
10 KiB
Python
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_jitchannel(
|
|
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_jitchannel(
|
|
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
|