lumina_node/node/
builder.rs1use 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 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
56impl NodeBuilder<InMemoryBlockstore, InMemoryStore> {
57 pub fn new() -> Self {
77 NodeBuilder {
78 blockstore: InMemoryBlockstore::new(),
79 store: InMemoryStore::new(),
80 keypair: None,
81 network: None,
82 bootnodes: Vec::new(),
83 listen: Vec::new(),
84 sync_batch_size: None,
85 pruning_window: None,
86 }
87 }
88}
89
90impl Default for NodeBuilder<InMemoryBlockstore, InMemoryStore> {
91 fn default() -> Self {
92 NodeBuilder::new()
93 }
94}
95
96impl<B, S> NodeBuilder<B, S>
97where
98 B: Blockstore + 'static,
99 S: Store + 'static,
100{
101 pub async fn start(self) -> Result<Node<B, S>> {
103 let (node, _) = self.start_subscribed().await?;
104 Ok(node)
105 }
106
107 pub async fn start_subscribed(self) -> Result<(Node<B, S>, EventSubscriber)> {
112 let config = self.build_config().await?;
113 Node::start(config).await
114 }
115
116 pub fn blockstore<B2>(self, blockstore: B2) -> NodeBuilder<B2, S>
120 where
121 B2: Blockstore + 'static,
122 {
123 NodeBuilder {
124 blockstore,
125 store: self.store,
126 keypair: self.keypair,
127 network: self.network,
128 bootnodes: self.bootnodes,
129 listen: self.listen,
130 sync_batch_size: self.sync_batch_size,
131 pruning_window: self.pruning_window,
132 }
133 }
134
135 pub fn store<S2>(self, store: S2) -> NodeBuilder<B, S2>
139 where
140 S2: Store + 'static,
141 {
142 NodeBuilder {
143 blockstore: self.blockstore,
144 store,
145 keypair: self.keypair,
146 network: self.network,
147 bootnodes: self.bootnodes,
148 listen: self.listen,
149 sync_batch_size: self.sync_batch_size,
150 pruning_window: self.pruning_window,
151 }
152 }
153
154 pub fn network(self, network: Network) -> Self {
156 NodeBuilder {
157 network: Some(network),
158 ..self
159 }
160 }
161
162 pub fn keypair(self, keypair: Keypair) -> Self {
166 NodeBuilder {
167 keypair: Some(keypair),
168 ..self
169 }
170 }
171
172 pub fn bootnodes<I>(self, addrs: I) -> Self
176 where
177 I: IntoIterator<Item = Multiaddr>,
178 {
179 NodeBuilder {
180 bootnodes: addrs.into_iter().collect(),
181 ..self
182 }
183 }
184
185 pub fn listen<I>(self, addrs: I) -> Self
187 where
188 I: IntoIterator<Item = Multiaddr>,
189 {
190 NodeBuilder {
191 listen: addrs.into_iter().collect(),
192 ..self
193 }
194 }
195
196 pub fn sync_batch_size(self, batch_size: u64) -> Self {
200 NodeBuilder {
201 sync_batch_size: Some(batch_size),
202 ..self
203 }
204 }
205
206 pub fn pruning_window(self, dur: Duration) -> Self {
217 NodeBuilder {
218 pruning_window: Some(dur),
219 ..self
220 }
221 }
222
223 async fn build_config(self) -> Result<NodeConfig<B, S>, NodeBuilderError> {
224 let network = self.network.ok_or(NodeBuilderError::NetworkNotSpecified)?;
225
226 let bootnodes = if self.bootnodes.is_empty() {
227 network.canonical_bootnodes().collect()
228 } else {
229 self.bootnodes
230 };
231
232 if bootnodes.is_empty() && self.listen.is_empty() {
233 warn!("Node has empty bootnodes and listening addresses. It will never connect to another peer.");
237 }
238
239 #[cfg(target_arch = "wasm32")]
240 let bootnodes = {
241 let bootnodes_was_empty = bootnodes.is_empty();
242 let bootnodes = resolve_bootnode_addresses(bootnodes).await;
243
244 if bootnodes.is_empty() && !bootnodes_was_empty {
247 return Err(NodeBuilderError::FailedResolvingBootnodes);
248 }
249
250 bootnodes
251 };
252
253 let in_memory_stores_used = TypeId::of::<S>() == TypeId::of::<InMemoryStore>()
257 || TypeId::of::<B>() == TypeId::of::<InMemoryBlockstore>();
258
259 let pruning_window = if let Some(dur) = self.pruning_window {
260 dur
261 } else if in_memory_stores_used {
262 DEFAULT_PRUNING_WINDOW_IN_MEMORY
263 } else {
264 DEFAULT_PRUNING_WINDOW
265 };
266
267 info!("Sampling window: {SAMPLING_WINDOW:?}, Pruning window: {pruning_window:?}",);
268
269 Ok(NodeConfig {
270 blockstore: self.blockstore,
271 store: self.store,
272 network_id: network.id().to_owned(),
273 p2p_local_keypair: self.keypair.unwrap_or_else(Keypair::generate_ed25519),
274 p2p_bootnodes: bootnodes,
275 p2p_listen_on: self.listen,
276 sync_batch_size: self.sync_batch_size.unwrap_or(512),
277 sampling_window: SAMPLING_WINDOW,
278 pruning_window,
279 })
280 }
281}