1use std::any::TypeId;
2use std::time::Duration;
3
4use blockstore::Blockstore;
5use libp2p::Multiaddr;
6use libp2p::identity::Keypair;
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, StoreError};
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 SAMPLING_WINDOW: Duration = Duration::from_secs(7 * DAY);
22
23pub const DEFAULT_PRUNING_WINDOW: Duration = Duration::from_secs(7 * DAY + HOUR);
25pub const DEFAULT_PRUNING_WINDOW_IN_MEMORY: Duration = Duration::from_secs(0);
27
28pub struct NodeBuilder<B, S>
30where
31 B: Blockstore + 'static,
32 S: Store + 'static,
33{
34 blockstore: B,
35 store: S,
36 keypair: Option<Keypair>,
37 network: Option<Network>,
38 bootnodes: Vec<Multiaddr>,
39 listen: Vec<Multiaddr>,
40 sync_batch_size: Option<u64>,
41 pruning_window: Option<Duration>,
42}
43
44#[derive(Debug, thiserror::Error)]
46pub enum NodeBuilderError {
47 #[error("Network is not specified")]
49 NetworkNotSpecified,
50
51 #[error("Could not resolve any of the bootnode addresses")]
53 FailedResolvingBootnodes,
54
55 #[error(transparent)]
57 IdentityDecodingError(#[from] libp2p::identity::DecodingError),
58
59 #[error(transparent)]
61 StoreError(#[from] StoreError),
62}
63
64impl NodeBuilder<InMemoryBlockstore, InMemoryStore> {
65 pub fn new() -> Self {
85 NodeBuilder {
86 blockstore: InMemoryBlockstore::new(),
87 store: InMemoryStore::new(),
88 keypair: None,
89 network: None,
90 bootnodes: Vec::new(),
91 listen: Vec::new(),
92 sync_batch_size: None,
93 pruning_window: None,
94 }
95 }
96}
97
98impl Default for NodeBuilder<InMemoryBlockstore, InMemoryStore> {
99 fn default() -> Self {
100 NodeBuilder::new()
101 }
102}
103
104impl<B, S> NodeBuilder<B, S>
105where
106 B: Blockstore + 'static,
107 S: Store + 'static,
108{
109 pub async fn start(self) -> Result<Node<B, S>> {
111 let (node, _) = self.start_subscribed().await?;
112 Ok(node)
113 }
114
115 pub async fn start_subscribed(self) -> Result<(Node<B, S>, EventSubscriber)> {
120 let config = self.build_config().await?;
121 Node::start(config).await
122 }
123
124 pub fn blockstore<B2>(self, blockstore: B2) -> NodeBuilder<B2, S>
128 where
129 B2: Blockstore + 'static,
130 {
131 NodeBuilder {
132 blockstore,
133 store: self.store,
134 keypair: self.keypair,
135 network: self.network,
136 bootnodes: self.bootnodes,
137 listen: self.listen,
138 sync_batch_size: self.sync_batch_size,
139 pruning_window: self.pruning_window,
140 }
141 }
142
143 pub fn store<S2>(self, store: S2) -> NodeBuilder<B, S2>
147 where
148 S2: Store + 'static,
149 {
150 NodeBuilder {
151 blockstore: self.blockstore,
152 store,
153 keypair: self.keypair,
154 network: self.network,
155 bootnodes: self.bootnodes,
156 listen: self.listen,
157 sync_batch_size: self.sync_batch_size,
158 pruning_window: self.pruning_window,
159 }
160 }
161
162 pub fn network(self, network: Network) -> Self {
164 NodeBuilder {
165 network: Some(network),
166 ..self
167 }
168 }
169
170 pub fn keypair(self, keypair: Keypair) -> Self {
186 NodeBuilder {
187 keypair: Some(keypair),
188 ..self
189 }
190 }
191
192 pub fn bootnodes<I>(self, addrs: I) -> Self
196 where
197 I: IntoIterator<Item = Multiaddr>,
198 {
199 NodeBuilder {
200 bootnodes: addrs.into_iter().collect(),
201 ..self
202 }
203 }
204
205 pub fn listen<I>(self, addrs: I) -> Self
207 where
208 I: IntoIterator<Item = Multiaddr>,
209 {
210 NodeBuilder {
211 listen: addrs.into_iter().collect(),
212 ..self
213 }
214 }
215
216 pub fn sync_batch_size(self, batch_size: u64) -> Self {
220 NodeBuilder {
221 sync_batch_size: Some(batch_size),
222 ..self
223 }
224 }
225
226 pub fn pruning_window(self, dur: Duration) -> Self {
237 NodeBuilder {
238 pruning_window: Some(dur),
239 ..self
240 }
241 }
242
243 async fn build_config(self) -> Result<NodeConfig<B, S>, NodeBuilderError> {
244 let network = self.network.ok_or(NodeBuilderError::NetworkNotSpecified)?;
245
246 let bootnodes = if self.bootnodes.is_empty() {
247 network.canonical_bootnodes().collect()
248 } else {
249 self.bootnodes
250 };
251
252 if bootnodes.is_empty() && self.listen.is_empty() {
253 warn!(
257 "Node has empty bootnodes and listening addresses. It will never connect to another peer."
258 );
259 }
260
261 #[cfg(target_arch = "wasm32")]
262 let bootnodes = {
263 let bootnodes_was_empty = bootnodes.is_empty();
264 let bootnodes = resolve_bootnode_addresses(bootnodes).await;
265
266 if bootnodes.is_empty() && !bootnodes_was_empty {
269 return Err(NodeBuilderError::FailedResolvingBootnodes);
270 }
271
272 bootnodes
273 };
274
275 let in_memory_stores_used = TypeId::of::<S>() == TypeId::of::<InMemoryStore>()
279 || TypeId::of::<B>() == TypeId::of::<InMemoryBlockstore>();
280
281 let pruning_window = if let Some(dur) = self.pruning_window {
282 dur
283 } else if in_memory_stores_used {
284 DEFAULT_PRUNING_WINDOW_IN_MEMORY
285 } else {
286 DEFAULT_PRUNING_WINDOW
287 };
288
289 info!("Sampling window: {SAMPLING_WINDOW:?}, Pruning window: {pruning_window:?}");
290
291 let p2p_local_keypair = if let Some(keypair) = self.keypair {
292 keypair
293 } else {
294 self.store.get_identity().await?
295 };
296
297 Ok(NodeConfig {
298 blockstore: self.blockstore,
299 store: self.store,
300 network_id: network.id().to_owned(),
301 p2p_local_keypair,
302 p2p_bootnodes: bootnodes,
303 p2p_listen_on: self.listen,
304 sync_batch_size: self.sync_batch_size.unwrap_or(512),
305 sampling_window: SAMPLING_WINDOW,
306 pruning_window,
307 })
308 }
309}