lumina_node_uniffi/
lib.rs

1//! Native library providing Rust to mobile language bindings for the Lumina node.
2//!
3//! This crate uses Mozillas UniFFI to generate Swift and Kotlin bindings for the Lumina node,
4//! allowing it to be used from iOS and Android applications.
5#![cfg(not(target_arch = "wasm32"))]
6
7use std::str::FromStr;
8
9use blockstore::EitherBlockstore;
10use celestia_types::ExtendedHeader;
11use lumina_node::Node;
12use lumina_node::blockstore::{InMemoryBlockstore, RedbBlockstore};
13use lumina_node::events::EventSubscriber;
14use lumina_node::node::PeerTrackerInfo;
15use lumina_node::store::{EitherStore, InMemoryStore, RedbStore};
16use tendermint::hash::Hash;
17use tokio::sync::{Mutex, RwLock};
18use uniffi::Object;
19
20use crate::error::{LuminaError, Result};
21use crate::types::{NetworkInfo, NodeConfig, NodeEvent, PeerId, SyncingInfo};
22
23mod error;
24mod types;
25
26uniffi::setup_scaffolding!();
27
28lumina_node::uniffi_reexport_scaffolding!();
29celestia_types::uniffi_reexport_scaffolding!();
30celestia_grpc::uniffi_reexport_scaffolding!();
31celestia_proto::uniffi_reexport_scaffolding!();
32
33pub(crate) type Blockstore = EitherBlockstore<InMemoryBlockstore, RedbBlockstore>;
34pub(crate) type Store = EitherStore<InMemoryStore, RedbStore>;
35
36/// The main Lumina node that manages the connection to the Celestia network.
37#[derive(Object)]
38pub struct LuminaNode {
39    node: RwLock<Option<Node<Blockstore, Store>>>,
40    events_subscriber: Mutex<Option<EventSubscriber>>,
41    config: NodeConfig,
42}
43
44#[uniffi::export(async_runtime = "tokio")]
45impl LuminaNode {
46    /// Sets a new connection to the Lumina node for the specified network.
47    #[uniffi::constructor]
48    pub fn new(config: NodeConfig) -> Result<Self> {
49        Ok(Self {
50            node: RwLock::new(None),
51            events_subscriber: Mutex::new(None),
52            config,
53        })
54    }
55
56    /// Starts the node and connects to the network.
57    pub async fn start(&self) -> Result<bool> {
58        let mut node_lock = self.node.write().await;
59        if node_lock.is_some() {
60            return Err(LuminaError::AlreadyRunning);
61        }
62
63        let builder = self.config.clone().into_node_builder().await?;
64        let (new_node, subscriber) = builder.start_subscribed().await?;
65
66        *self.events_subscriber.lock().await = Some(subscriber);
67        *node_lock = Some(new_node);
68
69        Ok(true)
70    }
71
72    /// Stops the running node and closes all network connections.
73    pub async fn stop(&self) -> Result<()> {
74        let mut node = self.node.write().await;
75        match node.take() {
76            Some(node) => {
77                node.stop().await;
78                Ok(())
79            }
80            _ => Err(LuminaError::NodeNotRunning),
81        }
82    }
83
84    /// Checks if the node is currently running.
85    pub async fn is_running(&self) -> bool {
86        self.node.read().await.is_some()
87    }
88
89    /// Gets the local peer ID as a string.
90    pub async fn local_peer_id(&self) -> Result<String> {
91        let node = self.node.read().await;
92        let node = node.as_ref().ok_or(LuminaError::NodeNotRunning)?;
93        Ok(node.local_peer_id().to_base58())
94    }
95
96    /// Gets information about connected peers.
97    pub async fn peer_tracker_info(&self) -> Result<PeerTrackerInfo> {
98        let node = self.node.read().await;
99        let node = node.as_ref().ok_or(LuminaError::NodeNotRunning)?;
100        Ok(node.peer_tracker_info())
101    }
102
103    /// Waits until the node is connected to at least one peer.
104    pub async fn wait_connected(&self) -> Result<()> {
105        let node = self.node.read().await;
106        let node = node.as_ref().ok_or(LuminaError::NodeNotRunning)?;
107        Ok(node.wait_connected().await?)
108    }
109
110    /// Waits until the node is connected to at least one trusted peer.
111    pub async fn wait_connected_trusted(&self) -> Result<()> {
112        let node = self.node.read().await;
113        let node = node.as_ref().ok_or(LuminaError::NodeNotRunning)?;
114        Ok(node.wait_connected_trusted().await?)
115    }
116
117    /// Gets current network information.
118    pub async fn network_info(&self) -> Result<NetworkInfo> {
119        let node = self.node.read().await;
120        let node = node.as_ref().ok_or(LuminaError::NodeNotRunning)?;
121        let info = node.network_info().await?;
122        Ok(info.into())
123    }
124
125    /// Gets list of addresses the node is listening to.
126    pub async fn listeners(&self) -> Result<Vec<String>> {
127        let node = self.node.read().await;
128        let node = node.as_ref().ok_or(LuminaError::NodeNotRunning)?;
129        let listeners = node.listeners().await?;
130        Ok(listeners.into_iter().map(|l| l.to_string()).collect())
131    }
132
133    /// Gets list of currently connected peer IDs.
134    pub async fn connected_peers(&self) -> Result<Vec<PeerId>> {
135        let node = self.node.read().await;
136        let node = node.as_ref().ok_or(LuminaError::NodeNotRunning)?;
137        let peers = node.connected_peers().await?;
138        Ok(peers.into_iter().map(PeerId::from).collect())
139    }
140
141    /// Sets whether a peer with give ID is trusted.
142    pub async fn set_peer_trust(&self, peer_id: PeerId, is_trusted: bool) -> Result<()> {
143        let node = self.node.read().await;
144        let node = node.as_ref().ok_or(LuminaError::NodeNotRunning)?;
145        let peer_id = peer_id.to_libp2p().map_err(LuminaError::network)?;
146        Ok(node.set_peer_trust(peer_id, is_trusted).await?)
147    }
148
149    /// Request the head header from the network.
150    ///
151    /// Returns a serialized ExtendedHeader string.
152    pub async fn request_head_header(&self) -> Result<String> {
153        let node = self.node.read().await;
154        let node = node.as_ref().ok_or(LuminaError::NodeNotRunning)?;
155        let header = node.request_head_header().await?;
156        Ok(serde_json::to_string(&header).unwrap()) //if extended header is needed, we need a wrapper
157    }
158
159    /// Request a header for the block with a given hash from the network.
160    pub async fn request_header_by_hash(&self, hash: String) -> Result<String> {
161        let node = self.node.read().await;
162        let node = node.as_ref().ok_or(LuminaError::NodeNotRunning)?;
163        let hash = Hash::from_str(&hash).map_err(|e| LuminaError::invalid_hash(e.to_string()))?;
164        let header = node.request_header_by_hash(&hash).await?;
165        Ok(serde_json::to_string(&header).unwrap())
166    }
167
168    /// Requests a header by its height.
169    pub async fn request_header_by_height(&self, height: u64) -> Result<String> {
170        let node = self.node.read().await;
171        let node = node.as_ref().ok_or(LuminaError::NodeNotRunning)?;
172        let header = node.request_header_by_height(height).await?;
173        Ok(serde_json::to_string(&header).unwrap())
174    }
175
176    /// Request headers in range (from, from + amount] from the network.
177    ///
178    /// The headers will be verified with the from header.
179    /// Returns array of serialized ExtendedHeader strings.
180    pub async fn request_verified_headers(
181        &self,
182        from: String, // serialized header like its done for WASM
183        amount: u64,
184    ) -> Result<Vec<String>> {
185        let node = self.node.read().await;
186        let node = node.as_ref().ok_or(LuminaError::NodeNotRunning)?;
187        let from: ExtendedHeader = serde_json::from_str(&from)
188            .map_err(|e| LuminaError::invalid_header(format!("Invalid header JSON: {e}")))?;
189        let headers = node.request_verified_headers(&from, amount).await?;
190        Ok(headers
191            .into_iter()
192            .map(|h| serde_json::to_string(&h).unwrap())
193            .collect())
194    }
195
196    /// Gets current syncing information.
197    pub async fn syncer_info(&self) -> Result<SyncingInfo> {
198        let node = self.node.read().await;
199        let node = node.as_ref().ok_or(LuminaError::NodeNotRunning)?;
200        let info = node.syncer_info().await?;
201        Ok(info.into())
202    }
203
204    /// Gets the latest header announced in the network.
205    pub async fn get_network_head_header(&self) -> Result<String> {
206        let node = self.node.read().await;
207        let node = node.as_ref().ok_or(LuminaError::NodeNotRunning)?;
208        let header = node.get_network_head_header().await?;
209        header.map_or(
210            Err(LuminaError::network("No network head header available")),
211            |h| Ok(serde_json::to_string(&h).unwrap()),
212        )
213    }
214
215    /// Gets the latest locally synced header.
216    pub async fn get_local_head_header(&self) -> Result<String> {
217        let node = self.node.read().await;
218        let node = node.as_ref().ok_or(LuminaError::NodeNotRunning)?;
219        let header = node.get_local_head_header().await?;
220        Ok(serde_json::to_string(&header).unwrap())
221    }
222
223    /// Get a synced header for the block with a given hash.
224    pub async fn get_header_by_hash(&self, hash: String) -> Result<String> {
225        let node = self.node.read().await;
226        let node = node.as_ref().ok_or(LuminaError::NodeNotRunning)?;
227        let hash = Hash::from_str(&hash).map_err(|e| LuminaError::invalid_hash(e.to_string()))?;
228        let header = node.get_header_by_hash(&hash).await?;
229        Ok(serde_json::to_string(&header).unwrap())
230    }
231
232    /// Get a synced header for the block with a given height.
233    pub async fn get_header_by_height(&self, height: u64) -> Result<String> {
234        let node = self.node.read().await;
235        let node = node.as_ref().ok_or(LuminaError::NodeNotRunning)?;
236        let header = node.get_header_by_height(height).await?;
237        Ok(serde_json::to_string(&header).unwrap())
238    }
239
240    /// Gets headers from the given heights range.
241    ///
242    /// If start of the range is undefined (None), the first returned header will be of height 1.
243    /// If end of the range is undefined (None), the last returned header will be the last header in the
244    /// store.
245    ///
246    /// Returns array of serialized ExtendedHeader strings.
247    pub async fn get_headers(
248        &self,
249        start_height: Option<u64>,
250        end_height: Option<u64>,
251    ) -> Result<Vec<String>> {
252        let node = self.node.read().await;
253        let node = node.as_ref().ok_or(LuminaError::NodeNotRunning)?;
254
255        let headers = match (start_height, end_height) {
256            (None, None) => node.get_headers(..).await,
257            (Some(start), None) => node.get_headers(start..).await,
258            (None, Some(end)) => node.get_headers(..=end).await,
259            (Some(start), Some(end)) => node.get_headers(start..=end).await,
260        }?;
261
262        Ok(headers
263            .into_iter()
264            .map(|h| serde_json::to_string(&h).unwrap())
265            .collect())
266    }
267
268    /// Gets data sampling metadata for a height.
269    ///
270    /// Returns serialized SamplingMetadata string if metadata exists for the height.
271    pub async fn get_sampling_metadata(&self, height: u64) -> Result<Option<String>> {
272        let node = self.node.read().await;
273        let node = node.as_ref().ok_or(LuminaError::NodeNotRunning)?;
274
275        let metadata = node.get_sampling_metadata(height).await?;
276        Ok(metadata.map(|m| serde_json::to_string(&m).unwrap()))
277    }
278
279    /// Returns the next event from the node's event channel.
280    pub async fn next_event(&self) -> Result<NodeEvent> {
281        let mut events_subscriber = self.events_subscriber.lock().await;
282        match events_subscriber.as_mut() {
283            Some(subscriber) => {
284                let event = subscriber
285                    .recv()
286                    .await
287                    .map_err(|_| LuminaError::NodeNotRunning)?;
288                Ok(event.event.into())
289            }
290            None => Err(LuminaError::NodeNotRunning),
291        }
292    }
293}