ant_node/spawn/
node_spawner.rs

1// Copyright 2025 MaidSafe.net limited.
2//
3// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
4// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
5// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
6// KIND, either express or implied. Please review the Licences for the specific language governing
7// permissions and limitations relating to use of the SAFE Network Software.
8
9use crate::utils::get_root_dir_and_keypair;
10use crate::{NodeBuilder, RunningNode};
11use ant_bootstrap::{BootstrapConfig, bootstrap::Bootstrap};
12pub use ant_evm::{EvmNetwork, RewardsAddress};
13pub use libp2p::Multiaddr;
14use std::net::{IpAddr, Ipv4Addr, SocketAddr};
15use std::path::PathBuf;
16
17#[derive(Debug, Clone)]
18pub struct NodeSpawner {
19    /// The socket address where the node will listen.
20    socket_addr: SocketAddr,
21    /// The EVM network the node will connect to.
22    evm_network: EvmNetwork,
23    /// The rewards address used for receiving rewards.
24    rewards_address: RewardsAddress,
25    /// The bootstrap configuration for the node.
26    bootstrap_config: Option<BootstrapConfig>,
27    /// A boolean indicating whether UPnP should be disabled.
28    no_upnp: bool,
29    /// An optional `PathBuf` representing the root directory for the node.
30    root_dir: Option<PathBuf>,
31}
32
33impl NodeSpawner {
34    /// Create a new instance of `NodeSpawner` with default values.
35    pub fn new() -> Self {
36        Self {
37            socket_addr: SocketAddr::new(IpAddr::from(Ipv4Addr::UNSPECIFIED), 0),
38            evm_network: Default::default(),
39            rewards_address: Default::default(),
40            bootstrap_config: None,
41            no_upnp: false,
42            root_dir: None,
43        }
44    }
45
46    /// Set the socket address for the node.
47    ///
48    /// # Arguments
49    ///
50    /// * `socket_addr` - The `SocketAddr` where the node will listen.
51    pub fn with_socket_addr(mut self, socket_addr: SocketAddr) -> Self {
52        self.socket_addr = socket_addr;
53        self
54    }
55
56    /// Set the EVM network for the node.
57    ///
58    /// # Arguments
59    ///
60    /// * `evm_network` - The `EvmNetwork` the node will connect to.
61    pub fn with_evm_network(mut self, evm_network: EvmNetwork) -> Self {
62        self.evm_network = evm_network;
63        self
64    }
65
66    /// Set the rewards address for the node.
67    ///
68    /// # Arguments
69    ///
70    /// * `rewards_address` - The `RewardsAddress` used for distributing rewards.
71    pub fn with_rewards_address(mut self, rewards_address: RewardsAddress) -> Self {
72        self.rewards_address = rewards_address;
73        self
74    }
75
76    /// Set the bootstrap configuration for the node.
77    ///
78    /// # Arguments
79    ///
80    /// * `bootstrap_config` - The `BootstrapConfig` containing bootstrap configuration.
81    pub fn with_bootstrap_config(mut self, bootstrap_config: BootstrapConfig) -> Self {
82        self.bootstrap_config = Some(bootstrap_config);
83        self
84    }
85
86    /// Set the to disable UPnP on the node.
87    ///
88    /// # Arguments
89    ///
90    /// * `no_upnp` - A boolean indicating whether UPnP should be disabled.
91    pub fn with_no_upnp(mut self, no_upnp: bool) -> Self {
92        self.no_upnp = no_upnp;
93        self
94    }
95
96    /// Set the root directory for the node.
97    ///
98    /// # Arguments
99    ///
100    /// * `root_dir` - An optional `PathBuf` representing the root directory for the node.
101    pub fn with_root_dir(mut self, root_dir: Option<PathBuf>) -> Self {
102        self.root_dir = root_dir;
103        self
104    }
105
106    /// Spawn the node using the configured parameters.
107    ///
108    /// # Returns
109    ///
110    /// An `eyre::Result` containing a `RunningNode` if successful, or an error.
111    pub async fn spawn(self) -> eyre::Result<RunningNode> {
112        spawn_node(
113            self.socket_addr,
114            self.evm_network,
115            self.rewards_address,
116            self.bootstrap_config,
117            self.no_upnp,
118            &self.root_dir,
119        )
120        .await
121    }
122}
123
124impl Default for NodeSpawner {
125    fn default() -> Self {
126        Self::new()
127    }
128}
129
130async fn spawn_node(
131    socket_addr: SocketAddr,
132    evm_network: EvmNetwork,
133    rewards_address: RewardsAddress,
134    bootstrap_config: Option<BootstrapConfig>,
135    no_upnp: bool,
136    root_dir: &Option<PathBuf>,
137) -> eyre::Result<RunningNode> {
138    let (root_dir, keypair) = get_root_dir_and_keypair(root_dir)?;
139
140    let bootstrap_config = bootstrap_config.unwrap_or_default();
141    let local = bootstrap_config.local;
142    let bootstrap = Bootstrap::new(bootstrap_config).await?;
143
144    let mut node_builder = NodeBuilder::new(
145        keypair,
146        bootstrap,
147        rewards_address,
148        evm_network,
149        socket_addr,
150        root_dir,
151    );
152    node_builder.local(local);
153    node_builder.no_upnp(no_upnp);
154
155    let running_node = node_builder.build_and_run()?;
156
157    // Verify that node is running
158    let mut retries: u8 = 0;
159
160    let listen_addrs: Vec<Multiaddr> = loop {
161        // Wait till we have at least 1 listen addrs
162        if let Ok(listen_addrs) = running_node.get_listen_addrs().await
163            && !listen_addrs.is_empty()
164        {
165            break Ok(listen_addrs);
166        }
167
168        if retries >= 3 {
169            break Err(eyre::eyre!(
170                "Failed to get listen addresses after {} retries",
171                retries
172            ));
173        }
174
175        retries += 1;
176
177        tokio::time::sleep(tokio::time::Duration::from_secs(retries as u64)).await;
178    }?;
179
180    info!("Node listening on addresses: {:?}", listen_addrs);
181
182    Ok(running_node)
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188    use ant_evm::EvmNetwork;
189    use futures::StreamExt;
190    use libp2p::swarm::dummy;
191
192    #[tokio::test]
193    async fn test_launch_node() {
194        let evm_network = EvmNetwork::ArbitrumSepoliaTest;
195
196        let bootstrap_config = BootstrapConfig::new(true)
197            .with_first(true)
198            .with_disable_cache_reading(true)
199            .with_disable_env_peers(true);
200
201        let running_node = NodeSpawner::new()
202            .with_evm_network(evm_network)
203            .with_bootstrap_config(bootstrap_config)
204            .spawn()
205            .await
206            .unwrap();
207
208        let listen_addrs = running_node.get_listen_addrs().await.unwrap();
209
210        assert!(!listen_addrs.is_empty());
211
212        let mut swarm = libp2p::SwarmBuilder::with_new_identity()
213            .with_tokio()
214            .with_quic()
215            .with_behaviour(|_| dummy::Behaviour)
216            .unwrap()
217            .build();
218
219        let address = listen_addrs.first().unwrap().clone();
220
221        assert!(swarm.dial(address).is_ok());
222        assert!(matches!(
223            swarm.next().await,
224            Some(libp2p::swarm::SwarmEvent::ConnectionEstablished { .. })
225        ));
226
227        running_node.shutdown();
228    }
229}