kona_node_service/actors/network/
builder.rs1use 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#[derive(Debug)]
19pub struct NetworkBuilder {
20 pub(super) discovery: Discv5Builder,
22 pub(super) gossip: GossipDriverBuilder,
24 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 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 pub fn with_gater_config(self, config: GaterConfig) -> Self {
79 Self { gossip: self.gossip.with_gater_config(config), ..self }
80 }
81
82 pub fn with_signer(self, signer: Option<BlockSigner>) -> Self {
84 Self { signer, ..self }
85 }
86
87 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 pub fn with_discovery_randomize(self, randomize: Option<Duration>) -> Self {
97 Self { discovery: self.discovery.with_discovery_randomize(randomize), ..self }
98 }
99
100 pub fn with_bootnodes(self, bootnodes: Vec<Enr>) -> Self {
102 Self { discovery: self.discovery.with_bootnodes(bootnodes), ..self }
103 }
104
105 pub fn with_peer_scoring(self, level: PeerScoreLevel) -> Self {
107 Self { gossip: self.gossip.with_peer_scoring(level), ..self }
108 }
109
110 pub fn with_topic_scoring(self, topic_scoring: bool) -> Self {
112 Self { gossip: self.gossip.with_topic_scoring(topic_scoring), ..self }
113 }
114
115 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 pub fn with_discovery_interval(self, interval: tokio::time::Duration) -> Self {
122 Self { discovery: self.discovery.with_interval(interval), ..self }
123 }
124
125 pub fn with_discovery_address(self, address: LocalNode) -> Self {
127 Self { discovery: self.discovery.with_local_node(address), ..self }
128 }
129
130 pub fn with_gossip_config(self, config: libp2p::gossipsub::Config) -> Self {
132 Self { gossip: self.gossip.with_config(config), ..self }
133 }
134
135 pub fn with_discovery_config(self, config: Discv5Config) -> Self {
137 Self { discovery: self.discovery.with_discovery_config(config), ..self }
138 }
139
140 pub fn with_gossip_address(self, addr: Multiaddr) -> Self {
142 Self { gossip: self.gossip.with_address(addr), ..self }
143 }
144
145 pub fn with_timeout(self, timeout: Duration) -> Self {
147 Self { gossip: self.gossip.with_timeout(timeout), ..self }
148 }
149
150 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 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 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}