Skip to content

Commit b8bcc7d

Browse files
cdeckerrustyrussell
authored andcommitted
pytest: Add a new RPC interface to talk to grpc
This allows us to re-use existing tests (assuming the call and fields are covered by `cln-rpc` and `cln-grpc`) to test the full roundtrip from test over the grpc interface to the json-rpc interface and back again. You can switch to the grpc interface by setting the `CLN_TEST_GRPC` environment variable to 1, but for now only very few shims are implemented (due to the non-generated nature of LightningRpc).
1 parent 5307586 commit b8bcc7d

File tree

3 files changed

+145
-5
lines changed

3 files changed

+145
-5
lines changed
+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
"""A drop-in replacement for the JSON-RPC LightningRpc
2+
"""
3+
4+
from pyln.testing import node_pb2_grpc as pbgrpc
5+
from pyln.testing import node_pb2 as pb
6+
import grpc
7+
import json
8+
from google.protobuf.json_format import MessageToJson
9+
from pyln.testing import grpc2py
10+
11+
12+
DUMMY_CA_PEM = b"""-----BEGIN CERTIFICATE-----
13+
MIIBcTCCARigAwIBAgIJAJhah1bqO05cMAoGCCqGSM49BAMCMBYxFDASBgNVBAMM
14+
C2NsbiBSb290IENBMCAXDTc1MDEwMTAwMDAwMFoYDzQwOTYwMTAxMDAwMDAwWjAW
15+
MRQwEgYDVQQDDAtjbG4gUm9vdCBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA
16+
BPF4JrGsOsksgsYM1NNdUdLESwOxkzyD75Rnj/g7sFEVYXewcmyB3MRGCBx2a3/7
17+
ft2Xu2ED6WigajaHlnSvfUyjTTBLMBkGA1UdEQQSMBCCA2NsboIJbG9jYWxob3N0
18+
MB0GA1UdDgQWBBRcTjvqVodamGirO6sX1rOR02LwXzAPBgNVHRMBAf8EBTADAQH/
19+
MAoGCCqGSM49BAMCA0cAMEQCICDvV5iFw/nmJdl6rlEEGAdBdZqjxD0tV6U/FvuL
20+
7PycAiASEMtsFtpfiUvxveBkOGt7AN32GP/Z75l+GhYXh7L1ig==
21+
-----END CERTIFICATE-----"""
22+
23+
24+
DUMMY_CA_KEY_PEM = b"""-----BEGIN PRIVATE KEY-----
25+
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgqbU7LQsRcvmI5vE5
26+
MBBNK3imhIU2jmAczgvLuBi/Ys+hRANCAATxeCaxrDrJLILGDNTTXVHSxEsDsZM8
27+
g++UZ4/4O7BRFWF3sHJsgdzERggcdmt/+37dl7thA+looGo2h5Z0r31M
28+
-----END PRIVATE KEY-----"""
29+
30+
31+
DUMMY_CLIENT_KEY_PEM = b"""-----BEGIN PRIVATE KEY-----
32+
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgIEdQyKso8PaD1kiz
33+
xxFEcKiTvTg+bej4Nc/GqnXipcGhRANCAARGoUNSnWx1qgt4RiVG8tOMX1vpKvhr
34+
OLcUJ92T++kIFZchZvcTXwnlNiTAQg3ukL+RYyG5Q1PaYrYRVlOtl1T0
35+
-----END PRIVATE KEY-----"""
36+
37+
38+
DUMMY_CLIENT_PEM = b"""-----BEGIN CERTIFICATE-----
39+
MIIBRDCB7KADAgECAgkA8SsXq7IZfi8wCgYIKoZIzj0EAwIwFjEUMBIGA1UEAwwL
40+
Y2xuIFJvb3QgQ0EwIBcNNzUwMTAxMDAwMDAwWhgPNDA5NjAxMDEwMDAwMDBaMBox
41+
GDAWBgNVBAMMD2NsbiBncnBjIFNlcnZlcjBZMBMGByqGSM49AgEGCCqGSM49AwEH
42+
A0IABEahQ1KdbHWqC3hGJUby04xfW+kq+Gs4txQn3ZP76QgVlyFm9xNfCeU2JMBC
43+
De6Qv5FjIblDU9pithFWU62XVPSjHTAbMBkGA1UdEQQSMBCCA2NsboIJbG9jYWxo
44+
b3N0MAoGCCqGSM49BAMCA0cAMEQCICTU/YAs35cb6DRdZNzO1YbEt77uEjcqMRca
45+
Hh6kK99RAiAKOQOkGnoAICjBmBJeC/iC4/+hhhkWZtFgbC3Jg5JD0w==
46+
-----END CERTIFICATE-----"""
47+
48+
49+
class LightningGrpc(object):
50+
def __init__(
51+
self,
52+
host: str,
53+
port: int,
54+
root_certificates: bytes = DUMMY_CA_PEM,
55+
private_key: bytes = DUMMY_CLIENT_KEY_PEM,
56+
certificate_chain: bytes = DUMMY_CLIENT_PEM,
57+
):
58+
self.credentials = grpc.ssl_channel_credentials(
59+
root_certificates=root_certificates,
60+
private_key=private_key,
61+
certificate_chain=certificate_chain,
62+
)
63+
self.channel = grpc.secure_channel(
64+
f"{host}:{port}",
65+
self.credentials,
66+
options=(("grpc.ssl_target_name_override", "cln"),),
67+
)
68+
self.stub = pbgrpc.NodeStub(self.channel)
69+
70+
def getinfo(self):
71+
return grpc2py.getinfo2py(
72+
self.stub.Getinfo(pb.GetinfoRequest())
73+
)
74+
75+
def connect(self, peer_id, host=None, port=None):
76+
"""
77+
Connect to {peer_id} at {host} and {port}.
78+
"""
79+
payload = pb.ConnectRequest(
80+
id=peer_id,
81+
host=host,
82+
port=port
83+
)
84+
return grpc2py.connect2py(self.stub.ConnectPeer(payload))

