fix: client as dependency

This commit is contained in:
Pravdyvy 2026-01-08 09:10:00 +02:00
parent 1a033af5f2
commit f817279d33
13 changed files with 1578 additions and 714 deletions

1841
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -19,7 +19,8 @@ members = [
"integration_tests/proc_macro_test_attribute",
"examples/program_deployment",
"examples/program_deployment/methods",
"examples/program_deployment/methods/guest", "indexer", "bedrock_client",
"examples/program_deployment/methods/guest",
"bedrock_client",
]
[workspace.dependencies]
@ -77,9 +78,8 @@ borsh = "1.5.7"
base58 = "0.2.0"
itertools = "0.14.0"
url = "2.5.4"
const-hex = { default-features = false, features = ["alloc"], version = "1" }
serde_with = { default-features = false, version = "3.14.0" }
ed25519-dalek = { default-features = false, version = "2" }
common-http-client = { git = "https://github.com/logos-blockchain/logos-blockchain.git", branch = "marbella-offsite-2025-12" }
rocksdb = { version = "0.24.0", default-features = false, features = [
"snappy",
@ -98,4 +98,4 @@ actix-web = { version = "=4.1.0", default-features = false, features = [
"macros",
] }
clap = { version = "4.5.42", features = ["derive", "env"] }
reqwest = { version = "0.11.16", features = ["json"] }
reqwest = { version = "0.12", features = ["json", "rustls-tls", "stream"] }

View File

@ -12,6 +12,4 @@ thiserror.workspace = true
url.workspace = true
futures.workspace = true
serde_json.workspace = true
const-hex.workspace = true
serde_with.workspace = true
ed25519-dalek = { workspace = true, features = ["rand_core", "serde", "zeroize"] }
common-http-client.workspace = true

View File

@ -1,174 +1,21 @@
//A copy of http-client from logos-blockchain
//ToDo: replace with dependency
use anyhow::Result;
use common_http_client::CommonHttpClient;
pub use common_http_client::BasicAuthCredentials;
pub use common_http_client::Error;
use reqwest::Client;
use std::{fmt::Debug, sync::Arc};
use reqwest::{Client, ClientBuilder, RequestBuilder, StatusCode, Url};
use serde::{Serialize, de::DeserializeOwned};
use futures::{Stream, StreamExt as _};
// Simple wrapper
// maybe extend in the future for our purposes
pub struct BedrockClient(pub CommonHttpClient);
pub mod paths;
pub mod structs;
impl BedrockClient {
pub fn new(auth: Option<BasicAuthCredentials>) -> Result<Self> {
let client = Client::builder()
//Add more fiedls if needed
.timeout(std::time::Duration::from_secs(60))
.build()?;
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 }
Ok(BedrockClient(CommonHttpClient::new_with_client(client, auth)))
}
}
#[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
}
}

View File

@ -1,6 +0,0 @@
pub const CRYPTARCHIA_INFO: &str = "/cryptarchia/info";
pub const CRYPTARCHIA_LIB_STREAM: &str = "/cryptarchia/lib-stream";
pub const STORAGE_BLOCK: &str = "/storage/block";
pub const BLOCKS: &str = "/cryptarchia/blocks";
pub const BLOCKS_STREAM: &str = "/cryptarchia/blocks/stream";

View File

@ -1,103 +0,0 @@
#[derive(Clone, Debug, Eq, PartialEq, Copy, Hash, PartialOrd, Ord)]
pub struct HeaderId([u8; 32]);
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Invalid header id size: {0}")]
InvalidHeaderIdSize(usize),
}
impl From<[u8; 32]> for HeaderId {
fn from(id: [u8; 32]) -> Self {
Self(id)
}
}
impl From<HeaderId> for [u8; 32] {
fn from(id: HeaderId) -> Self {
id.0
}
}
impl TryFrom<&[u8]> for HeaderId {
type Error = Error;
fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
if slice.len() != 32 {
return Err(Error::InvalidHeaderIdSize(slice.len()));
}
let mut id = [0u8; 32];
id.copy_from_slice(slice);
Ok(Self::from(id))
}
}
impl AsRef<[u8]> for HeaderId {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
#[derive(Clone, Debug, Eq, PartialEq, Copy, Hash)]
pub struct ContentId([u8; 32]);
impl From<ContentId> for [u8; 32] {
fn from(id: ContentId) -> Self {
id.0
}
}
macro_rules! display_hex_bytes_newtype {
($newtype:ty) => {
impl core::fmt::Display for $newtype {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "0x")?;
for v in self.0 {
write!(f, "{:02x}", v)?;
}
Ok(())
}
}
};
}
macro_rules! serde_bytes_newtype {
($newtype:ty, $len:expr) => {
impl serde::Serialize for $newtype {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if serializer.is_human_readable() {
const_hex::const_encode::<$len, false>(&self.0)
.as_str()
.serialize(serializer)
} else {
self.0.serialize(serializer)
}
}
}
impl<'de> serde::Deserialize<'de> for $newtype {
fn deserialize<D>(deserializer: D) -> Result<$newtype, D::Error>
where
D: serde::Deserializer<'de>,
{
if deserializer.is_human_readable() {
let s = <String>::deserialize(deserializer)?;
const_hex::decode_to_array(s)
.map(Self)
.map_err(serde::de::Error::custom)
} else {
<[u8; $len] as serde::Deserialize>::deserialize(deserializer).map(Self)
}
}
}
};
}
display_hex_bytes_newtype!(HeaderId);
display_hex_bytes_newtype!(ContentId);
serde_bytes_newtype!(HeaderId, 32);
serde_bytes_newtype!(ContentId, 32);

