From 149b00d2a3ea11910d3076887126d3df63a36292 Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Tue, 6 Jan 2026 15:09:03 +0200 Subject: [PATCH 1/2] init: initial structure --- Cargo.lock | 16 ++++++++++++++++ Cargo.toml | 2 +- indexer/Cargo.toml | 19 +++++++++++++++++++ indexer/src/client/mod.rs | 0 indexer/src/lib.rs | 1 + 5 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 indexer/Cargo.toml create mode 100644 indexer/src/client/mod.rs create mode 100644 indexer/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index eeb8f5b..2c5200a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2198,6 +2198,22 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee796ad498c8d9a1d68e477df8f754ed784ef875de1414ebdaf169f70a6a784" +[[package]] +name = "indexer" +version = "0.1.0" +dependencies = [ + "anyhow", + "base58", + "common", + "futures", + "log", + "nssa", + "nssa_core", + "serde", + "storage", + "tokio", +] + [[package]] name = "indexmap" version = "1.9.3" diff --git a/Cargo.toml b/Cargo.toml index 14856d0..f832786 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ members = [ "integration_tests/proc_macro_test_attribute", "examples/program_deployment", "examples/program_deployment/methods", - "examples/program_deployment/methods/guest", + "examples/program_deployment/methods/guest", "indexer", ] [workspace.dependencies] diff --git a/indexer/Cargo.toml b/indexer/Cargo.toml new file mode 100644 index 0000000..4b93ad0 --- /dev/null +++ b/indexer/Cargo.toml @@ -0,0 +1,19 @@ +[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 \ No newline at end of file diff --git a/indexer/src/client/mod.rs b/indexer/src/client/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/indexer/src/lib.rs b/indexer/src/lib.rs new file mode 100644 index 0000000..3935517 --- /dev/null +++ b/indexer/src/lib.rs @@ -0,0 +1 @@ +pub mod client; \ No newline at end of file From 1a033af5f248d55e95625357c011645c103cc72c Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Wed, 7 Jan 2026 16:29:37 +0200 Subject: [PATCH 2/2] fix: adapting http client --- Cargo.lock | 87 ++++++++++++ Cargo.toml | 6 +- bedrock_client/Cargo.toml | 17 +++ bedrock_client/src/lib.rs | 174 ++++++++++++++++++++++++ bedrock_client/src/paths.rs | 6 + bedrock_client/src/structs/header_id.rs | 103 ++++++++++++++ bedrock_client/src/structs/info.rs | 23 ++++ bedrock_client/src/structs/mod.rs | 40 ++++++ bedrock_client/src/structs/signature.rs | 53 ++++++++ bedrock_client/src/structs/tx.rs | 9 ++ 10 files changed, 517 insertions(+), 1 deletion(-) create mode 100644 bedrock_client/Cargo.toml create mode 100644 bedrock_client/src/lib.rs create mode 100644 bedrock_client/src/paths.rs create mode 100644 bedrock_client/src/structs/header_id.rs create mode 100644 bedrock_client/src/structs/info.rs create mode 100644 bedrock_client/src/structs/mod.rs create mode 100644 bedrock_client/src/structs/signature.rs create mode 100644 bedrock_client/src/structs/tx.rs diff --git a/Cargo.lock b/Cargo.lock index 2c5200a..e58d4e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -657,6 +657,23 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" +[[package]] +name = "bedrock_client" +version = "0.1.0" +dependencies = [ + "anyhow", + "const-hex", + "ed25519-dalek", + "futures", + "log", + "reqwest 0.11.27", + "serde", + "serde_json", + "serde_with", + "thiserror", + "url", +] + [[package]] name = "bincode" version = "1.3.3" @@ -1046,6 +1063,18 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "const-hex" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bb320cac8a0750d7f25280aa97b09c26edfe161164238ecbbb31092b079e735" +dependencies = [ + "cfg-if", + "cpufeatures", + "proptest", + "serde_core", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -1150,6 +1179,33 @@ dependencies = [ "cipher", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "darling" version = "0.20.11" @@ -1407,6 +1463,31 @@ dependencies = [ "spki", ] +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "serde", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.6.4", + "serde", + "sha2", + "subtle", + "zeroize", +] + [[package]] name = "educe" version = "0.6.0" @@ -1561,6 +1642,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "find-msvc-tools" version = "0.1.5" diff --git a/Cargo.toml b/Cargo.toml index f832786..92e3b7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ members = [ "integration_tests/proc_macro_test_attribute", "examples/program_deployment", "examples/program_deployment/methods", - "examples/program_deployment/methods/guest", "indexer", + "examples/program_deployment/methods/guest", "indexer", "bedrock_client", ] [workspace.dependencies] @@ -76,6 +76,10 @@ chrono = "0.4.41" 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" } rocksdb = { version = "0.24.0", default-features = false, features = [ "snappy", diff --git a/bedrock_client/Cargo.toml b/bedrock_client/Cargo.toml new file mode 100644 index 0000000..bc0f927 --- /dev/null +++ b/bedrock_client/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "bedrock_client" +version = "0.1.0" +edition = "2024" + +[dependencies] +reqwest.workspace = true +anyhow.workspace = true +serde.workspace = true +log.workspace = true +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"] } diff --git a/bedrock_client/src/lib.rs b/bedrock_client/src/lib.rs new file mode 100644 index 0000000..d3d4fbb --- /dev/null +++ b/bedrock_client/src/lib.rs @@ -0,0 +1,174 @@ +//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, +} + +impl BasicAuthCredentials { + #[must_use] + pub const fn new(username: String, password: Option) -> Self { + Self { username, password } + } +} + +#[derive(Clone)] +pub struct CommonHttpClient { + client: Arc, + basic_auth: Option, +} + +impl CommonHttpClient { + #[must_use] + pub fn new(basic_auth: Option) -> 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) -> Self { + Self { + client: Arc::new(client), + basic_auth, + } + } + + pub async fn post(&self, request_url: Url, request_body: &Req) -> Result + where + Req: Serialize + ?Sized + Send + Sync, + Res: DeserializeOwned + Send + Sync, + { + let request = self.client.post(request_url).json(request_body); + self.execute_request::(request).await + } + + pub async fn get( + &self, + request_url: Url, + request_body: Option<&Req>, + ) -> Result + 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::(request).await + } + + async fn execute_request( + &self, + mut request: RequestBuilder, + ) -> Result { + 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, 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::(&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( + &self, + base_url: Url, + header_id: HeaderId, + ) -> Result>, 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 { + 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>, Error> { + let request_url = base_url + .join(STORAGE_BLOCK.trim_start_matches('/')) + .map_err(Error::Url)?; + self.post(request_url, &header_id).await + } +} diff --git a/bedrock_client/src/paths.rs b/bedrock_client/src/paths.rs new file mode 100644 index 0000000..5149f4d --- /dev/null +++ b/bedrock_client/src/paths.rs @@ -0,0 +1,6 @@ +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"; diff --git a/bedrock_client/src/structs/header_id.rs b/bedrock_client/src/structs/header_id.rs new file mode 100644 index 0000000..4b032bc --- /dev/null +++ b/bedrock_client/src/structs/header_id.rs @@ -0,0 +1,103 @@ +#[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 for [u8; 32] { + fn from(id: HeaderId) -> Self { + id.0 + } +} + +impl TryFrom<&[u8]> for HeaderId { + type Error = Error; + + fn try_from(slice: &[u8]) -> Result { + 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 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(&self, serializer: S) -> Result + 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(deserializer: D) -> Result<$newtype, D::Error> + where + D: serde::Deserializer<'de>, + { + if deserializer.is_human_readable() { + let s = ::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); \ No newline at end of file diff --git a/bedrock_client/src/structs/info.rs b/bedrock_client/src/structs/info.rs new file mode 100644 index 0000000..3e33246 --- /dev/null +++ b/bedrock_client/src/structs/info.rs @@ -0,0 +1,23 @@ +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, +} \ No newline at end of file diff --git a/bedrock_client/src/structs/mod.rs b/bedrock_client/src/structs/mod.rs new file mode 100644 index 0000000..36c2aab --- /dev/null +++ b/bedrock_client/src/structs/mod.rs @@ -0,0 +1,40 @@ +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 { + header: Header, + signature: Signature, + transactions: Vec, +} \ No newline at end of file diff --git a/bedrock_client/src/structs/signature.rs b/bedrock_client/src/structs/signature.rs new file mode 100644 index 0000000..1075354 --- /dev/null +++ b/bedrock_client/src/structs/signature.rs @@ -0,0 +1,53 @@ +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(&self, state: &mut H) + where + H: Hasher, + { + self.0.to_bytes().hash(state); + } +} + +impl From for Signature { + fn from(sig: ed25519_dalek::Signature) -> Self { + Self(sig) + } +} + +impl From 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) + } +} diff --git a/bedrock_client/src/structs/tx.rs b/bedrock_client/src/structs/tx.rs new file mode 100644 index 0000000..16c083b --- /dev/null +++ b/bedrock_client/src/structs/tx.rs @@ -0,0 +1,9 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct SignedMantleTx { + pub mantle_tx: MantleTx, + pub ops_proofs: Vec, + // Not sure, if we need this. + // ledger_tx_proof: ZkSignature, +} \ No newline at end of file