Off eth ref (#67)

* dummy prover

* delete binaries

* absolute binary paths

* revert processor change

* improve prover with more idiomatic rust code

* lightnode

* lightnode

* nomos types needed for client

* credentials

* basic http calls

* cryptarchia info and block fetch loop

* remove printlns

* serve proofs through http interface

* add missing deps

* remove tracing initialization

* check binary

* fix logs

* remove non-working code

* fix

---------

Co-authored-by: Petar Radovic <petar.radovic@gmail.com>
This commit is contained in:
Giacomo Pasini 2025-04-24 12:42:22 +02:00 committed by Giacomo Pasini
parent d464f8f465
commit 35cba3a980
No known key found for this signature in database
GPG Key ID: FC08489D2D895D4B
11 changed files with 359 additions and 14 deletions

View File

@ -1,5 +1,5 @@
[workspace]
members = ["evm/processor", "evm/sequencer-node", "evm/prover"]
members = ["evm/processor", "evm/sequencer-node", "evm/prover", "evm/lightnode"]
resolver = "3"
[workspace.package]
@ -10,6 +10,7 @@ edition = "2024"
evm-processor = { path = "evm/processor" }
evm-sequencer-node = { path = "evm/sequencer-node" }
evm-prover = { path = "evm/prover" }
evm-lightnode = { path = "evm/lightnode" }
# External
eyre = { version = "0.6" }

View File

@ -0,0 +1,18 @@
[package]
name = "evm-lightnode"
edition = { workspace = true }
[dependencies]
clap = { version = "4.5", features = ["derive"] }
reqwest = { version = "0.11", features = ["blocking", "json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
executor-http-client = { git = "https://github.com/logos-co/nomos", branch = "master" }
tokio = { version = "1", features = ["full"] }
crypto-bigint = { version = "0.5.5", features = ["serde"] }
risc0-zkvm = { version = "2" }
indexmap = { version = "1.9.3" }
url = { version = "2" }
hex = { version = "0.4" }

View File

@ -0,0 +1,94 @@
use nomos::{CryptarchiaInfo, HeaderId};
use reqwest::Url;
use tracing::{error, info};
pub const CRYPTARCHIA_INFO: &str = "cryptarchia/info";
pub const STORAGE_BLOCK: &str = "storage/block";
pub mod nomos;
pub mod proofcheck;
#[derive(Clone, Debug)]
pub struct Credentials {
pub username: String,
pub password: Option<String>,
}
pub struct NomosClient {
base_url: Url,
reqwest_client: reqwest::Client,
basic_auth: Credentials,
}
impl NomosClient {
pub fn new(base_url: Url, basic_auth: Credentials) -> Self {
Self {
base_url,
reqwest_client: reqwest::Client::new(),
basic_auth,
}
}
pub async fn get_cryptarchia_info(&self) -> Result<CryptarchiaInfo, String> {
let url = self.base_url.join(CRYPTARCHIA_INFO).expect("Invalid URL");
info!("Requesting cryptarchia info from {}", url);
let request = self.reqwest_client.get(url).basic_auth(
&self.basic_auth.username,
self.basic_auth.password.as_deref(),
);
let response = request.send().await.map_err(|e| {
error!("Failed to send request: {}", e);
"Failed to send request".to_string()
})?;
if !response.status().is_success() {
error!("Failed to get cryptarchia info: {}", response.status());
return Err("Failed to get cryptarchia info".to_string());
}
let info = response.json::<CryptarchiaInfo>().await.map_err(|e| {
error!("Failed to parse response: {}", e);
"Failed to parse response".to_string()
})?;
Ok(info)
}
pub async fn get_block(&self, id: HeaderId) -> Result<serde_json::Value, String> {
let url = self.base_url.join(STORAGE_BLOCK).expect("Invalid URL");
info!("Requesting block with HeaderId {}", id);
let request = self
.reqwest_client
.post(url)
.header("Content-Type", "application/json")
.basic_auth(
&self.basic_auth.username,
self.basic_auth.password.as_deref(),
)
.body(serde_json::to_string(&id).unwrap());
let response = request.send().await.map_err(|e| {
error!("Failed to send request: {}", e);
"Failed to send request".to_string()
})?;
if !response.status().is_success() {
error!("Failed to get block: {}", response.status());
return Err("Failed to get block".to_string());
}
let json: serde_json::Value = response.json().await.map_err(|e| {
error!("Failed to parse JSON: {}", e);
"Failed to parse JSON".to_string()
})?;
info!(
"Block (raw): {}",
serde_json::to_string_pretty(&json).unwrap()
);
Ok(json)
}
}

View File

@ -0,0 +1,48 @@
use clap::Parser;
use evm_lightnode::proofcheck;
use url::Url;
use std::path::PathBuf;
use std::error;
use tracing_subscriber::{EnvFilter, fmt};
#[derive(Parser, Debug)]
#[clap(author, version, about = "Light Node validator")]
struct Args {
#[clap(long, default_value = "info")]
log_level: String,
#[clap(long, default_value = "http://localhost:8546")]
rpc: Url,
#[clap(long, default_value = "http://localhost:8070")]
prover_url: Url,
#[clap(long)]
start_block: u64,
#[clap(long, default_value = "10")]
batch_size: u64,
#[clap(long)]
zeth_binary_dir: Option<PathBuf>,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn error::Error>> {
let args = Args::parse();
let filter =
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(&args.log_level));
fmt::fmt().with_env_filter(filter).with_target(false).init();
proofcheck::verify_proof(
args.start_block,
args.batch_size,
&args.rpc,
&args.prover_url,
&args.zeth_binary_dir.unwrap_or_else(|| std::env::current_dir().unwrap()).join("zeth-ethereum"),
).await?;
Ok(())
}

View File

@ -0,0 +1,52 @@
use serde::{Deserialize, Deserializer, Serialize};
use hex::FromHex;
// tip "4f573735fb987453f7467688ea4e034b9161e3ca200526faf5c8ce6db09da180"
// slot 5085
// height 1245
#[derive(Serialize, Deserialize, Debug)]
pub struct CryptarchiaInfo {
pub tip: HeaderId,
pub slot: u64,
pub height: u64,
}
#[derive(Clone, Debug, Eq, PartialEq, Copy, Hash, PartialOrd, Ord, Default)]
pub struct HeaderId([u8; 32]);
impl<'de> Deserialize<'de> for HeaderId {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let hex_str = String::deserialize(deserializer)?;
let bytes = <[u8; 32]>::from_hex(hex_str)
.map_err(|e| serde::de::Error::custom(format!("Invalid hex string: {}", e)))?;
Ok(HeaderId(bytes))
}
}
impl Serialize for HeaderId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let hex_str = hex::encode(self.0);
serializer.serialize_str(&hex_str)
}
}
use std::fmt;
impl fmt::Display for HeaderId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in &self.0 {
write!(f, "{:02x}", byte)?;
}
Ok(())
}
}

