reth_cli_commands/p2p/
bootnode.rs

1//! Standalone bootnode command
2
3use clap::Parser;
4use reth_cli_util::{get_secret_key, load_secret_key::rng_secret_key};
5use reth_discv4::{DiscoveryUpdate, Discv4, Discv4Config};
6use reth_discv5::{discv5::Event, Config, Discv5};
7use reth_net_nat::NatResolver;
8use reth_network_peers::NodeRecord;
9use secp256k1::SecretKey;
10use std::{net::SocketAddr, path::PathBuf};
11use tokio::select;
12use tokio_stream::StreamExt;
13use tracing::info;
14
15/// Start a discovery only bootnode.
16#[derive(Parser, Debug)]
17pub struct Command {
18    /// Listen address for the bootnode (default: "0.0.0.0:30301").
19    #[arg(long, default_value = "0.0.0.0:30301")]
20    pub addr: SocketAddr,
21
22    /// Secret key to use for the bootnode.
23    ///
24    /// This will also deterministically set the peer ID.  
25    /// If a path is provided but no key exists at that path,  
26    /// a new random secret will be generated and stored there.  
27    /// If no path is specified, a new ephemeral random secret will be used.
28    #[arg(long, value_name = "PATH")]
29    pub p2p_secret_key: Option<PathBuf>,
30
31    /// NAT resolution method (any|none|upnp|publicip|extip:\<IP\>)
32    #[arg(long, default_value = "any")]
33    pub nat: NatResolver,
34
35    /// Run a v5 topic discovery bootnode.
36    #[arg(long)]
37    pub v5: bool,
38}
39
40impl Command {
41    /// Execute the bootnode command.
42    pub async fn execute(self) -> eyre::Result<()> {
43        info!("Bootnode started with config: {self:?}");
44
45        let sk = self.network_secret()?;
46        let local_enr = NodeRecord::from_secret_key(self.addr, &sk);
47
48        let config = Discv4Config::builder().external_ip_resolver(Some(self.nat)).build();
49
50        let (_discv4, mut discv4_service) = Discv4::bind(self.addr, local_enr, sk, config).await?;
51
52        info!("Started discv4 at address: {local_enr:?}");
53
54        let mut discv4_updates = discv4_service.update_stream();
55        discv4_service.spawn();
56
57        // Optional discv5 update event listener if v5 is enabled
58        let mut discv5_updates = None;
59
60        if self.v5 {
61            info!("Starting discv5");
62            let config = Config::builder(self.addr).build();
63            let (_discv5, updates, _local_enr_discv5) = Discv5::start(&sk, config).await?;
64            discv5_updates = Some(updates);
65        };
66
67        // event info loop for logging
68        loop {
69            select! {
70                //discv4 updates
71                update = discv4_updates.next() => {
72                    if let Some(update) = update {
73                        match update {
74                            DiscoveryUpdate::Added(record) => {
75                                info!("(Discv4) new peer added, peer_id={:?}", record.id);
76                            }
77                            DiscoveryUpdate::Removed(peer_id) => {
78                                info!("(Discv4) peer with peer-id={:?} removed", peer_id);
79                            }
80                            _ => {}
81                        }
82                    } else {
83                        info!("(Discv4) update stream ended.");
84                        break;
85                    }
86                }
87                //if discv5, discv5 update stream, else do nothing
88                update = async {
89                    if let Some(updates) = &mut discv5_updates {
90                        updates.recv().await
91                    } else {
92                        futures::future::pending().await
93                    }
94                } => {
95                    if let Some(update) = update {
96                     if let Event::SessionEstablished(enr, _) = update {
97                            info!("(Discv5) new peer added, peer_id={:?}", enr.id());
98                        }
99                    } else {
100                        info!("(Discv5) update stream ended.");
101                        break;
102                    }
103                }
104            }
105        }
106
107        Ok(())
108    }
109
110    fn network_secret(&self) -> eyre::Result<SecretKey> {
111        match &self.p2p_secret_key {
112            Some(path) => Ok(get_secret_key(path)?),
113            None => Ok(rng_secret_key()),
114        }
115    }
116}