contrib/pyln-testing/pyln/testing/utils.py

+60-4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from decimal import Decimal
1111
from pyln.client import LightningRpc
1212
from pyln.client import Millisatoshi
13+
from pyln.testing import grpc
1314

1415
import ephemeral_port_reserve # type: ignore
1516
import json
@@ -538,7 +539,15 @@ def getnewaddress(self):
538539

539540

540541
class LightningD(TailableProc):
541-
def __init__(self, lightning_dir, bitcoindproxy, port=9735, random_hsm=False, node_id=0):
542+
def __init__(
543+
self,
544+
lightning_dir,
545+
bitcoindproxy,
546+
port=9735,
547+
random_hsm=False,
548+
node_id=0,
549+
grpc_port=None
550+
):
542551
# We handle our own version of verbose, below.
543552
TailableProc.__init__(self, lightning_dir, verbose=False)
544553
self.executable = 'lightningd'
@@ -564,6 +573,9 @@ def __init__(self, lightning_dir, bitcoindproxy, port=9735, random_hsm=False, no
564573
'bitcoin-datadir': lightning_dir,
565574
}
566575

576+
if grpc_port is not None:
577+
opts['grpc-port'] = grpc_port
578+
567579
for k, v in opts.items():
568580
self.opts[k] = v
569581

