phos-data-network 0.1.3

DATA Network light-client integration for Phos
Documentation
#[cfg(not(target_arch = "wasm32"))]
use std::time::Duration;

use async_trait::async_trait;
use cometbft_rpc::{
    self as rpc, client::Client, request::RequestMessage, response::Response, SimpleRequest,
};
use reqwest::header;

use phos_light_client::{
    components::io::{AtHeight, Io, IoError},
    verifier::types::{Height, LightBlock, PeerId, SignedHeader, ValidatorAddress, ValidatorSet},
};

#[derive(Clone, Debug)]
pub struct HttpClient {
    inner: reqwest::Client,
    url: reqwest::Url,
}

impl HttpClient {
    pub fn new(url: reqwest::Url) -> Self {
        #[cfg(not(target_arch = "wasm32"))]
        let inner = reqwest::Client::builder()
            .timeout(Duration::from_secs(30))
            .connect_timeout(Duration::from_secs(10))
            .pool_idle_timeout(Duration::from_secs(60))
            .build()
            .unwrap_or_else(|_| reqwest::Client::new());

        #[cfg(target_arch = "wasm32")]
        let inner = reqwest::Client::new();

        Self { inner, url }
    }

    fn build_request<R>(&self, request: R) -> Result<reqwest::Request, rpc::Error>
    where
        R: RequestMessage,
    {
        let request_body = request.into_json();

        self.inner
            .post(self.url.clone())
            .header(header::CONTENT_TYPE, "application/json")
            .body(request_body.into_bytes())
            .build()
            .map_err(rpc::Error::http)
    }
}

#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
impl Client for HttpClient {
    async fn perform<R>(&self, request: R) -> Result<R::Output, rpc::Error>
    where
        R: SimpleRequest,
    {
        let request = self.build_request(request)?;
        let response = self
            .inner
            .execute(request)
            .await
            .map_err(rpc::Error::http)?;
        let response_status = response.status();
        let response_body = response.bytes().await.map_err(rpc::Error::http)?;

        if response_status != reqwest::StatusCode::OK {
            return Err(rpc::Error::http_request_failed(response_status));
        }

        R::Response::from_string(&response_body).map(Into::into)
    }
}

#[derive(Clone, Debug)]
pub struct ProdIo {
    peer_id: PeerId,
    rpc_client: HttpClient,
}

impl ProdIo {
    pub fn new(peer_id: PeerId, rpc_client: HttpClient) -> Self {
        Self {
            peer_id,
            rpc_client,
        }
    }

    pub fn peer_id(&self) -> PeerId {
        self.peer_id
    }

    pub async fn fetch_signed_header(&self, height: AtHeight) -> Result<SignedHeader, IoError> {
        let response = match height {
            AtHeight::Highest => self.rpc_client.latest_commit().await,
            AtHeight::At(height) => self.rpc_client.commit(height).await,
        }
        .map_err(IoError::from_rpc)?;

        Ok(response.signed_header)
    }

    pub async fn fetch_validator_set(
        &self,
        height: AtHeight,
        proposer_address: Option<ValidatorAddress>,
    ) -> Result<ValidatorSet, IoError> {
        let height = match height {
            AtHeight::Highest => return Err(IoError::invalid_height()),
            AtHeight::At(height) => height,
        };

        let response = self
            .rpc_client
            .validators(height, rpc::Paging::All)
            .await
            .map_err(IoError::rpc)?;

        match proposer_address {
            Some(proposer_address) => {
                ValidatorSet::with_proposer(response.validators, proposer_address)
                    .map_err(IoError::invalid_validator_set)
            }
            None => Ok(ValidatorSet::without_proposer(response.validators)),
        }
    }

    pub async fn fetch_block(&self, height: Height) -> Result<cometbft::block::Block, IoError> {
        self.rpc_client
            .block(height)
            .await
            .map(|response| response.block)
            .map_err(IoError::from_rpc)
    }
}

#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
impl Io for ProdIo {
    async fn fetch_light_block(&self, height: AtHeight) -> Result<LightBlock, IoError> {
        let signed_header = self.fetch_signed_header(height).await?;
        let height = signed_header.header.height;
        let proposer_address = signed_header.header.proposer_address;

        let validator_set = self
            .fetch_validator_set(height.into(), Some(proposer_address))
            .await?;
        let next_validator_set = self
            .fetch_validator_set(height.increment().into(), None)
            .await?;

        Ok(LightBlock::new(
            signed_header,
            validator_set,
            next_validator_set,
            self.peer_id,
        ))
    }
}