spawn-lnd 0.1.0

Docker-backed Bitcoin Core and LND regtest clusters for Rust integration tests
Documentation

spawn-lnd

spawn-lnd is a Rust library for spawning Docker-backed Bitcoin Core and LND regtest nodes for integration tests.

The crate is library-first. It owns Docker lifecycle, daemon startup, wallet initialization, credential extraction, and readiness checks, then returns connection data that works with lnd_grpc_rust.

Status

Implemented:

  • Docker connection and labeled container lifecycle.
  • Pull-if-missing Docker images.
  • Cleanup by cluster label and rollback for partial startup failures.
  • Bitcoin Core regtest spawn and JSON-RPC client.
  • LND spawn, TLS cert extraction, wallet init, admin macaroon extraction.
  • LND readiness that requires authenticated GetInfo and synced_to_chain=true.
  • Multi-node cluster orchestration with one Bitcoin Core per three LNDs by default.
  • Alias-keyed node metadata and connect_nodes() integration with lnd_grpc_rust.
  • Lightning peer connection between aliases.
  • Wallet funding through a primary Bitcoin Core wallet: mine mature coinbase funds once, then use wallet RPC (sendmany for batches) to fund LND wallets and mine one confirmation block.
  • Channel opening helpers that wait for pending state, mine confirmations, and wait for both sides to report the channel active.
  • Configurable startup retry policy for slower Docker hosts and CI.

In progress:

  • Minimal CLI.

Default Images

  • Bitcoin Core: lightninglabs/bitcoin-core:30
  • LND: lightninglabs/lnd:v0.20.1-beta

Example

use spawn_lnd::SpawnLnd;

#[tokio::test]
async fn spawn_two_lnds() -> Result<(), Box<dyn std::error::Error>> {
    let mut cluster = SpawnLnd::builder()
        .nodes(["alice", "bob"])
        .spawn()
        .await?;

    let result = async {
        cluster.connect_peer("alice", "bob").await?;
        let funding = cluster.fund_node("alice").await?;
        assert!(funding.spendable_utxo_total_sat > 0);
        let channel = cluster.open_channel("alice", "bob").await?;
        assert!(channel.from_channel.active);

        let mut clients = cluster.connect_nodes().await?;
        let info = clients
            .get_mut("alice")
            .expect("alice")
            .lightning()
            .get_info(lnd_grpc_rust::lnrpc::GetInfoRequest {})
            .await?
            .into_inner();

        assert!(info.synced_to_chain);
        Ok::<_, Box<dyn std::error::Error>>(())
    }
    .await;

    cluster.shutdown().await?;
    result
}

Tests

Docker-backed tests require a local Docker Engine that can pull:

  • lightninglabs/bitcoin-core:30
  • lightninglabs/lnd:v0.20.1-beta
  • hello-world:latest

Run normal unit and gated smoke tests without Docker:

cargo test

Run Docker-backed smoke tests:

RUN_DOCKER_TESTS=1 cargo test --test docker_smoke -- --nocapture
RUN_DOCKER_TESTS=1 cargo test --test bitcoind_smoke -- --nocapture
RUN_DOCKER_TESTS=1 cargo test --test lnd_smoke -- --nocapture
RUN_DOCKER_TESTS=1 cargo test --test cluster_smoke -- --nocapture
RUN_DOCKER_TESTS=1 cargo test --test channel_smoke -- --nocapture
RUN_DOCKER_TESTS=1 cargo test --test e2e_smoke -- --nocapture
RUN_DOCKER_TESTS=1 cargo test --test startup_failure_smoke -- --nocapture

Check for leftover managed containers:

docker ps -a --filter label=spawn-lnd=true

Remove all managed containers:

docker rm -f $(docker ps -aq --filter label=spawn-lnd=true)

Keep containers for debugging failed tests:

SPAWN_LND_KEEP_CONTAINERS=1 RUN_DOCKER_TESTS=1 cargo test --test lnd_smoke -- --nocapture

Startup failures include a bounded Docker log tail in the typed error when a container was created before readiness failed.

Configuration

The builder supports node aliases, image overrides, chain grouping, debug cleanup behavior, and startup retry policy:

use spawn_lnd::{RetryPolicy, SpawnLnd};

let config = SpawnLnd::builder()
    .nodes(["alice", "bob"])
    .startup_retry_policy(RetryPolicy::new(600, 100))
    .build()?;

Environment overrides:

  • SPAWN_LND_BITCOIND_IMAGE
  • SPAWN_LND_LND_IMAGE
  • SPAWN_LND_NODES_PER_BITCOIND
  • SPAWN_LND_KEEP_CONTAINERS
  • SPAWN_LND_STARTUP_RETRY_ATTEMPTS
  • SPAWN_LND_STARTUP_RETRY_INTERVAL_MS

Startup Flags

Bitcoin Core uses compose-style regtest flags:

  • -regtest
  • -printtoconsole
  • -rpcbind=0.0.0.0
  • -rpcallowip=0.0.0.0/0
  • -fallbackfee=0.00001
  • -server
  • -txindex
  • -blockfilterindex
  • -coinstatsindex
  • -rpcuser=<generated>
  • -rpcpassword=<generated>

LND uses:

  • --bitcoin.regtest
  • --bitcoin.node=bitcoind
  • --bitcoind.rpcpolling
  • --bitcoind.rpchost=<bitcoind-bridge-ip>:18443
  • --bitcoind.rpcuser=<generated>
  • --bitcoind.rpcpass=<generated>
  • --debuglevel=info
  • --noseedbackup
  • --listen=0.0.0.0:9735
  • --rpclisten=0.0.0.0:10009