Skip to main content

forest/dev/subcommands/
devnet_cmd.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4//! Integration suites that run against the local docker devnet. Unlike the unit
5//! suite, these need a running devnet with both a Forest and a Lotus node reachable and the
6//! test harness environment wired up. [`preflight`] fails early with actionable errors when
7//! that environment is missing, rather than letting a suite surface it as an opaque mid-run error.
8
9mod eth_gas;
10mod eth_skip_sender;
11
12use crate::dev::subcommands::tests_cmd::helpers::{docker, forest_client, lotus_client};
13use crate::rpc::prelude::*;
14use anyhow::{Context as _, ensure};
15
16/// Integration tests that require the docker devnet
17#[derive(Debug, clap::Subcommand)]
18pub enum DevnetCommand {
19    EthGas(eth_gas::EthGasTestCommand),
20    EthSkipSender(eth_skip_sender::EthSkipSenderTestCommand),
21}
22
23impl DevnetCommand {
24    pub async fn run(self) -> anyhow::Result<()> {
25        preflight().await.context("devnet pre-flight failed")?;
26        match self {
27            Self::EthGas(cmd) => cmd.run().await,
28            Self::EthSkipSender(cmd) => cmd.run().await,
29        }
30    }
31}
32
33async fn preflight() -> anyhow::Result<()> {
34    for container in ["forest", "lotus"] {
35        let running = docker(&["inspect", "-f", "{{.State.Running}}", container]).with_context(
36            || format!("could not query container `{container}`; is docker running and the local docker devnet up?"),
37        )?;
38        ensure!(
39            running.trim() == "true",
40            "devnet container `{container}` is not running; bring the local docker devnet up first"
41        );
42    }
43
44    for var in [
45        "FULLNODE_API_INFO",
46        "FOREST_TEST_PRELOADED_ADDRESS",
47        "LOTUS_RPC_PORT",
48    ] {
49        ensure!(
50            std::env::var_os(var).is_some(),
51            "{var} is not set; source the devnet test harness and run `devnet_test_env_init` first"
52        );
53    }
54
55    // Probe eth RPC specifically, not just `ChainHead`: the suites need it, and a devnet with eth
56    // RPC disabled would otherwise pass here and fail opaquely mid-suite.
57    for (node, client) in [("forest", forest_client()?), ("lotus", lotus_client()?)] {
58        EthBlockNumber::call(&client, ()).await.with_context(|| {
59            format!("{node} eth RPC is not reachable; is the local docker devnet up (with eth RPC enabled) and synced?")
60        })?;
61    }
62
63    Ok(())
64}