View File

@ -1,23 +0,0 @@
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use crate::structs::header_id::HeaderId;
#[derive(Clone, Debug, Eq, PartialEq, Copy, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Slot(u64);
#[derive(Clone, Debug, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum State {
Bootstrapping,
Online,
}
#[serde_as]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CryptarchiaInfo {
pub lib: HeaderId,
pub tip: HeaderId,
pub slot: Slot,
pub height: u64,
pub mode: State,
}

View File

@ -1,40 +0,0 @@
use ed25519_dalek::Signature;
use serde::{Deserialize, Serialize};
use crate::structs::{header_id::{ContentId, HeaderId}, info::Slot};
pub mod header_id;
pub mod info;
pub mod signature;
pub mod tx;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct BlockInfo {
pub height: u64,
pub header_id: HeaderId,
}
pub const BEDROCK_VERSION: u8 = 1;
#[derive(Clone, Debug, Eq, PartialEq, Copy, Serialize, Deserialize)]
#[repr(u8)]
pub enum Version {
Bedrock = BEDROCK_VERSION,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Header {
version: Version,
parent_block: HeaderId,
slot: Slot,
block_root: ContentId,
// Not sure, if need this.
// proof_of_leadership: Groth16LeaderProof,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Block<Tx> {
header: Header,
signature: Signature,
transactions: Vec<Tx>,
}

View File

@ -1,53 +0,0 @@
use core::hash::{Hash, Hasher};
use ed25519_dalek::SIGNATURE_LENGTH;
use serde::{Deserialize, Serialize};
pub const SIGNATURE_SIZE: usize = SIGNATURE_LENGTH;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub struct Signature(ed25519_dalek::Signature);
impl Signature {
#[must_use]
pub fn from_bytes(bytes: &[u8; SIGNATURE_SIZE]) -> Self {
Self(ed25519_dalek::Signature::from_bytes(bytes))
}
#[must_use]
pub fn to_bytes(&self) -> [u8; SIGNATURE_SIZE] {
self.0.to_bytes()
}
#[must_use]
pub const fn as_inner(&self) -> &ed25519_dalek::Signature {
&self.0
}
}
impl Hash for Signature {
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
{
self.0.to_bytes().hash(state);
}
}
impl From<ed25519_dalek::Signature> for Signature {
fn from(sig: ed25519_dalek::Signature) -> Self {
Self(sig)
}
}
impl From<Signature> for ed25519_dalek::Signature {
fn from(sig: Signature) -> Self {
sig.0
}
}
impl From<[u8; SIGNATURE_SIZE]> for Signature {
fn from(bytes: [u8; SIGNATURE_SIZE]) -> Self {
Self::from_bytes(&bytes)
}
}

View File

@ -1,9 +0,0 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct SignedMantleTx {
pub mantle_tx: MantleTx,
pub ops_proofs: Vec<OpProof>,
// Not sure, if we need this.
// ledger_tx_proof: ZkSignature,
}

View File

@ -1,19 +0,0 @@
[package]
name = "indexer"
version = "0.1.0"
edition = "2024"
[dependencies]
nssa.workspace = true
nssa_core.workspace = true
common.workspace = true
storage.workspace = true
base58.workspace = true
anyhow.workspace = true
serde.workspace = true
log.workspace = true
[dev-dependencies]
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
futures.workspace = true

View File

@ -1 +0,0 @@
pub mod client;