forc_node/testnet/
op.rs

1use crate::{
2    chain_config::{check_and_update_chain_config, ChainConfig},
3    consts::{
4        TESTNET_RELAYER_DA_DEPLOY_HEIGHT, TESTNET_RELAYER_LISTENING_CONTRACT,
5        TESTNET_RELAYER_LOG_PAGE_SIZE, TESTNET_SERVICE_NAME, TESTNET_SYNC_BLOCK_STREAM_BUFFER_SIZE,
6        TESTNET_SYNC_HEADER_BATCH_SIZE,
7    },
8    run_opts::{DbType, RunOpts},
9    testnet::cmd::TestnetCmd,
10    util::{ask_user_keypair, ask_user_string, HumanReadableCommand, KeyPair},
11};
12use anyhow::Context;
13use forc_tracing::println_green;
14use std::{
15    net::IpAddr,
16    path::PathBuf,
17    process::{Child, Command},
18};
19
20/// Configures the node with testnet configuration to connect the node to latest testnet.
21/// Returns `None` if this is a dry_run and no child process created for fuel-core.
22pub(crate) async fn run(cmd: TestnetCmd, dry_run: bool) -> anyhow::Result<Option<Child>> {
23    check_and_update_chain_config(ChainConfig::Testnet).await?;
24    let keypair = if let (Some(peer_id), Some(secret)) = (
25        &cmd.connection_settings.peer_id,
26        &cmd.connection_settings.secret,
27    ) {
28        KeyPair {
29            peer_id: peer_id.clone(),
30            secret: secret.clone(),
31        }
32    } else {
33        ask_user_keypair()?
34    };
35
36    let relayer = cmd.connection_settings.relayer.unwrap_or_else(|| {
37        ask_user_string("Ethereum RPC (Sepolia) Endpoint:").expect("Failed to get RPC endpoint")
38    });
39
40    let opts = TestnetOpts {
41        keypair,
42        relayer,
43        ip: cmd.connection_settings.ip,
44        port: cmd.connection_settings.port,
45        peering_port: cmd.connection_settings.peering_port,
46        db_path: cmd.db_path,
47        bootstrap_node: cmd.bootstrap_node,
48    };
49    let run_opts = RunOpts::from(opts);
50    let params = run_opts.generate_params();
51    let mut fuel_core_command = Command::new("fuel-core");
52    fuel_core_command.arg("run");
53    fuel_core_command.args(params.as_slice());
54
55    println_green(&format!(
56        "{}",
57        HumanReadableCommand::from(&fuel_core_command)
58    ));
59
60    if dry_run {
61        return Ok(None);
62    }
63
64    // Spawn the process with proper error handling
65    let handle = fuel_core_command
66        .spawn()
67        .with_context(|| "Failed to spawn fuel-core process:".to_string())?;
68    Ok(Some(handle))
69}
70
71#[derive(Debug)]
72pub struct TestnetOpts {
73    keypair: KeyPair,
74    relayer: String,
75    ip: IpAddr,
76    port: u16,
77    peering_port: u16,
78    db_path: PathBuf,
79    bootstrap_node: String,
80}
81
82impl From<TestnetOpts> for RunOpts {
83    fn from(value: TestnetOpts) -> Self {
84        Self {
85            service_name: Some(TESTNET_SERVICE_NAME.to_string()),
86            db_type: DbType::RocksDb,
87            debug: false,
88            snapshot: ChainConfig::Testnet.into(),
89            keypair: Some(value.keypair.secret),
90            relayer: Some(value.relayer),
91            ip: Some(value.ip),
92            port: Some(value.port),
93            peering_port: Some(value.peering_port),
94            db_path: Some(value.db_path),
95            bootstrap_nodes: Some(value.bootstrap_node),
96            utxo_validation: true,
97            poa_instant: false,
98            enable_p2p: true,
99            sync_header_batch_size: Some(TESTNET_SYNC_HEADER_BATCH_SIZE),
100            enable_relayer: true,
101            relayer_listener: Some(TESTNET_RELAYER_LISTENING_CONTRACT.to_string()),
102            relayer_da_deploy_height: Some(TESTNET_RELAYER_DA_DEPLOY_HEIGHT),
103            relayer_log_page_size: Some(TESTNET_RELAYER_LOG_PAGE_SIZE),
104            sync_block_stream_buffer_size: Some(TESTNET_SYNC_BLOCK_STREAM_BUFFER_SIZE),
105        }
106    }
107}