@@ -693,19 +705,22 @@ def __init__(self, node_id, lightning_dir, bitcoind, executor, valgrind, may_fai
693705
self.allow_bad_gossip = allow_bad_gossip
694706
self.allow_warning = allow_warning
695707
self.db = db
708+
self.lightning_dir = Path(lightning_dir)
696709

697710
# Assume successful exit
698711
self.rc = 0
699712

700-
socket_path = os.path.join(lightning_dir, TEST_NETWORK, "lightning-rpc").format(node_id)
701-
self.rpc = PrettyPrintingLightningRpc(socket_path, self.executor, jsonschemas=jsonschemas)
713+
# Ensure we have an RPC we can use to talk to the node
714+
self._create_rpc(jsonschemas)
702715

703716
self.gossip_store = GossipStore(Path(lightning_dir, TEST_NETWORK, "gossip_store"))
704717

705718
self.daemon = LightningD(
706719
lightning_dir, bitcoindproxy=bitcoind.get_proxy(),
707-
port=port, random_hsm=random_hsm, node_id=node_id
720+
port=port, random_hsm=random_hsm, node_id=node_id,
721+
grpc_port=self.grpc_port,
708722
)
723+
709724
# If we have a disconnect string, dump it to a file for daemon.
710725
if disconnect:
711726
self.daemon.disconnect_file = os.path.join(lightning_dir, TEST_NETWORK, "dev_disconnect")
@@ -751,6 +766,47 @@ def __init__(self, node_id, lightning_dir, bitcoind, executor, valgrind, may_fai
751766
if SLOW_MACHINE:
752767
self.daemon.cmd_prefix += ['--read-inline-info=no']
753768

769+
def _create_rpc(self, jsonschemas):
770+
"""Prepares anything related to the RPC.
771+
"""
772+
if os.environ.get('CLN_TEST_GRPC') == '1':
773+
logging.info("Switching to GRPC based RPC for tests")
774+
self._create_grpc_rpc()
775+
else:
776+
self._create_jsonrpc_rpc(jsonschemas)
777+
778+
def _create_grpc_rpc(self):
779+
self.grpc_port = reserve_unused_port()
780+
d = self.lightning_dir / TEST_NETWORK
781+
d.mkdir(parents=True, exist_ok=True)
782+
783+
# Copy all the certificates and keys into place:
784+
with (d / "ca.pem").open(mode='wb') as f:
785+
f.write(grpc.DUMMY_CA_PEM)
786+
787+
with (d / "ca-key.pem").open(mode='wb') as f:
788+
f.write(grpc.DUMMY_CA_KEY_PEM)
789+
790+
# Now the node will actually start up and use them, so we can
791+
# create the RPC instance.
792+
self.rpc = grpc.LightningGrpc(
793+
host='localhost',
794+
port=self.grpc_port,
795+
root_certificates=grpc.DUMMY_CA_PEM,
796+
private_key=grpc.DUMMY_CLIENT_KEY_PEM,
797+
certificate_chain=grpc.DUMMY_CLIENT_PEM
798+
)
799+
800+
def _create_jsonrpc_rpc(self, jsonschemas):
801+
socket_path = self.lightning_dir / TEST_NETWORK / "lightning-rpc"
802+
self.grpc_port = None
803+
804+
self.rpc = PrettyPrintingLightningRpc(
805+
str(socket_path),
806+
self.executor,
807+
jsonschemas=jsonschemas
808+
)
809+
754810
def connect(self, remote_node):
755811
self.rpc.connect(remote_node.info['id'], '127.0.0.1', remote_node.daemon.port)
756812

tools/check-spelling.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env bash
22

3-
if git --no-pager grep -nHiE 'l[ightn]{6}g|l[ightn]{8}g|ilghtning|lgihtning|lihgtning|ligthning|lighnting|lightinng|lightnnig|lightnign' -- . ':!tools/check-spelling.sh' | grep -vE "highlighting"; then
3+
if git --no-pager grep -nHiE 'l[ightn]{6}g|l[ightn]{8}g|ilghtning|lgihtning|lihgtning|ligthning|lighnting|lightinng|lightnnig|lightnign' -- . ':!tools/check-spelling.sh' | grep -vE "highlighting|LightningGrpc"; then
44
echo "Identified a likely misspelling of the word \"lightning\" (see above). Please fix."
55
echo "Is this warning incorrect? Please teach tools/check-spelling.sh about the exciting new word."
66
exit 1

0 commit comments

Comments
 (0)