1use std::any::TypeId;
2use std::time::Duration;
3
4use blockstore::Blockstore;
5use libp2p::identity::Keypair;
6use libp2p::Multiaddr;
7use tracing::{info, warn};
8
9use crate::blockstore::InMemoryBlockstore;
10use crate::events::EventSubscriber;
11use crate::network::Network;
12use crate::node::{Node, NodeConfig, Result};
13use crate::store::{InMemoryStore, Store};
14#[cfg(target_arch = "wasm32")]
15use crate::utils::resolve_bootnode_addresses;
16
17const HOUR: u64 = 60 * 60;
18const DAY: u64 = 24 * HOUR;
19
20pub const DEFAULT_SAMPLING_WINDOW: Duration = Duration::from_secs(30 * DAY);
22pub const MIN_SAMPLING_WINDOW: Duration = Duration::from_secs(60);
24
25pub const DEFAULT_PRUNING_DELAY: Duration = Duration::from_secs(HOUR);
27pub const MIN_PRUNING_DELAY: Duration = Duration::from_secs(60);
29
30pub struct NodeBuilder<B, S>
32where
33 B: Blockstore + 'static,
34 S: Store + 'static,
35{
36 blockstore: B,
37 store: S,
38 keypair: Option<Keypair>,
39 network: Option<Network>,
40 bootnodes: Vec<Multiaddr>,
41 listen: Vec<Multiaddr>,
42 sync_batch_size: Option<u64>,
43 sampling_window: Option<Duration>,
44 pruning_delay: Option<Duration>,
45}
46
47#[derive(Debug, thiserror::Error)]
49pub enum NodeBuilderError {
50 #[error("Network is not specified")]
52 NetworkNotSpecified,
53
54 #[error("Sampling window is {0:?} but cannot be smaller than {MIN_SAMPLING_WINDOW:?}")]
56 SamplingWindowTooSmall(Duration),
57
58 #[error("Pruning delay is {0:?} but cannot be smaller than {MIN_PRUNING_DELAY:?}")]
60 PruningDelayTooSmall(Duration),
61
62 #[error("Could not resolve bootnode addresses: {0}")]
64 FailedResolvingBootnodes(String),
65}
66
67impl NodeBuilder<InMemoryBlockstore, InMemoryStore> {
68 pub fn new() -> Self {
88 NodeBuilder {
89 blockstore: InMemoryBlockstore::new(),
90 store: InMemoryStore::new(),
91 keypair: None,
92 network: None,
93 bootnodes: Vec::new(),
94 listen: Vec::new(),
95 sync_batch_size: None,
96 sampling_window: None,
97 pruning_delay: None,
98 }
99 }
100}
101
102impl Default for NodeBuilder<InMemoryBlockstore, InMemoryStore> {
103 fn default() -> Self {
104 NodeBuilder::new()
105 }
106}
107
108impl<B, S> NodeBuilder<B, S>
109where
110 B: Blockstore + 'static,
111 S: Store + 'static,
112{
113 pub async fn start(self) -> Result<Node<B, S>> {
115 let (node, _) = self.start_subscribed().await?;
116 Ok(node)
117 }
118
119 pub async fn start_subscribed(self) -> Result<(Node<B, S>, EventSubscriber)> {
124 let config = self.build_config().await?;
125 Node::start(config).await
126 }
127
128 pub fn blockstore<B2>(self, blockstore: B2) -> NodeBuilder<B2, S>
132 where
133 B2: Blockstore + 'static,
134 {
135 NodeBuilder {
136 blockstore,
137 store: self.store,
138 keypair: self.keypair,
139 network: self.network,
140 bootnodes: self.bootnodes,
141 listen: self.listen,
142 sync_batch_size: self.sync_batch_size,
143 sampling_window: self.sampling_window,
144 pruning_delay: self.pruning_delay,
145 }
146 }
147
148 pub fn store<S2>(self, store: S2) -> NodeBuilder<B, S2>
152 where
153 S2: Store + 'static,
154 {
155 NodeBuilder {
156 blockstore: self.blockstore,
157 store,
158 keypair: self.keypair,
159 network: self.network,
160 bootnodes: self.bootnodes,
161 listen: self.listen,
162 sync_batch_size: self.sync_batch_size,
163 sampling_window: self.sampling_window,
164 pruning_delay: self.pruning_delay,
165 }
166 }
167
168 pub fn network(self, network: Network) -> Self {
170 NodeBuilder {
171 network: Some(network),
172 ..self
173 }
174 }
175
176 pub fn keypair(self, keypair: Keypair) -> Self {
180 NodeBuilder {
181 keypair: Some(keypair),
182 ..self
183 }
184 }
185
186 pub fn bootnodes<I>(self, addrs: I) -> Self
190 where
191 I: IntoIterator<Item = Multiaddr>,
192 {
193 NodeBuilder {
194 bootnodes: addrs.into_iter().collect(),
195 ..self
196 }
197 }
198
199 pub fn listen<I>(self, addrs: I) -> Self
201 where
202 I: IntoIterator<Item = Multiaddr>,
203 {
204 NodeBuilder {
205 listen: addrs.into_iter().collect(),
206 ..self
207 }
208 }
209
210 pub fn sync_batch_size(self, batch_size: u64) -> Self {
214 NodeBuilder {
215 sync_batch_size: Some(batch_size),
216 ..self
217 }
218 }
219
220 pub fn sampling_window(self, dur: Duration) -> Self {
228 NodeBuilder {
229 sampling_window: Some(dur),
230 ..self
231 }
232 }
233
234 pub fn pruning_delay(self, dur: Duration) -> Self {
243 NodeBuilder {
244 pruning_delay: Some(dur),
245 ..self
246 }
247 }
248
249 async fn build_config(self) -> Result<NodeConfig<B, S>, NodeBuilderError> {
250 let network = self.network.ok_or(NodeBuilderError::NetworkNotSpecified)?;
251
252 let bootnodes = if self.bootnodes.is_empty() {
253 network.canonical_bootnodes().collect()
254 } else {
255 self.bootnodes
256 };
257
258 if bootnodes.is_empty() && self.listen.is_empty() {
259 warn!("Node has empty bootnodes and listening addresses. It will never connect to another peer.");
263 }
264
265 #[cfg(target_arch = "wasm32")]
266 let bootnodes = resolve_bootnode_addresses(bootnodes)
267 .await
268 .map_err(|e| NodeBuilderError::FailedResolvingBootnodes(e.to_string()))?;
269
270 let in_memory_stores_used = TypeId::of::<S>() == TypeId::of::<InMemoryStore>()
277 || TypeId::of::<B>() == TypeId::of::<InMemoryBlockstore>();
278
279 let sampling_window = if let Some(dur) = self.sampling_window {
280 dur
281 } else if in_memory_stores_used {
282 MIN_SAMPLING_WINDOW
283 } else {
284 DEFAULT_SAMPLING_WINDOW
285 };
286
287 let pruning_delay = if let Some(dur) = self.pruning_delay {
288 dur
289 } else if in_memory_stores_used {
290 MIN_PRUNING_DELAY
291 } else {
292 DEFAULT_PRUNING_DELAY
293 };
294
295 if sampling_window < MIN_SAMPLING_WINDOW {
296 return Err(NodeBuilderError::SamplingWindowTooSmall(sampling_window));
297 }
298
299 if pruning_delay < MIN_PRUNING_DELAY {
300 return Err(NodeBuilderError::PruningDelayTooSmall(pruning_delay));
301 }
302
303 let pruning_window = sampling_window.saturating_add(pruning_delay);
304
305 info!("Sampling window: {sampling_window:?}, Pruning window: {pruning_window:?}",);
306
307 Ok(NodeConfig {
308 blockstore: self.blockstore,
309 store: self.store,
310 network_id: network.id().to_owned(),
311 p2p_local_keypair: self.keypair.unwrap_or_else(Keypair::generate_ed25519),
312 p2p_bootnodes: bootnodes,
313 p2p_listen_on: self.listen,
314 sync_batch_size: self.sync_batch_size.unwrap_or(512),
315 sampling_window,
316 pruning_window,
317 })
318 }
319}