View File

@ -0,0 +1,57 @@
use tokio::process::Command;
use std::path::Path;
use reqwest::Url;
use tracing::{error, info};
pub async fn verify_proof(
block_number: u64,
block_count: u64,
rpc: &Url,
prover_url: &Url,
zeth_bin: &Path,
) -> Result<(), String> {
info!(
"Verifying proof for blocks {}-{}",
block_number,
block_number + block_count - 1
);
let url = prover_url.join(&format!(
"/?block_start={}&block_count={}",
block_number, block_count
)).map_err(|e| format!("Failed to construct URL: {}", e))?;
let proof = reqwest::get(url).await
.map_err(|e| format!("Failed to fetch proof: {}", e))?
.bytes()
.await
.map_err(|e| format!("Failed to read proof response: {}", e))?;
let filename = format!("{}-{}.zkp", block_number, block_count + block_number);
tokio::fs::write(&filename, &proof)
.await
.map_err(|e| format!("Failed to write proof to file: {}", e))?;
let output = Command::new(zeth_bin)
.args([
"verify",
&format!("--rpc={}", rpc),
&format!("--block-number={}", block_number),
&format!("--block-count={}", block_count),
&format!("--file={}", filename),
])
.output().await
.map_err(|e| format!("Failed to execute zeth-ethereum verify: {}", e))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
error!("zeth-ethereum verify command failed: {}", stderr);
return Err(format!(
"zeth-ethereum verify command failed with status: {}\nStderr: {}",
output.status, stderr
));
}
info!("Proof verification successful");
Ok(())
}

View File

