mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-03-26 12:13:08 +00:00
175 lines
5.4 KiB
Rust
175 lines
5.4 KiB
Rust
|
|
//A copy of http-client from logos-blockchain
|
||
|
|
//ToDo: replace with dependency
|
||
|
|
|
||
|
|
use std::{fmt::Debug, sync::Arc};
|
||
|
|
use reqwest::{Client, ClientBuilder, RequestBuilder, StatusCode, Url};
|
||
|
|
use serde::{Serialize, de::DeserializeOwned};
|
||
|
|
use futures::{Stream, StreamExt as _};
|
||
|
|
|
||
|
|
pub mod paths;
|
||
|
|
pub mod structs;
|
||
|
|
|
||
|
|
use nomos_core::{mantle::SignedMantleTx};
|
||
|
|
|
||
|
|
use crate::{paths::{CRYPTARCHIA_INFO, CRYPTARCHIA_LIB_STREAM, STORAGE_BLOCK}, structs::{Block, BlockInfo, info::CryptarchiaInfo}};
|
||
|
|
|
||
|
|
#[derive(thiserror::Error, Debug)]
|
||
|
|
pub enum Error {
|
||
|
|
#[error("Internal server error: {0}")]
|
||
|
|
Server(String),
|
||
|
|
#[error("Failed to execute request: {0}")]
|
||
|
|
Client(String),
|
||
|
|
#[error(transparent)]
|
||
|
|
Request(#[from] reqwest::Error),
|
||
|
|
#[error(transparent)]
|
||
|
|
Url(#[from] url::ParseError),
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Clone)]
|
||
|
|
pub struct BasicAuthCredentials {
|
||
|
|
username: String,
|
||
|
|
password: Option<String>,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl BasicAuthCredentials {
|
||
|
|
#[must_use]
|
||
|
|
pub const fn new(username: String, password: Option<String>) -> Self {
|
||
|
|
Self { username, password }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[derive(Clone)]
|
||
|
|
pub struct CommonHttpClient {
|
||
|
|
client: Arc<Client>,
|
||
|
|
basic_auth: Option<BasicAuthCredentials>,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl CommonHttpClient {
|
||
|
|
#[must_use]
|
||
|
|
pub fn new(basic_auth: Option<BasicAuthCredentials>) -> Self {
|
||
|
|
let client = ClientBuilder::new()
|
||
|
|
.build()
|
||
|
|
.expect("Client from default settings should be able to build");
|
||
|
|
Self {
|
||
|
|
client: Arc::new(client),
|
||
|
|
basic_auth,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[must_use]
|
||
|
|
pub fn new_with_client(client: Client, basic_auth: Option<BasicAuthCredentials>) -> Self {
|
||
|
|
Self {
|
||
|
|
client: Arc::new(client),
|
||
|
|
basic_auth,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
pub async fn post<Req, Res>(&self, request_url: Url, request_body: &Req) -> Result<Res, Error>
|
||
|
|
where
|
||
|
|
Req: Serialize + ?Sized + Send + Sync,
|
||
|
|
Res: DeserializeOwned + Send + Sync,
|
||
|
|
{
|
||
|
|
let request = self.client.post(request_url).json(request_body);
|
||
|
|
self.execute_request::<Res>(request).await
|
||
|
|
}
|
||
|
|
|
||
|
|
pub async fn get<Req, Res>(
|
||
|
|
&self,
|
||
|
|
request_url: Url,
|
||
|
|
request_body: Option<&Req>,
|
||
|
|
) -> Result<Res, Error>
|
||
|
|
where
|
||
|
|
Req: Serialize + ?Sized + Send + Sync,
|
||
|
|
Res: DeserializeOwned + Send + Sync,
|
||
|
|
{
|
||
|
|
let mut request = self.client.get(request_url);
|
||
|
|
if let Some(request_body) = request_body {
|
||
|
|
request = request.json(request_body);
|
||
|
|
}
|
||
|
|
self.execute_request::<Res>(request).await
|
||
|
|
}
|
||
|
|
|
||
|
|
async fn execute_request<Res: DeserializeOwned>(
|
||
|
|
&self,
|
||
|
|
mut request: RequestBuilder,
|
||
|
|
) -> Result<Res, Error> {
|
||
|
|
if let Some(basic_auth) = &self.basic_auth {
|
||
|
|
request = request.basic_auth(&basic_auth.username, basic_auth.password.as_deref());
|
||
|
|
}
|
||
|
|
|
||
|
|
let response = request.send().await.map_err(Error::Request)?;
|
||
|
|
let status = response.status();
|
||
|
|
let body = response.text().await.map_err(Error::Request)?;
|
||
|
|
|
||
|
|
match status {
|
||
|
|
StatusCode::OK => serde_json::from_str(&body)
|
||
|
|
.map_err(|e| Error::Server(format!("Failed to parse response: {e}"))),
|
||
|
|
StatusCode::INTERNAL_SERVER_ERROR => Err(Error::Server(body)),
|
||
|
|
_ => Err(Error::Server(format!(
|
||
|
|
"Unexpected response [{status}]: {body}",
|
||
|
|
))),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
pub async fn get_lib_stream(
|
||
|
|
&self,
|
||
|
|
base_url: Url,
|
||
|
|
) -> Result<impl Stream<Item = BlockInfo>, Error> {
|
||
|
|
let request_url = base_url
|
||
|
|
.join(CRYPTARCHIA_LIB_STREAM.trim_start_matches('/'))
|
||
|
|
.map_err(Error::Url)?;
|
||
|
|
let mut request = self.client.get(request_url);
|
||
|
|
|
||
|
|
if let Some(basic_auth) = &self.basic_auth {
|
||
|
|
request = request.basic_auth(&basic_auth.username, basic_auth.password.as_deref());
|
||
|
|
}
|
||
|
|
|
||
|
|
let response = request.send().await.map_err(Error::Request)?;
|
||
|
|
let status = response.status();
|
||
|
|
|
||
|
|
let lib_stream = response.bytes_stream().filter_map(async |item| {
|
||
|
|
let bytes = item.ok()?;
|
||
|
|
serde_json::from_slice::<BlockInfo>(&bytes).ok()
|
||
|
|
});
|
||
|
|
match status {
|
||
|
|
StatusCode::OK => Ok(lib_stream),
|
||
|
|
StatusCode::INTERNAL_SERVER_ERROR => Err(Error::Server("Error".to_owned())),
|
||
|
|
_ => Err(Error::Server(format!("Unexpected response [{status}]",))),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
pub async fn get_block_by_id<HeaderId>(
|
||
|
|
&self,
|
||
|
|
base_url: Url,
|
||
|
|
header_id: HeaderId,
|
||
|
|
) -> Result<Option<Block<SignedMantleTx>>, Error>
|
||
|
|
where
|
||
|
|
HeaderId: Serialize + Send + Sync,
|
||
|
|
{
|
||
|
|
let request_url = base_url
|
||
|
|
.join(STORAGE_BLOCK.trim_start_matches('/'))
|
||
|
|
.map_err(Error::Url)?;
|
||
|
|
self.post(request_url, &header_id).await
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Get consensus info (tip, height, etc.)
|
||
|
|
pub async fn consensus_info(&self, base_url: Url) -> Result<CryptarchiaInfo, Error> {
|
||
|
|
let request_url = base_url
|
||
|
|
.join(CRYPTARCHIA_INFO.trim_start_matches('/'))
|
||
|
|
.map_err(Error::Url)?;
|
||
|
|
self.get::<(), CryptarchiaInfo>(request_url, None).await
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Get a block by its header ID
|
||
|
|
pub async fn get_block(
|
||
|
|
&self,
|
||
|
|
base_url: Url,
|
||
|
|
header_id: HeaderId,
|
||
|
|
) -> Result<Option<Block<SignedMantleTx>>, Error> {
|
||
|
|
let request_url = base_url
|
||
|
|
.join(STORAGE_BLOCK.trim_start_matches('/'))
|
||
|
|
.map_err(Error::Url)?;
|
||
|
|
self.post(request_url, &header_id).await
|
||
|
|
}
|
||
|
|
}
|