celestia_client/
client.rs1use std::error::Error as StdError;
2use std::fmt::{self, Debug};
3use std::sync::Arc;
4use std::time::Duration;
5
6use blockstore::cond_send::CondSend;
7pub use celestia_grpc::Endpoint;
8use celestia_grpc::{GrpcClient, GrpcClientBuilder};
9use celestia_rpc::{Client as RpcClient, HeaderClient};
10use http::Request;
11use tonic::body::Body as TonicBody;
12use tonic::codegen::{Bytes, Service};
13
14use crate::blob::BlobApi;
15use crate::blobstream::BlobstreamApi;
16use crate::fraud::FraudApi;
17use crate::header::HeaderApi;
18use crate::share::ShareApi;
19use crate::state::StateApi;
20use crate::tx::{DocSigner, Keypair, VerifyingKey};
21use crate::types::ExtendedHeader;
22use crate::types::state::AccAddress;
23use crate::{Error, Result};
24
25pub struct Client {
75 inner: Arc<ClientInner>,
76 state: StateApi,
77 blob: BlobApi,
78 header: HeaderApi,
79 share: ShareApi,
80 fraud: FraudApi,
81 blobstream: BlobstreamApi,
82}
83
84pub(crate) struct ClientInner {
85 pub(crate) rpc: RpcClient,
86 grpc: Option<GrpcClient>,
87 pubkey: Option<VerifyingKey>,
88 chain_id: tendermint::chain::Id,
89}
90
91#[derive(Debug, Default)]
93pub struct ClientBuilder {
94 rpc_url: Option<String>,
95 rpc_auth_token: Option<String>,
96 timeout: Option<Duration>,
97 grpc_builder: Option<GrpcClientBuilder>,
98}
99
100impl ClientInner {
101 pub(crate) fn grpc(&self) -> Result<&GrpcClient> {
102 self.grpc.as_ref().ok_or(Error::GrpcEndpointNotSet)
103 }
104
105 pub(crate) fn pubkey(&self) -> Result<&VerifyingKey> {
106 self.pubkey.as_ref().ok_or(Error::NoAssociatedAddress)
107 }
108
109 pub(crate) fn address(&self) -> Result<AccAddress> {
110 let pubkey = self.pubkey()?.to_owned();
111 Ok(AccAddress::new(pubkey.into()))
112 }
113
114 pub(crate) async fn get_header_validated(&self, height: u64) -> Result<ExtendedHeader> {
115 let header = self.rpc.header_get_by_height(height).await?;
116 header.validate()?;
117 Ok(header)
118 }
119}
120
121impl Client {
122 pub fn builder() -> ClientBuilder {
124 ClientBuilder::new()
125 }
126
127 pub fn chain_id(&self) -> &tendermint::chain::Id {
129 &self.inner.chain_id
130 }
131
132 pub fn pubkey(&self) -> Result<VerifyingKey> {
134 self.inner.pubkey().cloned()
135 }
136
137 pub fn address(&self) -> Result<AccAddress> {
139 self.inner.address()
140 }
141
142 pub fn state(&self) -> &StateApi {
144 &self.state
145 }
146
147 pub fn blob(&self) -> &BlobApi {
149 &self.blob
150 }
151
152 pub fn blobstream(&self) -> &BlobstreamApi {
154 &self.blobstream
155 }
156
157 pub fn header(&self) -> &HeaderApi {
159 &self.header
160 }
161
162 pub fn share(&self) -> &ShareApi {
164 &self.share
165 }
166
167 pub fn fraud(&self) -> &FraudApi {
169 &self.fraud
170 }
171}
172
173impl ClientBuilder {
174 pub fn new() -> ClientBuilder {
176 ClientBuilder::default()
177 }
178
179 pub fn signer<S>(mut self, pubkey: VerifyingKey, signer: S) -> ClientBuilder
181 where
182 S: DocSigner + Sync + Send + 'static,
183 {
184 let grpc_builder = self.grpc_builder.unwrap_or_default();
185 self.grpc_builder = Some(grpc_builder.pubkey_and_signer(pubkey, signer));
186 self
187 }
188
189 pub fn keypair<S>(mut self, keypair: S) -> ClientBuilder
191 where
192 S: DocSigner + Keypair<VerifyingKey = VerifyingKey> + Sync + Send + 'static,
193 {
194 let grpc_builder = self.grpc_builder.unwrap_or_default();
195 self.grpc_builder = Some(grpc_builder.signer_keypair(keypair));
196 self
197 }
198
199 pub fn private_key(mut self, bytes: &[u8]) -> ClientBuilder {
201 let grpc_builder = self.grpc_builder.unwrap_or_default();
202 self.grpc_builder = Some(grpc_builder.private_key(bytes));
203 self
204 }
205
206 pub fn private_key_hex(mut self, s: &str) -> ClientBuilder {
208 let grpc_builder = self.grpc_builder.unwrap_or_default();
209 self.grpc_builder = Some(grpc_builder.private_key_hex(s));
210 self
211 }
212
213 pub fn rpc_url(mut self, url: &str) -> ClientBuilder {
215 self.rpc_url = Some(url.to_owned());
216 self
217 }
218
219 pub fn rpc_auth_token(mut self, auth_token: &str) -> ClientBuilder {
221 self.rpc_auth_token = Some(auth_token.to_owned());
222 self
223 }
224
225 pub fn timeout(mut self, timeout: Duration) -> ClientBuilder {
227 self.timeout = Some(timeout);
228 self
229 }
230
231 pub fn grpc_url(self, url: impl Into<Endpoint>) -> ClientBuilder {
241 self.grpc_endpoint(url)
242 }
243
244 pub fn grpc_endpoint(mut self, endpoint: impl Into<Endpoint>) -> ClientBuilder {
246 let grpc_builder = self.grpc_builder.unwrap_or_default();
247 self.grpc_builder = Some(grpc_builder.endpoint(endpoint));
248 self
249 }
250
251 pub fn grpc_endpoints<I, E>(mut self, endpoints: I) -> ClientBuilder
262 where
263 I: IntoIterator<Item = E>,
264 E: Into<Endpoint>,
265 {
266 let grpc_builder = self.grpc_builder.unwrap_or_default();
267 self.grpc_builder = Some(grpc_builder.endpoints(endpoints));
268 self
269 }
270
271 pub fn grpc_urls<I, E>(self, urls: I) -> ClientBuilder
273 where
274 I: IntoIterator<Item = E>,
275 E: Into<Endpoint>,
276 {
277 self.grpc_endpoints(urls)
278 }
279
280 pub fn grpc_transport<B, T>(mut self, transport: T) -> Self
282 where
283 B: http_body::Body<Data = Bytes> + Send + Unpin + 'static,
284 <B as http_body::Body>::Error: StdError + Send + Sync,
285 T: Service<Request<TonicBody>, Response = http::Response<B>>
286 + Send
287 + Sync
288 + Clone
289 + 'static,
290 <T as Service<Request<TonicBody>>>::Error: StdError + Send + Sync + 'static,
291 <T as Service<Request<TonicBody>>>::Future: CondSend + 'static,
292 {
293 let grpc_builder = self.grpc_builder.unwrap_or_default();
294 self.grpc_builder = Some(grpc_builder.transport(transport));
295 self
296 }
297
298 pub async fn build(self) -> Result<Client> {
300 let rpc_url = self.rpc_url.as_ref().ok_or(Error::RpcEndpointNotSet)?;
301 let rpc_auth_token = self.rpc_auth_token.as_deref();
302
303 let (grpc, pubkey) = if let Some(mut grpc_builder) = self.grpc_builder {
304 if let Some(timeout) = self.timeout {
305 grpc_builder = grpc_builder.timeout(timeout)
306 };
307 let client = grpc_builder.build()?;
308 let pubkey = client.get_account_pubkey();
309 (Some(client), pubkey)
310 } else {
311 (None, None)
312 };
313
314 let rpc = RpcClient::new(rpc_url, rpc_auth_token, self.timeout, self.timeout).await?;
315
316 let head = rpc.header_network_head().await?;
317 head.validate()?;
318
319 if let Some(grpc) = &grpc
320 && &grpc.chain_id().await? != head.chain_id()
321 {
322 return Err(Error::ChainIdMissmatch);
323 }
324
325 let inner = Arc::new(ClientInner {
326 rpc,
327 grpc,
328 pubkey,
329 chain_id: head.chain_id().to_owned(),
330 });
331
332 Ok(Client {
333 inner: inner.clone(),
334 blob: BlobApi::new(inner.clone()),
335 header: HeaderApi::new(inner.clone()),
336 share: ShareApi::new(inner.clone()),
337 fraud: FraudApi::new(inner.clone()),
338 blobstream: BlobstreamApi::new(inner.clone()),
339 state: StateApi::new(inner.clone()),
340 })
341 }
342}
343
344impl Debug for Client {
345 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
346 f.write_str("Client { .. }")
347 }
348}
349
350#[cfg(test)]
351mod tests {
352 use super::*;
353
354 use lumina_utils::test_utils::async_test;
355
356 use crate::test_utils::{TEST_PRIV_KEY, TEST_RPC_URL};
357
358 #[async_test]
359 async fn builder() {
360 let e = Client::builder()
361 .rpc_url(TEST_RPC_URL)
362 .private_key_hex(TEST_PRIV_KEY)
363 .build()
364 .await
365 .unwrap_err();
366 assert!(matches!(e, Error::GrpcEndpointNotSet))
367 }
368}