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_evm::{EvmNetwork, RewardsAddress};
12use libp2p::Multiaddr;
13use std::net::{IpAddr, Ipv4Addr, SocketAddr};
14use std::path::PathBuf;
15
16pub struct NodeSpawner {
17    /// The socket address where the node will listen.
18    socket_addr: SocketAddr,
19    /// The EVM network the node will connect to.
20    evm_network: EvmNetwork,
21    /// The rewards address used for receiving rewards.
22    rewards_address: RewardsAddress,
23    /// A vector of `Multiaddr` representing the initial peers.
24    initial_peers: Vec<Multiaddr>,
25    /// A boolean indicating whether the node should run in local mode.
26    local: bool,
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            initial_peers: vec![],
41            local: false,
42            no_upnp: false,
43            root_dir: None,
44        }
45    }
46
47    /// Set the socket address for the node.
48    ///
49    /// # Arguments
50    ///
51    /// * `socket_addr` - The `SocketAddr` where the node will listen.
52    pub fn with_socket_addr(mut self, socket_addr: SocketAddr) -> Self {
53        self.socket_addr = socket_addr;
54        self
55    }
56
57    /// Set the EVM network for the node.
58    ///
59    /// # Arguments
60    ///
61    /// * `evm_network` - The `EvmNetwork` the node will connect to.
62    pub fn with_evm_network(mut self, evm_network: EvmNetwork) -> Self {
63        self.evm_network = evm_network;
64        self
65    }
66
67    /// Set the rewards address for the node.
68    ///
69    /// # Arguments
70    ///
71    /// * `rewards_address` - The `RewardsAddress` used for distributing rewards.
72    pub fn with_rewards_address(mut self, rewards_address: RewardsAddress) -> Self {
73        self.rewards_address = rewards_address;
74        self
75    }
76
77    /// Set the initial peers for the node.
78    ///
79    /// # Arguments
80    ///
81    /// * `initial_peers` - A vector of `Multiaddr` representing the initial peers.
82    pub fn with_initial_peers(mut self, initial_peers: Vec<Multiaddr>) -> Self {
83        self.initial_peers = initial_peers;
84        self
85    }
86
87    /// Set the local mode flag for the node.
88    ///
89    /// # Arguments
90    ///
91    /// * `local` - A boolean indicating whether the node should run in local mode.
92    pub fn with_local(mut self, local: bool) -> Self {
93        self.local = local;
94        self
95    }
96
97    /// Set the to disable UPnP on the node.
98    ///
99    /// # Arguments
100    ///
101    /// * `no_upnp` - A boolean indicating whether UPnP should be disabled.
102    pub fn with_no_upnp(mut self, no_upnp: bool) -> Self {
103        self.no_upnp = no_upnp;
104        self
105    }
106
107    /// Set the root directory for the node.
108    ///
109    /// # Arguments
110    ///
111    /// * `root_dir` - An optional `PathBuf` representing the root directory for the node.
112    pub fn with_root_dir(mut self, root_dir: Option<PathBuf>) -> Self {
113        self.root_dir = root_dir;
114        self
115    }
116
117    /// Spawn the node using the configured parameters.
118    ///
119    /// # Returns
120    ///
121    /// An `eyre::Result` containing a `RunningNode` if successful, or an error.
122    pub async fn spawn(self) -> eyre::Result<RunningNode> {
123        spawn_node(
124            self.socket_addr,
125            self.evm_network,
126            self.rewards_address,
127            self.initial_peers,
128            self.local,
129            self.no_upnp,
130            &self.root_dir,
131        )
132        .await
133    }
134}
135
136impl Default for NodeSpawner {
137    fn default() -> Self {
138        Self::new()
139    }
140}
141
142async fn spawn_node(
143    socket_addr: SocketAddr,
144    evm_network: EvmNetwork,
145    rewards_address: RewardsAddress,
146    initial_peers: Vec<Multiaddr>,
147    local: bool,
148    no_upnp: bool,
149    root_dir: &Option<PathBuf>,
150) -> eyre::Result<RunningNode> {
151    let (root_dir, keypair) = get_root_dir_and_keypair(root_dir)?;
152
153    let mut node_builder = NodeBuilder::new(
154        keypair,
155        initial_peers,
156        rewards_address,
157        evm_network,
158        socket_addr,
159        root_dir,
160    );
161    node_builder.local(local);
162    node_builder.no_upnp(no_upnp);
163
164    let running_node = node_builder.build_and_run()?;
165
166    // Verify that node is running
167    let mut retries: u8 = 0;
168
169    let listen_addrs: Vec<Multiaddr> = loop {
170        // Wait till we have at least 1 listen addrs
171        if let Ok(listen_addrs) = running_node.get_listen_addrs().await {
172            if !listen_addrs.is_empty() {
173                break Ok(listen_addrs);
174            }
175        }
176
177        if retries >= 3 {
178            break Err(eyre::eyre!(
179                "Failed to get listen addresses after {} retries",
180                retries
181            ));
182        }
183
184        retries += 1;
185
186        tokio::time::sleep(tokio::time::Duration::from_secs(retries as u64)).await;
187    }?;
188
189    info!("Node listening on addresses: {:?}", listen_addrs);
190
191    Ok(running_node)
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197    use ant_evm::EvmNetwork;
198    use futures::StreamExt;
199    use libp2p::swarm::dummy;
200
201    #[tokio::test]
202    async fn test_launch_node() {
203        let evm_network = EvmNetwork::ArbitrumSepoliaTest;
204
205        let running_node = NodeSpawner::new()
206            .with_evm_network(evm_network)
207            .with_local(true)
208            .spawn()
209            .await
210            .unwrap();
211
212        let listen_addrs = running_node.get_listen_addrs().await.unwrap();
213
214        assert!(!listen_addrs.is_empty());
215
216        let mut swarm = libp2p::SwarmBuilder::with_new_identity()
217            .with_tokio()
218            .with_quic()
219            .with_behaviour(|_| dummy::Behaviour)
220            .unwrap()
221            .build();
222
223        let address = listen_addrs.first().unwrap().clone();
224
225        assert!(swarm.dial(address).is_ok());
226        assert!(matches!(
227            swarm.next().await,
228            Some(libp2p::swarm::SwarmEvent::ConnectionEstablished { .. })
229        ));
230
231        running_node.shutdown();
232    }
233}