The fixture had only bitcoind + three LND nodes, so RTL's Core Lightning screens
had no backend to exercise. Add a `cln` node (official elementsproject/lightningd
image, multi-arch) wired to RTL over clnrest with rune auth, and have the seed
open a cln->alice channel so the CLN channel/peer screens have real data.
- docker-compose.yml: cln service (clnrest on 0.0.0.0:3010, https), a healthcheck
gated on the rune file so rtl waits for it, and rtl now mounts the cln volume
read-only and depends on cln being healthy. The rtl image is parameterized via
${RTL_IMAGE:-...} so an unreleased build can be tested against the fixture.
- cln/poststart.d/create-rune.sh: once the RPC is up, create a master rune and
write it as LIGHTNING_RUNE="..." where RTL reads it (runePath). Polls for RPC
readiness because the image entrypoint can invoke poststart before the socket
exists.
- RTL-Config.regtest.json: add node index 4 (CLN, rune auth, https://cln:3010).
- seed.sh: fund cln, connect to alice, open a 4,000,000 sat channel, wait active.
- README + release notes updated.
Used to verify the CLN channel connection-status fix (#1606) end-to-end.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| bin | ||
| cln/poststart.d | ||
| rtl | ||
| scripts | ||
| .env | ||
| docker-compose.yml | ||
| README.md | ||
RTL regtest dev fixture
NOT suitable for production. Development only. Every credential here is throwaway.
A self-contained regtest network for developing and testing RTL: bitcoind, three
LND nodes, a Core Lightning node, and RTL wired to all four.
alice --[ 5,000,000 sat ]--> bob --[ 3,000,000 sat ]--> carol
cln --[ 4,000,000 sat ]--> alice
bob sits in the middle so it accrues forwarding history, which is what gives RTL's
routing screens something to show. Two nodes would leave them empty. The cln
(Core Lightning) node gives RTL's CLN screens a real backend — it talks to RTL over
clnrest with rune auth.
LND and bitcoind images come from Polar; the Core
Lightning image is the official elementsproject/lightningd.
All are multi-arch (amd64 + arm64) and nothing is built locally, so this works on
Apple Silicon.
Requirements
Docker with Compose v2 (docker compose, not docker-compose).
Quick start
From this directory:
docker compose up -d # bitcoind, alice, bob, carol, cln, rtl
./scripts/seed.sh # fund, connect, open channels, make payments
Then open http://localhost:3000 — password rtldev. All four nodes (alice, bob,
carol, cln) appear in the node switcher.
Tear down, discarding all state:
docker compose down -v
What the seed creates
| On-chain | 10,000,000 sats per node (LND) + 10,000,000 sats on cln, confirmed |
| Channels | alice→bob 5,000,000 sats · bob→carol 3,000,000 sats (1,000,000 pushed each) · cln→alice 4,000,000 sats |
| Routed payments | 5 × alice→carol via bob (10k, 25k, 50k, 75k, 100k sats) |
| Direct payments | 2 × alice→bob (5k, 15k sats) |
| Open invoices | 2 unpaid on carol (20k, 40k sats) |
| Personas | alice + bob + cln OPERATOR, carol MERCHANT |
Determinism
Every amount and payment in scripts/seed.sh is fixed. A fresh run always produces
identical state, so screenshots taken before and after a change differ only by the
change. Do not introduce randomness.
The seed is deterministic but deliberately not idempotent — running it twice would fund every node again and open a second set of channels. It refuses to run against an already-seeded network. To start over:
docker compose down -v && docker compose up -d && ./scripts/seed.sh
Helpers
bin/b-cli getblockcount # bitcoin-cli
bin/b-cli -rpcwallet=rtldev getbalance
bin/ln-cli alice getinfo # lncli, node name required
bin/ln-cli bob listchannels
bin/ln-cli bob fwdinghistory # forwarding history
docker compose exec cln lightning-cli --network=regtest listpeerchannels # Core Lightning
Logs:
docker compose logs -f rtl
docker compose logs alice
Notes and gotchas
RTL's config. rtl/RTL-Config.regtest.json is the tracked template. RTL rewrites
its config on startup, so an init container copies it into a volume rather than
bind-mounting it — a read-only mount makes RTL exit with EROFS, and a writable one
would let RTL modify a version-controlled file. The name is not RTL-Config.json
because .gitignore matches that bare filename at any depth.
lncli needs --lnddir=/home/lnd/.lnd. docker compose exec lands as root,
whose HOME is /root, but lnd's datadir is /home/lnd/.lnd. bin/ln-cli handles this.
Changing bitcoind credentials. docker-compose.yml carries an -rpcauth hash for
the BITCOIN_RPC_USER / BITCOIN_RPC_PASSWORD in .env. Changing them there is not
enough; regenerate the hash:
python3 - <<'EOF'
import hmac, hashlib
user, password, salt = "rtldev", "rtldev", "8a1f2c3d4e5b6a7c8d9e0f1a2b3c4d5e"
print(f"{user}:{salt}${hmac.new(salt.encode(), password.encode(), hashlib.sha256).hexdigest()}")
EOF
In docker-compose.yml the $ must be written $$ to escape Compose interpolation.
Payments right after channel open will fail. The channel graph has to reach alice before she can route to carol. The seed waits for this; anything you script yourself should too.
Core Lightning auth uses a rune. RTL talks to cln over clnrest and authenticates
with a rune, not a macaroon. On first start cln/poststart.d/create-rune.sh runs inside
the node (once the RPC is up), creates a master rune, and writes it as
LIGHTNING_RUNE="…" to rtl.rune in the shared cln_data volume; RTL reads it via the
runePath in its config. The cln healthcheck only passes once that file exists, so RTL
waits for it. --clnrest-host=0.0.0.0 is required for RTL (another container) to reach
clnrest; the default 127.0.0.1 would only be reachable from inside the node.
Not included
Eclair nodes and the Boltz swap service. Polar publishes a multi-arch eclair image, so
adding an Eclair node means a compose service, an RTL config entry, and a seeding adapter
— no image building.