kona_node_service/actors/network/
builder.rs

1//! Network Builder Module.
2
3use alloy_primitives::Address;
4use discv5::{Config as Discv5Config, Enr};
5use kona_genesis::RollupConfig;
6use kona_p2p::{Discv5Builder, GaterConfig, GossipDriverBuilder, LocalNode};
7use kona_peers::{PeerMonitoring, PeerScoreLevel};
8use kona_sources::BlockSigner;
9use libp2p::{Multiaddr, identity::Keypair};
10use std::{path::PathBuf, time::Duration};
11
12use crate::{
13    NetworkBuilderError,
14    actors::network::{NetworkConfig, NetworkDriver},
15};
16
17/// Constructs a [`NetworkDriver`] for the OP Stack Consensus Layer.
18#[derive(Debug)]
19pub struct NetworkBuilder {
20    /// The discovery driver.
21    pub(super) discovery: Discv5Builder,
22    /// The gossip driver.
23    pub(super) gossip: GossipDriverBuilder,
24    /// A signer for payloads.
25    pub(super) signer: Option<BlockSigner>,
26}
27
28impl From<NetworkConfig> for NetworkBuilder {
29    fn from(config: NetworkConfig) -> Self {
30        Self::new(
31            config.rollup_config,
32            config.unsafe_block_signer,
33            config.gossip_address,
34            config.keypair,
35            config.discovery_address,
36            config.discovery_config,
37        )
38        .with_discovery_randomize(config.discovery_randomize)
39        .with_bootstore(config.bootstore)
40        .with_bootnodes(config.bootnodes)
41        .with_discovery_interval(config.discovery_interval)
42        .with_gossip_config(config.gossip_config)
43        .with_peer_scoring(config.scoring)
44        .with_peer_monitoring(config.monitor_peers)
45        .with_topic_scoring(config.topic_scoring)
46        .with_gater_config(config.gater_config)
47        .with_signer(config.gossip_signer)
48    }
49}
50
51impl NetworkBuilder {
52    /// Creates a new [`NetworkBuilder`].
53    pub const fn new(
54        rollup_config: RollupConfig,
55        unsafe_block_signer: Address,
56        gossip_addr: Multiaddr,
57        keypair: Keypair,
58        discovery_address: LocalNode,
59        discovery_config: discv5::Config,
60    ) -> Self {
61        Self {
62            discovery: Discv5Builder::new(
63                discovery_address,
64                rollup_config.l2_chain_id.id(),
65                discovery_config,
66            ),
67            gossip: GossipDriverBuilder::new(
68                rollup_config,
69                unsafe_block_signer,
70                gossip_addr,
71                keypair,
72            ),
73            signer: None,
74        }
75    }
76
77    /// Sets the configuration for the connection gater.
78    pub fn with_gater_config(self, config: GaterConfig) -> Self {
79        Self { gossip: self.gossip.with_gater_config(config), ..self }
80    }
81
82    /// Sets the signer for the [`NetworkBuilder`].
83    pub fn with_signer(self, signer: Option<BlockSigner>) -> Self {
84        Self { signer, ..self }
85    }
86
87    /// Sets the bootstore path for the [`Discv5Builder`].
88    pub fn with_bootstore(self, bootstore: Option<PathBuf>) -> Self {
89        if let Some(bootstore) = bootstore {
90            return Self { discovery: self.discovery.with_bootstore(bootstore), ..self };
91        }
92        self
93    }
94
95    /// Sets the interval at which to randomize discovery peers.
96    pub fn with_discovery_randomize(self, randomize: Option<Duration>) -> Self {
97        Self { discovery: self.discovery.with_discovery_randomize(randomize), ..self }
98    }
99
100    /// Sets the initial bootnodes to add to the bootstore.
101    pub fn with_bootnodes(self, bootnodes: Vec<Enr>) -> Self {
102        Self { discovery: self.discovery.with_bootnodes(bootnodes), ..self }
103    }
104
105    /// Sets the peer scoring based on the given [`PeerScoreLevel`].
106    pub fn with_peer_scoring(self, level: PeerScoreLevel) -> Self {
107        Self { gossip: self.gossip.with_peer_scoring(level), ..self }
108    }
109
110    /// Sets topic scoring for the [`GossipDriverBuilder`].
111    pub fn with_topic_scoring(self, topic_scoring: bool) -> Self {
112        Self { gossip: self.gossip.with_topic_scoring(topic_scoring), ..self }
113    }
114
115    /// Sets the peer monitoring for the [`GossipDriverBuilder`].
116    pub fn with_peer_monitoring(self, peer_monitoring: Option<PeerMonitoring>) -> Self {
117        Self { gossip: self.gossip.with_peer_monitoring(peer_monitoring), ..self }
118    }
119
120    /// Sets the discovery interval for the [`Discv5Builder`].
121    pub fn with_discovery_interval(self, interval: tokio::time::Duration) -> Self {
122        Self { discovery: self.discovery.with_interval(interval), ..self }
123    }
124
125    /// Sets the address for the [`Discv5Builder`].
126    pub fn with_discovery_address(self, address: LocalNode) -> Self {
127        Self { discovery: self.discovery.with_local_node(address), ..self }
128    }
129
130    /// Sets the gossipsub config for the [`GossipDriverBuilder`].
131    pub fn with_gossip_config(self, config: libp2p::gossipsub::Config) -> Self {
132        Self { gossip: self.gossip.with_config(config), ..self }
133    }
134
135    /// Sets the [`Discv5Config`] for the [`Discv5Builder`].
136    pub fn with_discovery_config(self, config: Discv5Config) -> Self {
137        Self { discovery: self.discovery.with_discovery_config(config), ..self }
138    }
139
140    /// Sets the gossip address for the [`GossipDriverBuilder`].
141    pub fn with_gossip_address(self, addr: Multiaddr) -> Self {
142        Self { gossip: self.gossip.with_address(addr), ..self }
143    }
144
145    /// Sets the timeout for the [`GossipDriverBuilder`].
146    pub fn with_timeout(self, timeout: Duration) -> Self {
147        Self { gossip: self.gossip.with_timeout(timeout), ..self }
148    }
149
150    /// Builds the [`NetworkDriver`].
151    pub fn build(self) -> Result<NetworkDriver, NetworkBuilderError> {
152        let (gossip, unsafe_block_signer_sender) = self.gossip.build()?;
153        let discovery = self.discovery.build()?;
154
155        Ok(NetworkDriver { gossip, discovery, unsafe_block_signer_sender, signer: self.signer })
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162    use alloy_chains::Chain;
163    use discv5::{ConfigBuilder, ListenConfig, enr::CombinedKey};
164    use libp2p::gossipsub::IdentTopic;
165    use std::net::{IpAddr, Ipv4Addr, SocketAddr};
166
167    #[derive(Debug)]
168    struct NetworkBuilderParams {
169        rollup_config: RollupConfig,
170        signer: Address,
171    }
172
173    impl Default for NetworkBuilderParams {
174        fn default() -> Self {
175            Self { rollup_config: RollupConfig::default(), signer: Address::random() }
176        }
177    }
178
179    fn network_builder(params: NetworkBuilderParams) -> NetworkBuilder {
180        let keypair = Keypair::generate_secp256k1();
181        let signer = params.signer;
182        let gossip = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 9099);
183        let mut gossip_addr = Multiaddr::from(gossip.ip());
184        gossip_addr.push(libp2p::multiaddr::Protocol::Tcp(gossip.port()));
185
186        let CombinedKey::Secp256k1(secret_key) = CombinedKey::generate_secp256k1() else {
187            unreachable!()
188        };
189
190        let discovery_address =
191            LocalNode::new(secret_key, IpAddr::V4(Ipv4Addr::UNSPECIFIED), 9098, 9098);
192
193        let discovery_config =
194            ConfigBuilder::new(ListenConfig::from_ip(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 9098))
195                .build();
196
197        NetworkBuilder::new(
198            params.rollup_config,
199            signer,
200            gossip_addr,
201            keypair,
202            discovery_address,
203            discovery_config,
204        )
205    }
206
207    #[test]
208    fn test_build_simple_succeeds() {
209        let signer = Address::random();
210        let CombinedKey::Secp256k1(secret_key) = CombinedKey::generate_secp256k1() else {
211            unreachable!()
212        };
213        let disc_listen = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 9097);
214        let disc_enr = LocalNode::new(secret_key, IpAddr::V4(Ipv4Addr::UNSPECIFIED), 9098, 9098);
215        let gossip = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 9099);
216        let mut gossip_addr = Multiaddr::from(gossip.ip());
217        gossip_addr.push(libp2p::multiaddr::Protocol::Tcp(gossip.port()));
218
219        let driver = network_builder(NetworkBuilderParams {
220            rollup_config: RollupConfig {
221                l2_chain_id: Chain::optimism_mainnet(),
222                ..Default::default()
223            },
224            signer,
225        })
226        .with_gossip_address(gossip_addr.clone())
227        .with_discovery_address(disc_enr)
228        .with_discovery_config(ConfigBuilder::new(disc_listen.into()).build())
229        .build()
230        .unwrap();
231
232        // Driver Assertions
233        let id = 10;
234        assert_eq!(driver.gossip.addr, gossip_addr);
235        assert_eq!(driver.discovery.chain_id, id);
236        assert_eq!(driver.discovery.disc.local_enr().tcp4().unwrap(), 9098);
237
238        // Block Handler Assertions
239        assert_eq!(driver.gossip.handler.rollup_config.l2_chain_id, id);
240        let v1 = IdentTopic::new(format!("/optimism/{}/0/blocks", id));
241        println!("{:?}", driver.gossip.handler.blocks_v1_topic);
242        assert_eq!(driver.gossip.handler.blocks_v1_topic.hash(), v1.hash());
243        let v2 = IdentTopic::new(format!("/optimism/{}/1/blocks", id));
244        assert_eq!(driver.gossip.handler.blocks_v2_topic.hash(), v2.hash());
245        let v3 = IdentTopic::new(format!("/optimism/{}/2/blocks", id));
246        assert_eq!(driver.gossip.handler.blocks_v3_topic.hash(), v3.hash());
247        let v4 = IdentTopic::new(format!("/optimism/{}/3/blocks", id));
248        assert_eq!(driver.gossip.handler.blocks_v4_topic.hash(), v4.hash());
249    }
250
251    #[test]
252    fn test_build_network_custom_configs() {
253        let gossip = SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 9099);
254        let mut gossip_addr = Multiaddr::from(gossip.ip());
255        gossip_addr.push(libp2p::multiaddr::Protocol::Tcp(gossip.port()));
256
257        let CombinedKey::Secp256k1(secret_key) = CombinedKey::generate_secp256k1() else {
258            unreachable!()
259        };
260
261        let disc = LocalNode::new(secret_key, IpAddr::V4(Ipv4Addr::UNSPECIFIED), 9097, 9097);
262        let discovery_config =
263            ConfigBuilder::new(ListenConfig::from_ip(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 9098))
264                .build();
265        let driver = network_builder(Default::default())
266            .with_gossip_address(gossip_addr)
267            .with_discovery_address(disc)
268            .with_discovery_config(discovery_config)
269            .build()
270            .unwrap();
271
272        assert_eq!(driver.discovery.disc.local_enr().tcp4().unwrap(), 9097);
273    }
274}