Merge 1a033af5f248d55e95625357c011645c103cc72c into 1d09afd9e004953a7218fe02d4e897cc45c67465

This commit is contained in:
Pravdyvy 2026-01-07 14:29:45 +00:00 committed by GitHub
commit d34d101e30
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 553 additions and 1 deletions

103
Cargo.lock generated
View File

@ -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"
@ -2198,6 +2285,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"

View File

@ -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", "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",

17
bedrock_client/Cargo.toml Normal file
View File

@ -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"] }

174
bedrock_client/src/lib.rs Normal file
View File

@ -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<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
}
}

View File

@ -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";

View File

@ -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<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

@ -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,
}

View File

@ -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<Tx> {
header: Header,
signature: Signature,
transactions: Vec<Tx>,
}

View File

@ -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<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

@ -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<OpProof>,
// Not sure, if we need this.
// ledger_tx_proof: ZkSignature,
}

19
indexer/Cargo.toml Normal file
View File

@ -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

View File

1
indexer/src/lib.rs Normal file
View File

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