lumina_node_wasm/
client.rs1use std::time::Duration;
4
5use blockstore::EitherBlockstore;
6use celestia_types::nmt::Namespace;
7use celestia_types::{Blob, ExtendedHeader};
8use js_sys::Array;
9use libp2p::Multiaddr;
10use libp2p::identity::Keypair;
11use lumina_node::blockstore::{InMemoryBlockstore, IndexedDbBlockstore};
12use lumina_node::network;
13use lumina_node::node::{DEFAULT_PRUNING_WINDOW_IN_MEMORY, NodeBuilder};
14use lumina_node::store::{EitherStore, InMemoryStore, IndexedDbStore, SamplingMetadata};
15use serde::{Deserialize, Serialize};
16use tracing::{debug, error};
17use wasm_bindgen::prelude::*;
18use web_sys::BroadcastChannel;
19
20use crate::commands::{CheckableResponseExt, NodeCommand, SingleHeaderQuery};
21use crate::error::{Context, Result};
22use crate::ports::WorkerClient;
23use crate::utils::{
24 Network, is_safari, js_value_from_display, request_storage_persistence, timeout,
25};
26use crate::worker::{WasmBlockstore, WasmStore};
27pub use crate::wrapper::libp2p::{ConnectionCountersSnapshot, NetworkInfoSnapshot};
28use crate::wrapper::node::{PeerTrackerInfoSnapshot, SyncingInfoSnapshot};
29
30#[wasm_bindgen(inspectable, js_name = NodeConfig)]
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct WasmNodeConfig {
34 pub network: Network,
36
37 #[wasm_bindgen(getter_with_clone)]
39 pub bootnodes: Vec<String>,
40
41 #[wasm_bindgen(getter_with_clone)]
44 pub identity_key: Option<Vec<u8>>,
45
46 #[wasm_bindgen(js_name = usePersistentMemory)]
50 pub use_persistent_memory: bool,
51
52 #[wasm_bindgen(js_name = customPruningWindowSecs)]
63 pub custom_pruning_window_secs: Option<u32>,
64}
65
66#[wasm_bindgen]
71pub struct NodeClient {
72 worker: WorkerClient,
73}
74
75#[wasm_bindgen]
76impl NodeClient {
77 #[wasm_bindgen(constructor)]
80 #[allow(deprecated)] pub async fn new(port: JsValue) -> Result<NodeClient> {
82 if !is_safari()?
84 && let Err(e) = request_storage_persistence().await
85 {
86 error!("Error requesting storage persistence: {e}");
87 }
88
89 let worker = WorkerClient::new(port)?;
90
91 loop {
93 if timeout(100, worker.exec(NodeCommand::InternalPing))
94 .await
95 .is_ok()
96 {
97 break;
98 }
99 }
100
101 debug!("Connected to worker");
102
103 Ok(Self { worker })
104 }
105
106 #[wasm_bindgen(js_name = addConnectionToWorker)]
108 pub async fn add_connection_to_worker(&self, port: JsValue) -> Result<()> {
109 self.worker.add_connection_to_worker(port).await
110 }
111
112 #[wasm_bindgen(js_name = isRunning)]
114 pub async fn is_running(&self) -> Result<bool> {
115 let command = NodeCommand::IsRunning;
116 let response = self.worker.exec(command).await?;
117 let running = response.into_is_running().check_variant()?;
118
119 Ok(running)
120 }
121
122 pub async fn start(&self, config: &WasmNodeConfig) -> Result<()> {
124 let command = NodeCommand::StartNode(config.clone());
125 let response = self.worker.exec(command).await?;
126 response.into_node_started().check_variant()??;
127
128 Ok(())
129 }
130
131 pub async fn stop(&self) -> Result<()> {
133 let command = NodeCommand::StopNode;
134 let response = self.worker.exec(command).await?;
135 response.into_node_stopped().check_variant()?;
136
137 Ok(())
138 }
139
140 #[wasm_bindgen(js_name = localPeerId)]
142 pub async fn local_peer_id(&self) -> Result<String> {
143 let command = NodeCommand::GetLocalPeerId;
144 let response = self.worker.exec(command).await?;
145 let peer_id = response.into_local_peer_id().check_variant()?;
146
147 Ok(peer_id)
148 }
149
150 #[wasm_bindgen(js_name = peerTrackerInfo)]
152 pub async fn peer_tracker_info(&self) -> Result<PeerTrackerInfoSnapshot> {
153 let command = NodeCommand::GetPeerTrackerInfo;
154 let response = self.worker.exec(command).await?;
155 let peer_info = response.into_peer_tracker_info().check_variant()?;
156
157 Ok(peer_info.into())
158 }
159
160 #[wasm_bindgen(js_name = waitConnected)]
162 pub async fn wait_connected(&self) -> Result<()> {
163 let command = NodeCommand::WaitConnected { trusted: false };
164 let response = self.worker.exec(command).await?;
165 let _ = response.into_connected().check_variant()?;
166
167 Ok(())
168 }
169
170 #[wasm_bindgen(js_name = waitConnectedTrusted)]
172 pub async fn wait_connected_trusted(&self) -> Result<()> {
173 let command = NodeCommand::WaitConnected { trusted: true };
174 let response = self.worker.exec(command).await?;
175 response.into_connected().check_variant()?
176 }
177
178 #[wasm_bindgen(js_name = networkInfo)]
180 pub async fn network_info(&self) -> Result<NetworkInfoSnapshot> {
181 let command = NodeCommand::GetNetworkInfo;
182 let response = self.worker.exec(command).await?;
183
184 response.into_network_info().check_variant()?
185 }
186
187 pub async fn listeners(&self) -> Result<Array> {
189 let command = NodeCommand::GetListeners;
190 let response = self.worker.exec(command).await?;
191 let listeners = response.into_listeners().check_variant()?;
192 let result = listeners?.iter().map(js_value_from_display).collect();
193
194 Ok(result)
195 }
196
197 #[wasm_bindgen(js_name = connectedPeers)]
199 pub async fn connected_peers(&self) -> Result<Array> {
200 let command = NodeCommand::GetConnectedPeers;
201 let response = self.worker.exec(command).await?;
202 let peers = response.into_connected_peers().check_variant()?;
203 let result = peers?.iter().map(js_value_from_display).collect();
204
205 Ok(result)
206 }
207
208 #[wasm_bindgen(js_name = setPeerTrust)]
210 pub async fn set_peer_trust(&self, peer_id: &str, is_trusted: bool) -> Result<()> {
211 let command = NodeCommand::SetPeerTrust {
212 peer_id: peer_id.parse()?,
213 is_trusted,
214 };
215 let response = self.worker.exec(command).await?;
216 response.into_set_peer_trust().check_variant()?
217 }
218
219 #[wasm_bindgen(js_name = requestHeadHeader)]
221 pub async fn request_head_header(&self) -> Result<ExtendedHeader> {
222 let command = NodeCommand::RequestHeader(SingleHeaderQuery::Head);
223 let response = self.worker.exec(command).await?;
224 response.into_header().check_variant()?
225 }
226
227 #[wasm_bindgen(js_name = requestHeaderByHash)]
229 pub async fn request_header_by_hash(&self, hash: &str) -> Result<ExtendedHeader> {
230 let command = NodeCommand::RequestHeader(SingleHeaderQuery::ByHash(hash.parse()?));
231 let response = self.worker.exec(command).await?;
232 response.into_header().check_variant()?
233 }
234
235 #[wasm_bindgen(js_name = requestHeaderByHeight)]
237 pub async fn request_header_by_height(&self, height: u64) -> Result<ExtendedHeader> {
238 let command = NodeCommand::RequestHeader(SingleHeaderQuery::ByHeight(height));
239 let response = self.worker.exec(command).await?;
240 response.into_header().check_variant()?
241 }
242
243 #[wasm_bindgen(js_name = requestVerifiedHeaders)]
247 pub async fn request_verified_headers(
248 &self,
249 from: &ExtendedHeader,
250 amount: u64,
251 ) -> Result<Vec<ExtendedHeader>> {
252 let command = NodeCommand::GetVerifiedHeaders {
253 from: from.clone(),
254 amount,
255 };
256 let response = self.worker.exec(command).await?;
257 response.into_headers().check_variant()?
258 }
259
260 #[wasm_bindgen(js_name = requestAllBlobs)]
263 pub async fn request_all_blobs(
264 &self,
265 namespace: &Namespace,
266 block_height: u64,
267 timeout_secs: Option<f64>,
268 ) -> Result<Vec<Blob>> {
269 let command = NodeCommand::RequestAllBlobs {
270 namespace: *namespace,
271 block_height,
272 timeout_secs,
273 };
274 let response = self.worker.exec(command).await?;
275 response.into_blobs().check_variant()?
276 }
277
278 #[wasm_bindgen(js_name = syncerInfo)]
280 pub async fn syncer_info(&self) -> Result<SyncingInfoSnapshot> {
281 let command = NodeCommand::GetSyncerInfo;
282 let response = self.worker.exec(command).await?;
283 let syncer_info = response.into_syncer_info().check_variant()?;
284
285 Ok(syncer_info?.into())
286 }
287
288 #[wasm_bindgen(js_name = getNetworkHeadHeader)]
290 pub async fn get_network_head_header(&self) -> Result<Option<ExtendedHeader>> {
291 let command = NodeCommand::LastSeenNetworkHead;
292 let response = self.worker.exec(command).await?;
293 response.into_last_seen_network_head().check_variant()?
294 }
295
296 #[wasm_bindgen(js_name = getLocalHeadHeader)]
298 pub async fn get_local_head_header(&self) -> Result<ExtendedHeader> {
299 let command = NodeCommand::GetHeader(SingleHeaderQuery::Head);
300 let response = self.worker.exec(command).await?;
301 response.into_header().check_variant()?
302 }
303
304 #[wasm_bindgen(js_name = getHeaderByHash)]
306 pub async fn get_header_by_hash(&self, hash: &str) -> Result<ExtendedHeader> {
307 let command = NodeCommand::GetHeader(SingleHeaderQuery::ByHash(hash.parse()?));
308 let response = self.worker.exec(command).await?;
309 response.into_header().check_variant()?
310 }
311
312 #[wasm_bindgen(js_name = getHeaderByHeight)]
314 pub async fn get_header_by_height(&self, height: u64) -> Result<ExtendedHeader> {
315 let command = NodeCommand::GetHeader(SingleHeaderQuery::ByHeight(height));
316 let response = self.worker.exec(command).await?;
317 response.into_header().check_variant()?
318 }
319
320 #[wasm_bindgen(js_name = getHeaders)]
330 pub async fn get_headers(
331 &self,
332 start_height: Option<u64>,
333 end_height: Option<u64>,
334 ) -> Result<Vec<ExtendedHeader>> {
335 let command = NodeCommand::GetHeadersRange {
336 start_height,
337 end_height,
338 };
339 let response = self.worker.exec(command).await?;
340 response.into_headers().check_variant()?
341 }
342
343 #[wasm_bindgen(js_name = getSamplingMetadata)]
345 pub async fn get_sampling_metadata(&self, height: u64) -> Result<Option<SamplingMetadata>> {
346 let command = NodeCommand::GetSamplingMetadata { height };
347 let response = self.worker.exec(command).await?;
348 response.into_sampling_metadata().check_variant()?
349 }
350
351 #[wasm_bindgen(js_name = eventsChannel)]
353 pub async fn events_channel(&self) -> Result<BroadcastChannel> {
354 let command = NodeCommand::GetEventsChannelName;
355 let response = self.worker.exec(command).await?;
356 let name = response.into_events_channel_name().check_variant()?;
357
358 Ok(BroadcastChannel::new(&name).unwrap())
359 }
360}
361
362#[wasm_bindgen(js_class = NodeConfig)]
363impl WasmNodeConfig {
364 pub fn default(network: Network) -> WasmNodeConfig {
366 let bootnodes = network::Network::from(network)
367 .canonical_bootnodes()
368 .map(|addr| addr.to_string())
369 .collect::<Vec<_>>();
370
371 WasmNodeConfig {
372 network,
373 bootnodes,
374 identity_key: None,
375 use_persistent_memory: true,
376 custom_pruning_window_secs: None,
377 }
378 }
379
380 pub(crate) async fn into_node_builder(self) -> Result<NodeBuilder<WasmBlockstore, WasmStore>> {
381 let network = network::Network::from(self.network);
382 let network_id = network.id();
383
384 let mut builder = if self.use_persistent_memory {
385 let store_name = format!("lumina-{network_id}");
386 let blockstore_name = format!("lumina-{network_id}-blockstore");
387
388 let store = IndexedDbStore::new(&store_name)
389 .await
390 .context("Failed to open the store")?;
391
392 let blockstore = IndexedDbBlockstore::new(&blockstore_name)
393 .await
394 .context("Failed to open the blockstore")?;
395
396 NodeBuilder::new()
397 .store(EitherStore::Right(store))
398 .blockstore(EitherBlockstore::Right(blockstore))
399 } else {
400 NodeBuilder::new()
401 .store(EitherStore::Left(InMemoryStore::new()))
402 .blockstore(EitherBlockstore::Left(InMemoryBlockstore::new()))
403 .pruning_window(DEFAULT_PRUNING_WINDOW_IN_MEMORY)
405 };
406
407 if let Some(key_bytes) = self.identity_key {
408 let keypair = Keypair::ed25519_from_bytes(key_bytes).context("could not decode key")?;
409 builder = builder.keypair(keypair);
410 }
411
412 let bootnodes = self
413 .bootnodes
414 .into_iter()
415 .map(|addr| {
416 addr.parse()
417 .with_context(|| format!("invalid multiaddr: {addr}"))
418 })
419 .collect::<Result<Vec<Multiaddr>, _>>()?;
420
421 builder = builder
422 .network(network)
423 .sync_batch_size(128)
424 .bootnodes(bootnodes);
425
426 if let Some(secs) = self.custom_pruning_window_secs {
427 let dur = Duration::from_secs(secs.into());
428 builder = builder.pruning_window(dur);
429 }
430
431 Ok(builder)
432 }
433}