2022-06-30 11:08:50 +02:00
|
|
|
from ephemeral_port_reserve import reserve
|
2022-01-14 18:56:00 +01:00
|
|
|
from fixtures import * # noqa: F401,F403
|
|
|
|
from pathlib import Path
|
2022-06-30 11:08:50 +02:00
|
|
|
from pyln.testing import node_pb2 as nodepb
|
|
|
|
from pyln.testing import node_pb2_grpc as nodegrpc
|
|
|
|
from pyln.testing import primitives_pb2 as primitivespb
|
2022-03-23 11:50:36 +01:00
|
|
|
from pyln.testing.utils import env, TEST_NETWORK, wait_for
|
2022-01-18 10:44:20 +01:00
|
|
|
import grpc
|
2022-01-14 18:56:00 +01:00
|
|
|
import pytest
|
2022-01-18 10:44:20 +01:00
|
|
|
import subprocess
|
2022-01-14 18:56:00 +01:00
|
|
|
|
2022-02-09 17:10:09 +01:00
|
|
|
# Skip the entire module if we don't have Rust.
|
|
|
|
pytestmark = pytest.mark.skipif(
|
|
|
|
env('RUST') != '1',
|
|
|
|
reason='RUST is not enabled skipping rust-dependent tests'
|
|
|
|
)
|
2022-01-14 18:56:00 +01:00
|
|
|
|
|
|
|
|
2022-03-29 02:46:33 +02:00
|
|
|
def wait_for_grpc_start(node):
|
|
|
|
"""This can happen before "public key" which start() swallows"""
|
|
|
|
wait_for(lambda: node.daemon.is_in_log(r'serving grpc on 0.0.0.0:'))
|
|
|
|
|
|
|
|
|
2022-01-14 18:56:00 +01:00
|
|
|
def test_rpc_client(node_factory):
|
|
|
|
l1 = node_factory.get_node()
|
|
|
|
bin_path = Path.cwd() / "target" / "debug" / "examples" / "cln-rpc-getinfo"
|
|
|
|
rpc_path = Path(l1.daemon.lightning_dir) / TEST_NETWORK / "lightning-rpc"
|
|
|
|
out = subprocess.check_output([bin_path, rpc_path], stderr=subprocess.STDOUT)
|
|
|
|
assert(b'0266e4598d1d3c415f572a8488830b60f7e744ed9235eb0b1ba93283b315c03518' in out)
|
2022-01-24 13:19:45 +01:00
|
|
|
|
|
|
|
|
|
|
|
def test_plugin_start(node_factory):
|
2022-02-07 11:00:08 +01:00
|
|
|
"""Start a minimal plugin and ensure it is well-behaved
|
|
|
|
"""
|
|
|
|
bin_path = Path.cwd() / "target" / "debug" / "examples" / "cln-plugin-startup"
|
2022-01-21 18:09:16 +01:00
|
|
|
l1 = node_factory.get_node(options={"plugin": str(bin_path), 'test-option': 31337})
|
2022-02-23 19:00:25 +01:00
|
|
|
l2 = node_factory.get_node()
|
2022-02-07 11:00:08 +01:00
|
|
|
|
2022-01-18 10:44:20 +01:00
|
|
|
# The plugin should be in the list of active plugins
|
|
|
|
plugins = l1.rpc.plugin('list')['plugins']
|
|
|
|
assert len([p for p in plugins if 'cln-plugin-startup' in p['name'] and p['active']]) == 1
|
|
|
|
|
2022-02-07 11:00:08 +01:00
|
|
|
cfg = l1.rpc.listconfigs()
|
|
|
|
p = cfg['plugins'][0]
|
|
|
|
p['path'] = None # The path is host-specific, so blank it.
|
|
|
|
expected = {
|
|
|
|
'name': 'cln-plugin-startup',
|
|
|
|
'options': {
|
2022-01-21 18:09:16 +01:00
|
|
|
'test-option': 31337
|
2022-02-07 11:00:08 +01:00
|
|
|
},
|
|
|
|
'path': None
|
|
|
|
}
|
|
|
|
assert expected == p
|
2022-02-21 18:31:29 +01:00
|
|
|
|
|
|
|
# Now check that the `testmethod was registered ok
|
|
|
|
l1.rpc.help("testmethod") == {
|
|
|
|
'help': [
|
|
|
|
{
|
|
|
|
'command': 'testmethod ',
|
|
|
|
'category': 'plugin',
|
|
|
|
'description': 'This is a test',
|
|
|
|
'verbose': 'This is a test'
|
|
|
|
}
|
|
|
|
],
|
|
|
|
'format-hint': 'simple'
|
|
|
|
}
|
|
|
|
|
|
|
|
assert l1.rpc.testmethod() == "Hello"
|
2022-02-23 19:00:25 +01:00
|
|
|
|
|
|
|
l1.connect(l2)
|
2022-02-25 14:36:39 +01:00
|
|
|
l1.daemon.wait_for_log(r'Got a connect hook call')
|
2022-02-23 19:00:25 +01:00
|
|
|
l1.daemon.wait_for_log(r'Got a connect notification')
|
2022-01-20 15:09:21 +01:00
|
|
|
|
|
|
|
|
|
|
|
def test_grpc_connect(node_factory):
|
|
|
|
"""Attempts to connect to the grpc interface and call getinfo"""
|
2022-04-01 06:12:45 +02:00
|
|
|
# These only exist if we have rust!
|
|
|
|
|
2022-03-09 17:30:36 +01:00
|
|
|
grpc_port = reserve()
|
2022-04-12 10:17:37 +02:00
|
|
|
l1 = node_factory.get_node(options={"grpc-port": str(grpc_port)})
|
2022-01-20 15:09:21 +01:00
|
|
|
|
|
|
|
p = Path(l1.daemon.lightning_dir) / TEST_NETWORK
|
|
|
|
cert_path = p / "client.pem"
|
|
|
|
key_path = p / "client-key.pem"
|
|
|
|
ca_cert_path = p / "ca.pem"
|
|
|
|
creds = grpc.ssl_channel_credentials(
|
|
|
|
root_certificates=ca_cert_path.open('rb').read(),
|
|
|
|
private_key=key_path.open('rb').read(),
|
|
|
|
certificate_chain=cert_path.open('rb').read()
|
|
|
|
)
|
|
|
|
|
2022-03-29 02:46:33 +02:00
|
|
|
wait_for_grpc_start(l1)
|
2022-01-20 15:09:21 +01:00
|
|
|
channel = grpc.secure_channel(
|
2022-03-09 17:30:36 +01:00
|
|
|
f"localhost:{grpc_port}",
|
2022-01-20 15:09:21 +01:00
|
|
|
creds,
|
|
|
|
options=(('grpc.ssl_target_name_override', 'cln'),)
|
|
|
|
)
|
2022-06-30 11:08:50 +02:00
|
|
|
stub = nodegrpc.NodeStub(channel)
|
2022-01-20 15:09:21 +01:00
|
|
|
|
|
|
|
response = stub.Getinfo(nodepb.GetinfoRequest())
|
|
|
|
print(response)
|
|
|
|
|
|
|
|
response = stub.ListFunds(nodepb.ListfundsRequest())
|
|
|
|
print(response)
|
|
|
|
|
2022-04-01 06:12:45 +02:00
|
|
|
inv = stub.Invoice(nodepb.InvoiceRequest(
|
2022-06-30 11:08:50 +02:00
|
|
|
amount_msat=primitivespb.AmountOrAny(any=True),
|
2022-04-01 06:12:45 +02:00
|
|
|
description="hello",
|
|
|
|
label="lbl1",
|
|
|
|
preimage=b"\x00" * 32,
|
|
|
|
cltv=24
|
|
|
|
))
|
|
|
|
print(inv)
|
|
|
|
|
2022-04-01 06:13:35 +02:00
|
|
|
rates = stub.Feerates(nodepb.FeeratesRequest(style='PERKB'))
|
|
|
|
print(rates)
|
|
|
|
|
2022-04-01 06:13:34 +02:00
|
|
|
# Test a failing RPC call, so we know that errors are returned correctly.
|
|
|
|
with pytest.raises(Exception, match=r'Duplicate label'):
|
|
|
|
# This request creates a label collision
|
|
|
|
stub.Invoice(nodepb.InvoiceRequest(
|
2022-06-30 11:08:50 +02:00
|
|
|
amount_msat=primitivespb.AmountOrAny(amount=primitivespb.Amount(msat=12345)),
|
2022-04-01 06:13:34 +02:00
|
|
|
description="hello",
|
|
|
|
label="lbl1",
|
|
|
|
))
|
|
|
|
|
2022-01-20 15:09:21 +01:00
|
|
|
|
|
|
|
def test_grpc_generate_certificate(node_factory):
|
|
|
|
"""Test whether we correctly generate the certificates.
|
|
|
|
|
|
|
|
- If we have no certs, we need to generate them all
|
|
|
|
- If we have certs, we they should just get loaded
|
|
|
|
- If we delete one cert or its key it should get regenerated.
|
|
|
|
"""
|
2022-03-09 17:30:36 +01:00
|
|
|
grpc_port = reserve()
|
2022-01-20 15:09:21 +01:00
|
|
|
l1 = node_factory.get_node(options={
|
2022-03-09 17:30:36 +01:00
|
|
|
"grpc-port": str(grpc_port),
|
2022-01-20 15:09:21 +01:00
|
|
|
}, start=False)
|
|
|
|
|
|
|
|
p = Path(l1.daemon.lightning_dir) / TEST_NETWORK
|
2022-01-20 15:21:07 +01:00
|
|
|
files = [p / f for f in [
|
|
|
|
'ca.pem',
|
|
|
|
'ca-key.pem',
|
|
|
|
'client.pem',
|
|
|
|
'client-key.pem',
|
|
|
|
'server-key.pem',
|
|
|
|
'server.pem',
|
|
|
|
]]
|
2022-01-20 15:09:21 +01:00
|
|
|
|
|
|
|
# Before starting no files exist.
|
|
|
|
assert [f.exists() for f in files] == [False] * len(files)
|
|
|
|
|
|
|
|
l1.start()
|
|
|
|
assert [f.exists() for f in files] == [True] * len(files)
|
|
|
|
|
|
|
|
# The files exist, restarting should not change them
|
|
|
|
contents = [f.open().read() for f in files]
|
|
|
|
l1.restart()
|
|
|
|
assert contents == [f.open().read() for f in files]
|
|
|
|
|
|
|
|
# Now we delete the last file, we should regenerate it as well as its key
|
|
|
|
files[-1].unlink()
|
|
|
|
l1.restart()
|
|
|
|
assert contents[-2] != files[-2].open().read()
|
|
|
|
assert contents[-1] != files[-1].open().read()
|
2022-01-20 15:21:07 +01:00
|
|
|
|
|
|
|
|
2022-03-23 11:50:36 +01:00
|
|
|
def test_grpc_no_auto_start(node_factory):
|
|
|
|
"""Ensure that we do not start cln-grpc unless a port is configured.
|
|
|
|
"""
|
2022-04-12 10:17:37 +02:00
|
|
|
l1 = node_factory.get_node()
|
2022-03-23 11:50:36 +01:00
|
|
|
|
2022-04-08 05:41:12 +02:00
|
|
|
wait_for(lambda: [p for p in l1.rpc.plugin('list')['plugins'] if 'cln-grpc' in p['name']] == [])
|
|
|
|
assert l1.daemon.is_in_log(r'plugin-cln-grpc: Killing plugin: disabled itself at init')
|
2022-03-23 11:50:36 +01:00
|
|
|
|
|
|
|
|
2022-01-20 15:21:07 +01:00
|
|
|
def test_grpc_wrong_auth(node_factory):
|
|
|
|
"""An mTLS client certificate should only be usable with its node
|
|
|
|
|
|
|
|
We create two instances, each generates its own certs and keys,
|
|
|
|
and then we try to cross the wires.
|
|
|
|
"""
|
2022-04-01 06:12:45 +02:00
|
|
|
# These only exist if we have rust!
|
|
|
|
|
2022-03-09 17:30:36 +01:00
|
|
|
grpc_port = reserve()
|
|
|
|
l1, l2 = node_factory.get_nodes(2, opts={
|
|
|
|
"start": False,
|
|
|
|
"grpc-port": str(grpc_port),
|
|
|
|
})
|
2022-01-20 15:21:07 +01:00
|
|
|
l1.start()
|
2022-03-29 02:46:33 +02:00
|
|
|
wait_for_grpc_start(l1)
|
2022-01-20 15:21:07 +01:00
|
|
|
|
|
|
|
def connect(node):
|
|
|
|
p = Path(node.daemon.lightning_dir) / TEST_NETWORK
|
|
|
|
cert, key, ca = [f.open('rb').read() for f in [
|
|
|
|
p / 'client.pem',
|
|
|
|
p / 'client-key.pem',
|
|
|
|
p / "ca.pem"]]
|
|
|
|
|
|
|
|
creds = grpc.ssl_channel_credentials(
|
|
|
|
root_certificates=ca,
|
|
|
|
private_key=key,
|
|
|
|
certificate_chain=cert,
|
|
|
|
)
|
|
|
|
|
|
|
|
channel = grpc.secure_channel(
|
2022-03-09 17:30:36 +01:00
|
|
|
f"localhost:{grpc_port}",
|
2022-01-20 15:21:07 +01:00
|
|
|
creds,
|
|
|
|
options=(('grpc.ssl_target_name_override', 'cln'),)
|
|
|
|
)
|
2022-06-30 11:08:50 +02:00
|
|
|
return nodegrpc.NodeStub(channel)
|
2022-01-20 15:21:07 +01:00
|
|
|
|
|
|
|
stub = connect(l1)
|
|
|
|
# This should work, it's the correct node
|
|
|
|
stub.Getinfo(nodepb.GetinfoRequest())
|
|
|
|
|
|
|
|
l1.stop()
|
|
|
|
l2.start()
|
2022-03-29 02:46:33 +02:00
|
|
|
wait_for_grpc_start(l2)
|
2022-01-20 15:21:07 +01:00
|
|
|
|
|
|
|
# This should not work, it's a different node
|
|
|
|
with pytest.raises(Exception, match=r'Socket closed|StatusCode.UNAVAILABLE'):
|
|
|
|
stub.Getinfo(nodepb.GetinfoRequest())
|
|
|
|
|
|
|
|
# Now load the correct ones and we should be good to go
|
|
|
|
stub = connect(l2)
|
|
|
|
stub.Getinfo(nodepb.GetinfoRequest())
|