test: VM smoke and Lightning regtest suites

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-06-28 06:48:38 +02:00
commit 7f4a71b98d
4 changed files with 358 additions and 0 deletions

109
tests/regtest-core.nix Normal file
View file

@ -0,0 +1,109 @@
# regtest-core — NixOS-native Lightning regtest integration test.
#
# A reproducible, hermetic replacement for the core of the docker
# `legend-regtest-enviroment` stack (aiolabs/omnixient#27): one VM running
# bitcoind(regtest) + Core Lightning + LND (the bitcoin pack's nix-bitcoin
# modules), driving a real payment flow and asserting it settles.
#
# Both LN nodes are co-located on one node and share the local bitcoind;
# lnd's p2p port is moved to 9736 so it doesn't clash with clightning's
# 9735. CLIs are run as the nix-bitcoin `operator` user, matching how
# nix-bitcoin's own test suite drives them.
#
# The node config is shared with the interactive dev VM via
# tests/regtest-node.nix so the two can never drift.
#
# Run headless:
# nix build .#checks.x86_64-linux.regtest-core -L
# Interactive driver (poke the node by hand):
# nix build .#checks.x86_64-linux.regtest-core.driverInteractive
# ./result/bin/nixos-test-driver
# Friendlier interactive shell (SSH, copy/paste, operator user, helpers):
# nix run .#regtest (see docs/regtest.md)
{
nixpkgs,
system ? "x86_64-linux",
nix-bitcoin,
...
}:
let
pkgs = import nixpkgs {
inherit system;
config.allowUnfree = true;
};
in
pkgs.testers.runNixOSTest {
name = "omni-regtest-core";
nodes.regtest = {
imports = [
nix-bitcoin.nixosModules.default
./regtest-node.nix # shared bitcoind + CLN + LND config
];
};
testScript = ''
def op(cmd):
"""Run a command as the nix-bitcoin operator and return stdout."""
return regtest.succeed(f"runuser -u operator -- {cmd}")
regtest.start()
# --- services up ---
regtest.wait_for_unit("bitcoind.service")
regtest.wait_for_unit("clightning.service")
regtest.wait_for_unit("lnd.service")
# CLIs answer (clightning/lnd take a moment past unit-active to be RPC-ready).
regtest.wait_until_succeeds("runuser -u operator -- lightning-cli getinfo", timeout=120)
regtest.wait_until_succeeds("runuser -u operator -- lncli getinfo", timeout=120)
# --- both LN nodes synced to the pre-mined regtest chain ---
regtest.wait_until_succeeds(
f"[[ $(runuser -u operator -- lightning-cli getinfo | jq -M .blockheight) == {110} ]]",
timeout=120,
)
regtest.wait_until_succeeds(
f"[[ $(runuser -u operator -- lncli getinfo | jq -M .block_height) == {110} ]]",
timeout=120,
)
# --- fund LND's on-chain wallet from the bitcoind test wallet ---
lnd_addr = op("lncli newaddress p2wkh | jq -r .address").strip()
op(f"bitcoin-cli -rpcwallet=test sendtoaddress {lnd_addr} 5")
mine_addr = op("bitcoin-cli -rpcwallet=test getnewaddress").strip()
op(f"bitcoin-cli -rpcwallet=test generatetoaddress 6 {mine_addr}")
regtest.wait_until_succeeds(
"[[ $(runuser -u operator -- lncli walletbalance | jq -M '.confirmed_balance | tonumber') -gt 0 ]]",
timeout=120,
)
# --- connect LND -> CLN and open a channel ---
cln_id = op("lightning-cli getinfo | jq -r .id").strip()
op(f"lncli connect {cln_id}@127.0.0.1:9735")
op(f"lncli openchannel --node_key={cln_id} --local_amt=1000000") # 0.01 BTC
# confirm the channel and wait for it to go active on the LND side
op(f"bitcoin-cli -rpcwallet=test generatetoaddress 6 {mine_addr}")
regtest.wait_until_succeeds(
"[[ $(runuser -u operator -- lncli listchannels | jq '[.channels[] | select(.active)] | length') -ge 1 ]]",
timeout=240,
)
# --- CLN issues an invoice, LND pays it ---
bolt11 = op(
"lightning-cli invoice 100000000 regtest-core-test 'regtest-core payment' | jq -r .bolt11"
).strip() # 100000 sat = 100,000,000 msat
op(f"lncli payinvoice --force {bolt11}")
# --- assert CLN saw the invoice settle ---
regtest.wait_until_succeeds(
"[[ $(runuser -u operator -- lightning-cli listinvoices regtest-core-test "
"| jq -r '.invoices[0].status') == paid ]]",
timeout=180,
)
print("regtest-core: LND -> CLN channel opened and invoice paid ")
'';
}