@ -8,4 +8,4 @@ executor-http-client = { git = "https://github.com/logos-co/nomos", branch = "ma
reqwest = "0.11"
kzgrs-backend = { git = "https://github.com/logos-co/nomos", branch = "master" }
bincode = "1"
reth-tracing = { workspace = true }
reth-tracing = { workspace = true }

View File

@ -3,9 +3,11 @@ name = "evm-prover"
edition = { workspace = true }
[dependencies]
axum = "0.8.3"
clap = { version = "4.5", features = ["derive"] }
reqwest = { version = "0.11", features = ["blocking", "json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1.44.2", features = ["full"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

View File

@ -0,0 +1,39 @@
use axum::{
extract::Query,
http::StatusCode,
response::{IntoResponse, Response},
};
use serde::Deserialize;
use std::path::PathBuf;
use tokio::fs;
#[derive(Deserialize)]
pub struct ProofRequest {
block_start: u64,
block_count: u64
}
/// Handler for GET /
pub async fn serve_proof(Query(query): Query<ProofRequest>) -> Response {
let file_name = format!("{}-{}.zkp", query.block_start, query.block_count + query.block_start);
let path = PathBuf::from(&file_name);
// Read file contents
match fs::read(&path).await {
Ok(bytes) => (
StatusCode::OK,
bytes,
).into_response(),
Err(err) => {
let status = if err.kind() == std::io::ErrorKind::NotFound {
StatusCode::NOT_FOUND
} else {
StatusCode::INTERNAL_SERVER_ERROR
};
(status, format!("Error reading file: {}", err)).into_response()
}
}
}

View File

@ -1,9 +1,15 @@
use clap::Parser;
use reqwest::blocking::Client;
use serde_json::{Value, json};
use std::{path::PathBuf, process::Command, thread, time::Duration};
use serde_json::{json, Value};
use std::{path::PathBuf, process::Command, thread, time::Duration, net::SocketAddr};
use tracing::{debug, error, info};
use tracing_subscriber::{EnvFilter, fmt};
use tracing_subscriber::{fmt, EnvFilter};
use axum::{
routing::get,
Router,
};
mod http;
#[derive(Parser, Debug)]
#[clap(author, version, about = "Ethereum Proof Generation Tool")]
@ -117,6 +123,12 @@ fn get_latest_block(client: &Client, rpc: &str) -> Result<u64, String> {
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();
std::thread::spawn(move || {
if let Err(e) = run_server() {
error!("Error running server: {}", e);
}
});
let filter =
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(&args.log_level));
@ -166,3 +178,20 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
}
}
#[tokio::main]
async fn run_server() -> Result<(), Box<dyn std::error::Error>> {
// Build our application with a route
let app = Router::new()
.route("/", get(http::serve_proof));
let addr = SocketAddr::from(([127, 0, 0, 1], 8070));
// Run it on localhost:8070
tracing::info!("Serving files on http://{}", addr);
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
axum::serve(listener, app).await.unwrap();
Ok(())
}

View File

@ -1,4 +1,4 @@
use evm_processor::{Processor, NomosDa, BasicAuthCredentials};
use evm_processor::{BasicAuthCredentials, NomosDa, Processor};
use futures::TryStreamExt as _;
use reth::{
api::{FullNodeTypes, NodePrimitives, NodeTypes},
@ -25,13 +25,15 @@ where
continue;
};
info!(committed_chain = ?new.range(), "Received commit");
processor.process_blocks(
new.inner()
.0
.clone()
.into_blocks()
.map(reth_ethereum::primitives::RecoveredBlock::into_block),
).await;
processor
.process_blocks(
new.inner()
.0
.clone()
.into_blocks()
.map(reth_ethereum::primitives::RecoveredBlock::into_block),
)
.await;
ctx.events
.send(ExExEvent::FinishedHeight(new.tip().num_hash()))
@ -57,7 +59,10 @@ fn main() -> eyre::Result<()> {
let url = std::env::var("NOMOS_EXECUTOR").unwrap_or(TESTNET_EXECUTOR.to_string());
let user = std::env::var("NOMOS_USER").unwrap_or_default();
let password = std::env::var("NOMOS_PASSWORD").unwrap_or_default();
let da = NomosDa::new( BasicAuthCredentials::new(user, Some(password)), url::Url::parse(&url).unwrap());
let da = NomosDa::new(
BasicAuthCredentials::new(user, Some(password)),
url::Url::parse(&url).unwrap(),
);
let processor = Processor::new(da);
let handle = Box::pin(
builder