-
Notifications
You must be signed in to change notification settings - Fork 991
Integrating VLS option on tests #8917
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
675da94
51e3bb2
71786f6
65de8cf
0d6de55
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| from utils import TEST_NETWORK, VALGRIND # noqa: F401,F403 | ||
| from pyln.testing.fixtures import directory, test_base_dir, test_name, chainparams, node_factory, bitcoind, teardown_checks, db_provider, executor, setup_logging, jsonschemas # noqa: F401,F403 | ||
| from utils import TEST_NETWORK, BITCOIND_CONFIG, VALGRIND # noqa: F401,F403 | ||
| from pyln.testing.fixtures import directory, test_base_dir, test_name, chainparams, bitcoind, teardown_checks, db_provider, executor, setup_logging, jsonschemas # noqa: F401,F403 | ||
| from pyln.testing import utils | ||
| from pyln.testing.utils import NodeFactory as _NodeFactory | ||
| from utils import COMPAT | ||
| from pathlib import Path | ||
|
|
||
|
|
@@ -11,20 +12,80 @@ | |
| import subprocess | ||
| import tempfile | ||
| import time | ||
| from pyln.testing.utils import env | ||
| from vls import ValidatingLightningSignerD | ||
|
|
||
|
|
||
| class NodeFactory(_NodeFactory): | ||
| """Make `use_vls` option reaches the `LightningNode.__init__` in | ||
| `NodeFactory` as node-level kwarg instead of being forwarded as a | ||
| lightningd CLI flag.""" | ||
|
|
||
| def split_options(self, opts): | ||
| node_opts, cli_opts = super().split_options(opts) | ||
| if 'use_vls' in cli_opts: | ||
| node_opts['use_vls'] = cli_opts.pop('use_vls') | ||
| return node_opts, cli_opts | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def node_cls(): | ||
| return LightningNode | ||
|
|
||
| # Override the default fixture to use the new `NodeFactory` which supports `use_vls` as a node-level option. | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def node_factory(request, directory, test_name, bitcoind, executor, db_provider, teardown_checks, node_cls, jsonschemas): # noqa: F811 | ||
| nf = NodeFactory( | ||
| request, | ||
| test_name, | ||
| bitcoind, | ||
| executor, | ||
| directory=directory, | ||
| db_provider=db_provider, | ||
| node_cls=node_cls, | ||
| jsonschemas=jsonschemas, | ||
| ) | ||
|
|
||
| yield nf | ||
| ok, errs = nf.killall([not n.may_fail for n in nf.nodes]) | ||
|
|
||
| for e in errs: | ||
| print(e.format()) | ||
|
|
||
| if not ok: | ||
| raise Exception("At least one lightning exited with unexpected non-zero return code") | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def use_vls(pytestconfig): | ||
| # This fixture is used to mark tests as using VLS. It doesn't do anything | ||
| # by itself, but it allows us to select tests with `-m vls` and to skip | ||
| # them if the signer is not available. | ||
| markerexpr = pytestconfig.getoption("markexpr") or "" | ||
| return "vls" in markerexpr.split() | ||
|
|
||
|
|
||
| class LightningNode(utils.LightningNode): | ||
| def __init__(self, *args, **kwargs): | ||
| def __init__(self, *args, use_vls=False, **kwargs): | ||
| # Yes, we really want to test the local development version, not | ||
| # something in out path. | ||
| kwargs["executable"] = "lightningd/lightningd" | ||
| utils.LightningNode.__init__(self, *args, **kwargs) | ||
|
|
||
| # node_id is pyln's first positional arg; keep it for the VLS label. | ||
| self._node_id = args[0] if args else kwargs["node_id"] | ||
| self.network = TEST_NETWORK | ||
|
|
||
| if use_vls is True: | ||
| self.vls_mode = "cln:socket" | ||
| elif use_vls is False: | ||
| self.vls_mode = "cln:native" | ||
|
|
||
| self.use_vls = self.vls_mode == "cln:socket" | ||
| self.vlsd: ValidatingLightningSignerD | None = None | ||
|
|
||
| # Avoid socket path name too long on Linux | ||
| if os.uname()[0] == 'Linux' and \ | ||
| len(str(self.lightning_dir / TEST_NETWORK / 'lightning-rpc')) >= 108: | ||
|
|
@@ -61,6 +122,56 @@ def __init__(self, *args, **kwargs): | |
| accts_db = self.db.provider.get_db('', 'accounts', 0) | ||
| self.daemon.opts['bookkeeper-db'] = accts_db.get_dsn() | ||
|
|
||
| def start(self, wait_for_bitcoind_sync=True, stderr_redir=False): | ||
| # Start the signer first and wait for it to be up, otherwise lightningd | ||
| # hangs on the hsmd init message. | ||
| if self.use_vls: | ||
| self.vlsd = ValidatingLightningSignerD( | ||
| lightning_dir=self.lightning_dir, | ||
| node_id=self._node_id, | ||
| network=self.network, | ||
| ) | ||
| self.daemon.opts["subdaemon"] = f"hsmd:{self.vlsd.remote_socket}" | ||
|
|
||
| # FIXME: VLS doesn't implement WIRE_HSMD_SIGN_SPLICE_TX, so lightningd | ||
| # would fatal() during hsm_init if OPT_SPLICE (bit 62) is offered. | ||
| # Strip the optional splice bit (63 = OPTIONAL_FEATURE(62)). | ||
| self.daemon.opts["dev-force-features"] = "-63" | ||
|
|
||
| # These are consumed by lightningd's remote_hsmd_socket bridge; | ||
| # they go on the daemon's per-proc env so each node gets its own | ||
| # signer configuration (see test-env precedence in pyln). | ||
| self.daemon.env["VLS_PORT"] = str(self.vlsd.port) | ||
| self.daemon.env["VLS_LSS"] = env("LSS_URI", "") | ||
| self.daemon.env["VLS_NETWORK"] = env("VLS_NETWORK", self.network) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think these get inherited automatically, but good habit to explicitly pass them in. |
||
| self.daemon.env["BITCOIND_RPC_URL"] = env( | ||
| "BITCOIND_RPC_URL", | ||
| f"http://{BITCOIND_CONFIG['rpcuser']}:{BITCOIND_CONFIG['rpcpassword']}@127.0.0.1:{self.bitcoin.rpcport}", | ||
| ) | ||
| # We must feed `remote_hsmd_socket` (via VLS_CLN_VERSION) *just* | ||
| # the bare version, otherwise the check fails and lightningd | ||
| # exits before spawning hsmd. | ||
| raw = subprocess.check_output( | ||
| [self.daemon.executable, "--version"] | ||
| ).decode("ascii") | ||
| cln_version = next( | ||
| line for line in reversed(raw.splitlines()) if line.strip() | ||
| ) | ||
| self.daemon.env["VLS_CLN_VERSION"] = env("VLS_CLN_VERSION", cln_version) | ||
| self.vlsd.start() | ||
|
|
||
| utils.LightningNode.start( | ||
| self, | ||
| wait_for_bitcoind_sync=wait_for_bitcoind_sync, | ||
| stderr_redir=stderr_redir, | ||
| ) | ||
|
|
||
| def stop(self, timeout: int = 10): | ||
| utils.LightningNode.stop(self, timeout=timeout) | ||
| if self.vlsd is not None: | ||
| rc = self.vlsd.stop(timeout=timeout) | ||
| print(f"VLSD2 exited with rc={rc}") | ||
|
|
||
|
|
||
| class CompatLevel(object): | ||
| """An object that encapsulates the compat-level of our build. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,7 @@ | |
| from pyln.testing.utils import env, only_one, wait_for, write_config, TailableProc, sync_blockheight, wait_channel_quiescent, get_tx_p2wsh_outnum, mine_funding_to_announce, scid_to_int # noqa: F401 | ||
| import bitstring | ||
| from pyln.client import Millisatoshi | ||
| from pyln.testing.utils import EXPERIMENTAL_DUAL_FUND | ||
| from pyln.testing.utils import EXPERIMENTAL_DUAL_FUND, BITCOIND_CONFIG # noqa: F401 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is this change for? Wildcard import?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exactly, this is required for the |
||
| from pyln.proto.onion import TlvPayload | ||
| import struct | ||
| import subprocess | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| from pyln.testing.utils import TailableProc, env, reserve_unused_port | ||
| from pathlib import Path | ||
| from subprocess import run | ||
| import logging | ||
| import os | ||
|
|
||
|
|
||
| REPOS = ["https://gitlab.com/lightning-signer/validating-lightning-signer.git"] | ||
|
|
||
|
|
||
| def _resolve_executable(datadir: Path) -> Path: | ||
| """ | ||
| Return the path where the vlsd executable can be found. | ||
| """ | ||
| prebuilt = os.environ.get("REMOTE_SIGNER_PATH") | ||
| if prebuilt: | ||
| path = Path(os.path.expanduser(prebuilt)).resolve() | ||
| if not path.exists(): | ||
| raise RuntimeError(f"REMOTE_SIGNER_PATH={prebuilt} does not exist") | ||
| return path | ||
|
|
||
| if os.environ.get("VLS_AUTO_BUILD") != "1": | ||
| raise RuntimeError( | ||
| "No VLS binary available: set REMOTE_SIGNER_PATH to a pre-built " | ||
| "vlsd, or VLS_AUTO_BUILD=1 to clone and compile it." | ||
| ) | ||
|
|
||
| signer_folder = REPOS[0].split("/")[-1].removesuffix(".git") | ||
| vlsd_dir = (datadir / signer_folder).resolve() | ||
| logging.info(f"Cloning {REPOS[0]} into {vlsd_dir}") | ||
| run(["git", "clone", REPOS[0]], cwd=datadir, check=True, timeout=120) | ||
| cargo_target_dir = os.environ.get("CARGO_TARGET_DIR") | ||
| target_dir = ( | ||
| Path(os.path.expanduser(cargo_target_dir)).resolve() | ||
| if cargo_target_dir | ||
| else vlsd_dir / "target" | ||
| ) | ||
| logging.info(f"Building vlsd in {vlsd_dir} (target dir: {target_dir})") | ||
| run(["cargo", "build", "--features", "developer"], | ||
| cwd=vlsd_dir, check=True, timeout=600) | ||
| return (target_dir / "debug" / "vlsd").resolve() | ||
|
|
||
|
|
||
| class ValidatingLightningSignerD(TailableProc): | ||
| def __init__(self, lightning_dir, node_id, network): | ||
| # Each node gets its own datadir and socket, so multiple nodes can run | ||
| # their own signer in parallel even when sharing a prebuilt binary. | ||
| self.datadir = (Path(lightning_dir) / "vlsd").resolve() | ||
| self.datadir.mkdir(exist_ok=True, parents=True) | ||
|
|
||
| self.bin_dir = str(_resolve_executable(self.datadir)) | ||
| self.executable = self.bin_dir / "vlsd" | ||
| self.port = reserve_unused_port() | ||
| self.rpc_port = reserve_unused_port() | ||
| self.remote_socket = (Path(self.bin_dir) / "remote_hsmd_socket").resolve() | ||
| if not self.remote_socket.exists(): | ||
| raise RuntimeError( | ||
| f"remote_hsmd_socket binary not found next to vlsd at {self.remote_socket}" | ||
| ) | ||
|
|
||
| TailableProc.__init__(self, self.datadir, verbose=True) | ||
| # Set ALLOWLIST on the signer's proc env instead of os.environ so | ||
| # multiple signers can coexist without the test coordinator leaking | ||
| # state between them. | ||
| allowlist = os.environ.get("REMOTE_SIGNER_ALLOWLIST") | ||
| if allowlist: | ||
| self.env["ALLOWLIST"] = allowlist | ||
| else: | ||
| logging.warning( | ||
| "REMOTE_SIGNER_ALLOWLIST is not set; vlsd will start without " | ||
| "an allowlist. Point it at an absolute path to override." | ||
| ) | ||
| self.env["VLS_AUTOAPPROVE"] = env("VLS_AUTO_APPROVE", "1") | ||
| self.opts = [ | ||
| f"--network={network}", | ||
| f"--datadir={self.datadir}", | ||
| f"--connect=http://localhost:{self.port}", | ||
| f"--rpc-server-port={self.rpc_port}", | ||
| f"--rpc-user=bitcoind", | ||
| f"--rpc-pass=bitcoind" | ||
|
Comment on lines
+79
to
+80
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The linter will most likely not like the f-strings without placeholders. |
||
| ] | ||
| self.prefix = "vlsd-%d" % node_id | ||
|
|
||
| @property | ||
| def cmd_line(self): | ||
| return [self.executable] + self.opts | ||
|
|
||
| def start(self, stdin=None, stdout_redir=True, stderr_redir=True): | ||
| TailableProc.start(self, stdin, stdout_redir, stderr_redir) | ||
| self.wait_for_log("vlsd git_desc") | ||
| logging.info("vlsd started") | ||
|
|
||
| def stop(self, timeout=10): | ||
| logging.info("stopping vlsd") | ||
| rc = TailableProc.stop(self, timeout) | ||
| logging.info("vlsd stopped") | ||
| self.logs_catchup() | ||
| return rc | ||
|
|
||
| def __del__(self): | ||
| # __init__ may have raised before TailableProc finished setup. | ||
| if hasattr(self, "stdout_read"): | ||
| self.logs_catchup() | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should likely be removed pretty soon as we are working with VLS to add support. Then once that support lands, we can go back to marking entire tests as VLS-enabled or not.