use std::error::Error as StdError;
use std::fmt::{self, Debug};
use std::sync::Arc;
use blockstore::cond_send::CondSend;
use celestia_grpc::{GrpcClient, GrpcClientBuilder};
use celestia_rpc::{Client as RpcClient, HeaderClient};
use http::Request;
use tonic::body::Body as TonicBody;
use tonic::codegen::{Bytes, Service};
use tonic::metadata::MetadataMap;
use crate::blob::BlobApi;
use crate::blobstream::BlobstreamApi;
use crate::fraud::FraudApi;
use crate::header::HeaderApi;
use crate::share::ShareApi;
use crate::state::StateApi;
use crate::tx::{DocSigner, Keypair, VerifyingKey};
use crate::types::state::AccAddress;
use crate::types::ExtendedHeader;
use crate::{Error, Result};
pub struct Client {
inner: Arc<ClientInner>,
state: StateApi,
blob: BlobApi,
header: HeaderApi,
share: ShareApi,
fraud: FraudApi,
blobstream: BlobstreamApi,
}
pub(crate) struct ClientInner {
pub(crate) rpc: RpcClient,
grpc: Option<GrpcClient>,
pubkey: Option<VerifyingKey>,
chain_id: tendermint::chain::Id,
}
#[derive(Debug, Default)]
pub struct ClientBuilder {
rpc_url: Option<String>,
rpc_auth_token: Option<String>,
grpc_builder: Option<GrpcClientBuilder>,
}
impl ClientInner {
pub(crate) fn grpc(&self) -> Result<&GrpcClient> {
self.grpc.as_ref().ok_or(Error::GrpcEndpointNotSet)
}
pub(crate) fn pubkey(&self) -> Result<&VerifyingKey> {
self.pubkey.as_ref().ok_or(Error::NoAssociatedAddress)
}
pub(crate) fn address(&self) -> Result<AccAddress> {
let pubkey = self.pubkey()?.to_owned();
Ok(AccAddress::new(pubkey.into()))
}
pub(crate) async fn get_header_validated(&self, height: u64) -> Result<ExtendedHeader> {
let header = self.rpc.header_get_by_height(height).await?;
header.validate()?;
Ok(header)
}
}
impl Client {
pub fn builder() -> ClientBuilder {
ClientBuilder::new()
}
pub fn chain_id(&self) -> &tendermint::chain::Id {
&self.inner.chain_id
}
pub fn pubkey(&self) -> Result<VerifyingKey> {
self.inner.pubkey().cloned()
}
pub fn address(&self) -> Result<AccAddress> {
self.inner.address()
}
pub fn state(&self) -> &StateApi {
&self.state
}
pub fn blob(&self) -> &BlobApi {
&self.blob
}
pub fn blobstream(&self) -> &BlobstreamApi {
&self.blobstream
}
pub fn header(&self) -> &HeaderApi {
&self.header
}
pub fn share(&self) -> &ShareApi {
&self.share
}
pub fn fraud(&self) -> &FraudApi {
&self.fraud
}
}
impl ClientBuilder {
pub fn new() -> ClientBuilder {
ClientBuilder::default()
}
pub fn signer<S>(mut self, pubkey: VerifyingKey, signer: S) -> ClientBuilder
where
S: DocSigner + Sync + Send + 'static,
{
let grpc_builder = self.grpc_builder.unwrap_or_default();
self.grpc_builder = Some(grpc_builder.pubkey_and_signer(pubkey, signer));
self
}
pub fn keypair<S>(mut self, keypair: S) -> ClientBuilder
where
S: DocSigner + Keypair<VerifyingKey = VerifyingKey> + Sync + Send + 'static,
{
let grpc_builder = self.grpc_builder.unwrap_or_default();
self.grpc_builder = Some(grpc_builder.signer_keypair(keypair));
self
}
pub fn private_key(mut self, bytes: &[u8]) -> ClientBuilder {
let grpc_builder = self.grpc_builder.unwrap_or_default();
self.grpc_builder = Some(grpc_builder.private_key(bytes));
self
}
pub fn private_key_hex(mut self, s: &str) -> ClientBuilder {
let grpc_builder = self.grpc_builder.unwrap_or_default();
self.grpc_builder = Some(grpc_builder.private_key_hex(s));
self
}
pub fn rpc_url(mut self, url: &str) -> ClientBuilder {
self.rpc_url = Some(url.to_owned());
self
}
pub fn rpc_auth_token(mut self, auth_token: &str) -> ClientBuilder {
self.rpc_auth_token = Some(auth_token.to_owned());
self
}
pub fn grpc_url(mut self, url: &str) -> ClientBuilder {
let grpc_builder = self.grpc_builder.unwrap_or_default();
self.grpc_builder = Some(grpc_builder.url(url));
self
}
pub fn grpc_transport<B, T>(mut self, transport: T) -> Self
where
B: http_body::Body<Data = Bytes> + Send + Unpin + 'static,
<B as http_body::Body>::Error: StdError + Send + Sync,
T: Service<Request<TonicBody>, Response = http::Response<B>>
+ Send
+ Sync
+ Clone
+ 'static,
<T as Service<Request<TonicBody>>>::Error: StdError + Send + Sync + 'static,
<T as Service<Request<TonicBody>>>::Future: CondSend + 'static,
{
let grpc_builder = self.grpc_builder.unwrap_or_default();
self.grpc_builder = Some(grpc_builder.transport(transport));
self
}
pub fn grpc_metadata(mut self, key: &str, value: &str) -> Self {
let grpc_builder = self.grpc_builder.unwrap_or_default();
self.grpc_builder = Some(grpc_builder.metadata(key, value));
self
}
pub fn grpc_metadata_bin(mut self, key: &str, value: &[u8]) -> Self {
let grpc_builder = self.grpc_builder.unwrap_or_default();
self.grpc_builder = Some(grpc_builder.metadata_bin(key, value));
self
}
pub fn grpc_metadata_map(mut self, metadata: MetadataMap) -> Self {
let grpc_builder = self.grpc_builder.unwrap_or_default();
self.grpc_builder = Some(grpc_builder.metadata_map(metadata));
self
}
pub async fn build(self) -> Result<Client> {
let rpc_url = self.rpc_url.as_ref().ok_or(Error::RpcEndpointNotSet)?;
let rpc_auth_token = self.rpc_auth_token.as_deref();
#[cfg(target_arch = "wasm32")]
if rpc_auth_token.is_some() {
return Err(Error::AuthTokenNotSupported);
}
let (grpc, pubkey) = if let Some(grpc_builder) = self.grpc_builder {
let client = grpc_builder.build()?;
let pubkey = client.get_account_pubkey();
(Some(client), pubkey)
} else {
(None, None)
};
#[cfg(not(target_arch = "wasm32"))]
let rpc = RpcClient::new(rpc_url, rpc_auth_token).await?;
#[cfg(target_arch = "wasm32")]
let rpc = RpcClient::new(rpc_url).await?;
let head = rpc.header_network_head().await?;
head.validate()?;
if let Some(grpc) = &grpc {
if &grpc.chain_id().await? != head.chain_id() {
return Err(Error::ChainIdMissmatch);
}
}
let inner = Arc::new(ClientInner {
rpc,
grpc,
pubkey,
chain_id: head.chain_id().to_owned(),
});
Ok(Client {
inner: inner.clone(),
blob: BlobApi::new(inner.clone()),
header: HeaderApi::new(inner.clone()),
share: ShareApi::new(inner.clone()),
fraud: FraudApi::new(inner.clone()),
blobstream: BlobstreamApi::new(inner.clone()),
state: StateApi::new(inner.clone()),
})
}
}
impl Debug for Client {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Client { .. }")
}
}
#[cfg(test)]
mod tests {
use super::*;
use lumina_utils::test_utils::async_test;
use crate::test_utils::{TEST_PRIV_KEY, TEST_RPC_URL};
#[async_test]
async fn builder() {
let e = Client::builder()
.rpc_url(TEST_RPC_URL)
.private_key_hex(TEST_PRIV_KEY)
.build()
.await
.unwrap_err();
assert!(matches!(e, Error::GrpcEndpointNotSet))
}
}