use std::fmt::{self, Debug};
use std::sync::Arc;
use celestia_grpc::TxClient;
use celestia_rpc::{Client as RpcClient, HeaderClient};
use zeroize::Zeroizing;
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, SigningKey, VerifyingKey};
use crate::types::state::AccAddress;
use crate::types::ExtendedHeader;
use crate::utils::DispatchedDocSigner;
use crate::{Error, Result};
#[cfg(not(target_arch = "wasm32"))]
type Transport = tonic::transport::Channel;
#[cfg(target_arch = "wasm32")]
type Transport = tonic_web_wasm_client::Client;
pub(crate) struct Context {
pub(crate) rpc: RpcClient,
grpc: Option<TxClient<Transport, DispatchedDocSigner>>,
pubkey: Option<VerifyingKey>,
chain_id: tendermint::chain::Id,
}
pub struct Client {
ctx: Arc<Context>,
state: StateApi,
blob: BlobApi,
header: HeaderApi,
share: ShareApi,
fraud: FraudApi,
blobstream: BlobstreamApi,
}
#[derive(Debug, Default)]
pub struct ClientBuilder {
rpc_url: Option<String>,
rpc_auth_token: Option<String>,
grpc_url: Option<String>,
signer: Option<SignerKind>,
}
enum SignerKind {
Signer((VerifyingKey, DispatchedDocSigner)),
PrivKeyBytes(Zeroizing<Vec<u8>>),
PrivKeyHex(Zeroizing<String>),
}
impl Debug for SignerKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
SignerKind::Signer(..) => "SignerKind::Signer(..)",
SignerKind::PrivKeyBytes(..) => "SignerKind::PrivKeyBytes(..)",
SignerKind::PrivKeyHex(..) => "SignerKind::PrivKeyHex(..)",
};
f.write_str(s)
}
}
impl Context {
pub(crate) fn grpc(&self) -> Result<&TxClient<Transport, DispatchedDocSigner>> {
self.grpc.as_ref().ok_or(Error::ReadOnlyMode)
}
pub(crate) fn pubkey(&self) -> Result<&VerifyingKey> {
self.pubkey.as_ref().ok_or(Error::ReadOnlyMode)
}
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.ctx.chain_id
}
pub fn pubkey(&self) -> Result<VerifyingKey> {
self.ctx.pubkey().cloned()
}
pub fn address(&self) -> Result<AccAddress> {
self.ctx.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 signer = DispatchedDocSigner::new(signer);
self.signer = Some(SignerKind::Signer((pubkey, signer)));
self
}
pub fn keypair<S>(self, keypair: S) -> ClientBuilder
where
S: DocSigner + Keypair<VerifyingKey = VerifyingKey> + Sync + Send + 'static,
{
let pubkey = keypair.verifying_key();
self.signer(pubkey, keypair)
}
pub fn private_key(mut self, bytes: &[u8]) -> ClientBuilder {
let bytes = Zeroizing::new(bytes.to_vec());
self.signer = Some(SignerKind::PrivKeyBytes(bytes));
self
}
pub fn private_key_hex(mut self, s: &str) -> ClientBuilder {
let s = Zeroizing::new(s.to_string());
self.signer = Some(SignerKind::PrivKeyHex(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 {
self.grpc_url = Some(url.to_owned());
self
}
pub async fn build(mut 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 signer = match self.signer.take() {
Some(SignerKind::Signer((pubkey, signer))) => Some((pubkey, signer)),
Some(SignerKind::PrivKeyBytes(bytes)) => Some(priv_key_signer(&bytes)?),
Some(SignerKind::PrivKeyHex(s)) => {
let bytes =
Zeroizing::new(hex::decode(s.trim()).map_err(|_| Error::InvalidPrivateKey)?);
Some(priv_key_signer(&bytes)?)
}
None => None,
};
let (pubkey, grpc) = match (&self.grpc_url, signer) {
(Some(url), Some((pubkey, signer))) => {
#[cfg(not(target_arch = "wasm32"))]
let tx_client = TxClient::with_url(url, pubkey, signer).await?;
#[cfg(target_arch = "wasm32")]
let tx_client = TxClient::with_grpcweb_url(url, pubkey, signer).await?;
(Some(pubkey), Some(tx_client))
}
(Some(_url), None) => return Err(Error::SignerNotSet),
(None, Some((_pubkey, _signer))) => return Err(Error::GrpcEndpointNotSet),
(None, None) => (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() != head.chain_id() {
return Err(Error::ChainIdMissmatch);
}
}
let ctx = Arc::new(Context {
rpc,
grpc,
pubkey,
chain_id: head.chain_id().to_owned(),
});
Ok(Client {
ctx: ctx.clone(),
blob: BlobApi::new(ctx.clone()),
header: HeaderApi::new(ctx.clone()),
share: ShareApi::new(ctx.clone()),
fraud: FraudApi::new(ctx.clone()),
blobstream: BlobstreamApi::new(ctx.clone()),
state: StateApi::new(ctx.clone()),
})
}
}
fn priv_key_signer(bytes: &[u8]) -> Result<(VerifyingKey, DispatchedDocSigner)> {
let signing_key = SigningKey::from_slice(bytes).map_err(|_| Error::InvalidPrivateKey)?;
let pubkey = signing_key.verifying_key().to_owned();
let signer = DispatchedDocSigner::new(signing_key);
Ok((pubkey, signer))
}