Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
9d4b9b0
Add proving time instrumentation and log-parsing benchmark script
avilagaston9 Feb 6, 2026
f16d3d0
Fix stale VK file paths in deployer after guest program was moved out…
avilagaston9 Feb 6, 2026
5bccf59
Use pending nonce in load test to support consecutive runs without no…
avilagaston9 Feb 6, 2026
0ff9f59
Enrich benchmark script with batch metadata from Prometheus metrics e…
avilagaston9 Feb 6, 2026
a236c08
Make benchmark script read the log file once and exit instead of tail…
avilagaston9 Feb 6, 2026
a56a257
Fix off-by-one in batch_size metric: a batch spanning blocks [first, …
avilagaston9 Feb 6, 2026
9fbeaef
Add --timed/--no-timed flag to prover to control proving time measure…
avilagaston9 Feb 9, 2026
05c0785
Add env var support to load test CLI for RPC URL, tx amount, and endl…
avilagaston9 Feb 9, 2026
ab55110
Add prover benchmarking guide and agent workflow documentation.
avilagaston9 Feb 9, 2026
a786cd1
Extract load test round logic into run_round function and use it once
avilagaston9 Feb 9, 2026
48ec1c1
Change load_test and wait_until_all_included to take references for c…
avilagaston9 Feb 9, 2026
9fd1d07
Change --timed flag default to false so it must be explicitly opted in.
avilagaston9 Feb 9, 2026
131c650
Clarify GPU acceleration option in prover benchmarking guide by showing
avilagaston9 Feb 9, 2026
8375cb2
Add PROVER_ARGS variable to Makefile prover targets so extra flags like
avilagaston9 Feb 9, 2026
d0fbcaa
Use PROVER_CLIENT_TIMED env var in Makefile prover targets instead of
avilagaston9 Feb 9, 2026
baae118
Use PROVER_CLIENT_TIMED env var directly instead of a TIMED Makefile …
avilagaston9 Feb 9, 2026
e60ed34
Rewrite benchmark script to output a markdown file with a table and s…
avilagaston9 Feb 9, 2026
ff9e680
Remove load-test target from crates/l2/Makefile and use the existing
avilagaston9 Feb 9, 2026
f87ed2a
Add automatic server specs detection (CPU, RAM, GPU) to the benchmark
avilagaston9 Feb 9, 2026
db185aa
Add batches-to-prove prompt to agent setup instructions and include
avilagaston9 Feb 9, 2026
7e49164
Clarify that LOAD_TEST_RPC_URL must point to the L2 node RPC (port 1729
avilagaston9 Feb 9, 2026
517e535
Add prover_type to BatchRequest so the proof coordinator can filter
avilagaston9 Feb 9, 2026
8bd6f49
Remove redundant prover_type field from Prover struct — the backend
avilagaston9 Feb 9, 2026
49cedbf
Add missing prover_type field to TDX quote-gen BatchRequest and fix l…
avilagaston9 Feb 10, 2026
88151ee
Clarify in prover benchmarking workflow that the load test must be st…
avilagaston9 Feb 10, 2026
b4fe39c
Pre-compile the load test binary before starting the L2 to avoid
avilagaston9 Feb 10, 2026
90f8b4b
Use pre-built binaries in benchmarking workflow to eliminate redundan…
avilagaston9 Feb 10, 2026
1d3ad05
Replace todo!() with unimplemented!() in OpenVM and ZisK backends, re…
avilagaston9 Feb 10, 2026
01db920
Skip batch assignment when the requesting prover's proof already exists,
avilagaston9 Feb 11, 2026
2cb2ffe
Return ProverTypeNotNeeded immediately when a prover connects with a
avilagaston9 Feb 11, 2026
00c7bf6
Remove the all_proofs_exist loop from handle_request since it is redu…
avilagaston9 Feb 11, 2026
a059a43
Flatten handle_request into sequential early returns instead of a nested
avilagaston9 Feb 11, 2026
368cda9
Remove the redundant contains_batch check in handle_request since
avilagaston9 Feb 11, 2026
5d34ed6
Move the prover type check before the store query in handle_request so
avilagaston9 Feb 11, 2026
0740241
Fix --sp1 flag in Makefile to pass explicit 'true' value (the arg uses
avilagaston9 Feb 11, 2026
b5a1d83
Rewrite the proof coordinator's batch assignment logic to properly de…
avilagaston9 Feb 14, 2026
a8385bc
Rename NoBatchForVersion to VersionMismatch and remove the commit_has…
avilagaston9 Feb 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions cmd/ethrex/l2/deployer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1015,17 +1015,17 @@ fn get_vk(prover_type: ProverType, opts: &DeployerOptions) -> Result<Bytes, Depl
let vk_path = {
let path = match &prover_type {
ProverType::RISC0 => format!(
"{}/../../crates/l2/prover/src/ethrex_guest_program/src/risc0/out/riscv32im-risc0-vk",
"{}/../../crates/guest-program/bin/risc0/out/riscv32im-risc0-vk",
env!("CARGO_MANIFEST_DIR")
),
// Aligned requires the vk's 32 bytes hash, while the L1 verifier requires
// the hash as a bn254 F_r element.
ProverType::SP1 if opts.aligned => format!(
"{}/../../crates/l2/prover/src/ethrex_guest_program/src/sp1/out/riscv32im-succinct-zkvm-vk-u32",
"{}/../../crates/guest-program/bin/sp1/out/riscv32im-succinct-zkvm-vk-u32",
env!("CARGO_MANIFEST_DIR")
),
ProverType::SP1 if !opts.aligned => format!(
"{}/../../crates/l2/prover/src/ethrex_guest_program/src/sp1/out/riscv32im-succinct-zkvm-vk-bn254",
"{}/../../crates/guest-program/bin/sp1/out/riscv32im-succinct-zkvm-vk-bn254",
env!("CARGO_MANIFEST_DIR")
),
// other types don't have a verification key
Expand Down
10 changes: 10 additions & 0 deletions cmd/ethrex/l2/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1081,6 +1081,14 @@ pub struct ProverClientOptions {
help_heading = "Prover client options"
)]
pub log_level: Level,
#[arg(
long,
default_value_t = false,
env = "PROVER_CLIENT_TIMED",
help = "Measure and log proving time for each batch",
help_heading = "Prover client options"
)]
pub timed: bool,
#[cfg(all(feature = "sp1", feature = "gpu"))]
#[arg(
long,
Expand All @@ -1098,6 +1106,7 @@ impl From<ProverClientOptions> for ProverConfig {
backend: config.backend,
proof_coordinators: config.proof_coordinator_endpoints,
proving_time_ms: config.proving_time_ms,
timed: config.timed,
#[cfg(all(feature = "sp1", feature = "gpu"))]
sp1_server: config.sp1_server,
}
Expand All @@ -1113,6 +1122,7 @@ impl Default for ProverClientOptions {
proving_time_ms: 5000,
log_level: Level::INFO,
backend: BackendType::Exec,
timed: false,
#[cfg(all(feature = "sp1", feature = "gpu"))]
sp1_server: None,
}
Expand Down
7 changes: 2 additions & 5 deletions crates/l2/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,12 @@ deploy-l1: ## 📜 Deploys the L1 contracts
--genesis-l2-path ${L2_GENESIS_FILE_PATH}

## Same as deploy-l1 but deploys the SP1 verifier
deploy-l1-sp1: ## 📜 Deploys the L1 contracts
deploy-l1-sp1: ## 📜 Deploys the L1 contracts with SP1 verifier
COMPILE_CONTRACTS=true \
cargo run --release --features l2,l2-sql --manifest-path ../../Cargo.toml -- l2 deploy \
--eth-rpc-url ${L1_RPC_URL} \
--private-key ${L1_PRIVATE_KEY} \
--risc0.verifier-address 0x00000000000000000000000000000000000000aa \
--sp1.deploy-verifier \
--tdx.verifier-address 0x00000000000000000000000000000000000000aa \
--aligned.aggregator-address 0x00000000000000000000000000000000000000aa \
--sp1 true \
--on-chain-proposer-owner ${L2_OWNER_ADDRESS} \
--bridge-owner ${L2_OWNER_ADDRESS} \
--bridge-owner-pk ${BRIDGE_OWNER_PRIVATE_KEY} \
Expand Down
34 changes: 24 additions & 10 deletions crates/l2/common/src/prover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,13 +179,24 @@ pub enum ProofData {
/// The Client initiates the connection with a BatchRequest.
/// Asking for the ProverInputData the prover_server considers/needs.
/// The commit hash is used to ensure the client and server are compatible.
BatchRequest { commit_hash: String },
/// The prover_type tells the coordinator which backend the client runs,
/// so it can skip batches that already have a proof for that type.
BatchRequest {
commit_hash: String,
prover_type: ProverType,
},

/// 4.
/// The Server responds with a NoBatchForVersion if the code version is not the same as the one
/// generated in the batch.
/// The Client can only prove batches of its own version.
NoBatchForVersion { commit_hash: String },
/// The Server responds with VersionMismatch when the prover's code version
/// does not match the version needed to prove the next batch. This can happen
/// when the batch was stored with a different version, or when the prover is
/// stale and future batches will use a newer version.
VersionMismatch,

/// 4b.
/// The Server responds with ProverTypeNotNeeded when the connecting prover's
/// backend type is not in the set of required proof types for this deployment.
ProverTypeNotNeeded { prover_type: ProverType },

/// 5.
/// The Server responds with a BatchResponse containing the ProverInputData.
Expand Down Expand Up @@ -224,13 +235,16 @@ impl ProofData {
}

/// Builder function for creating a BatchRequest
pub fn batch_request(commit_hash: String) -> Self {
ProofData::BatchRequest { commit_hash }
pub fn batch_request(commit_hash: String, prover_type: ProverType) -> Self {
ProofData::BatchRequest {
commit_hash,
prover_type,
}
}

/// Builder function for creating a NoBatchForVersion
pub fn no_batch_for_version(commit_hash: String) -> Self {
ProofData::NoBatchForVersion { commit_hash }
/// Builder function for creating a VersionMismatch
pub fn version_mismatch() -> Self {
ProofData::VersionMismatch
}

/// Builder function for creating a BatchResponse
Expand Down
4 changes: 4 additions & 0 deletions crates/l2/prover/src/backend/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ impl ProverBackend for ExecBackend {
type ProofOutput = ProgramOutput;
type SerializedInput = ();

fn prover_type(&self) -> ProverType {
ProverType::Exec
}

fn serialize_input(
&self,
_input: &ProgramInput,
Expand Down
5 changes: 4 additions & 1 deletion crates/l2/prover/src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::time::{Duration, Instant};

use clap::ValueEnum;
use ethrex_guest_program::input::ProgramInput;
use ethrex_l2_common::prover::{BatchProof, ProofFormat};
use ethrex_l2_common::prover::{BatchProof, ProofFormat, ProverType};
use serde::{Deserialize, Serialize};

pub mod error;
Expand Down Expand Up @@ -85,6 +85,9 @@ pub trait ProverBackend {
/// The serialized input type specific to this backend.
type SerializedInput;

/// Returns the ProverType for this backend.
fn prover_type(&self) -> ProverType;

/// Serialize the program input into the backend-specific format.
fn serialize_input(&self, input: &ProgramInput) -> Result<Self::SerializedInput, BackendError>;

Expand Down
6 changes: 5 additions & 1 deletion crates/l2/prover/src/backend/openvm.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::time::{Duration, Instant};

use ethrex_guest_program::input::ProgramInput;
use ethrex_l2_common::prover::{BatchProof, ProofFormat};
use ethrex_l2_common::prover::{BatchProof, ProofFormat, ProverType};
use openvm_continuations::verifier::internal::types::VmStarkProof;
use openvm_sdk::{Sdk, StdIn, types::EvmProof};
use openvm_stark_sdk::config::baby_bear_poseidon2::BabyBearPoseidon2Config;
Expand Down Expand Up @@ -64,6 +64,10 @@ impl ProverBackend for OpenVmBackend {
type ProofOutput = OpenVmProveOutput;
type SerializedInput = StdIn;

fn prover_type(&self) -> ProverType {
unimplemented!("OpenVM is not yet enabled as a backend for the L2")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: unimplemented!() panics at runtime. Since prover_type() is called on every BatchRequest (via self.backend.prover_type() in request_new_input), anyone who accidentally starts a prover with --backend openvm will get a panic instead of a clean error. This is fine for now since OpenVM isn't wired up, but worth knowing — the trait signature (-> ProverType instead of -> Result<ProverType, _>) makes this the only option short of changing the trait.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — keeping unimplemented\!() intentionally since changing the trait signature to Result would add complexity to every backend for a case that can't happen in practice (these backends are feature-gated and not wired for L2 yet).

}

fn serialize_input(&self, input: &ProgramInput) -> Result<Self::SerializedInput, BackendError> {
let mut stdin = StdIn::default();
let bytes = rkyv::to_bytes::<Error>(input).map_err(BackendError::serialization)?;
Expand Down
4 changes: 4 additions & 0 deletions crates/l2/prover/src/backend/risc0.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ impl ProverBackend for Risc0Backend {
type ProofOutput = Receipt;
type SerializedInput = ExecutorEnv<'static>;

fn prover_type(&self) -> ProverType {
ProverType::RISC0
}

fn serialize_input(&self, input: &ProgramInput) -> Result<Self::SerializedInput, BackendError> {
let bytes = rkyv::to_bytes::<RkyvError>(input).map_err(BackendError::serialization)?;
ExecutorEnv::builder()
Expand Down
4 changes: 4 additions & 0 deletions crates/l2/prover/src/backend/sp1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ impl ProverBackend for Sp1Backend {
type ProofOutput = Sp1ProveOutput;
type SerializedInput = SP1Stdin;

fn prover_type(&self) -> ProverType {
ProverType::SP1
}

fn serialize_input(&self, input: &ProgramInput) -> Result<Self::SerializedInput, BackendError> {
let mut stdin = SP1Stdin::new();
let bytes = rkyv::to_bytes::<Error>(input).map_err(BackendError::serialization)?;
Expand Down
6 changes: 5 additions & 1 deletion crates/l2/prover/src/backend/zisk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::{
};

use ethrex_guest_program::{ZKVM_ZISK_PROGRAM_ELF, input::ProgramInput};
use ethrex_l2_common::prover::{BatchProof, ProofFormat};
use ethrex_l2_common::prover::{BatchProof, ProofFormat, ProverType};

use crate::backend::{BackendError, ProverBackend};

Expand Down Expand Up @@ -113,6 +113,10 @@ impl ProverBackend for ZiskBackend {
type ProofOutput = ZiskProveOutput;
type SerializedInput = ();

fn prover_type(&self) -> ProverType {
unimplemented!("ZisK is not yet enabled as a backend for the L2")
}

fn serialize_input(&self, input: &ProgramInput) -> Result<Self::SerializedInput, BackendError> {
let input_bytes =
rkyv::to_bytes::<rkyv::rancor::Error>(input).map_err(BackendError::serialization)?;
Expand Down
1 change: 1 addition & 0 deletions crates/l2/prover/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub struct ProverConfig {
pub backend: BackendType,
pub proof_coordinators: Vec<Url>,
pub proving_time_ms: u64,
pub timed: bool,
#[cfg(all(feature = "sp1", feature = "gpu"))]
pub sp1_server: Option<Url>,
}
101 changes: 73 additions & 28 deletions crates/l2/prover/src/prover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{
};
use ethrex_guest_program::input::ProgramInput;
use ethrex_l2::sequencer::utils::get_git_commit_hash;
use ethrex_l2_common::prover::{BatchProof, ProofData, ProofFormat};
use ethrex_l2_common::prover::{BatchProof, ProofData, ProofFormat, ProverType};
use std::time::Duration;
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
Expand Down Expand Up @@ -57,10 +57,23 @@ struct ProverData {
format: ProofFormat,
}

/// The result of polling a proof coordinator for work.
enum InputRequest {
/// A batch was assigned to this prover.
Batch(Box<ProverData>),
/// No work available right now (prover ahead of proposer, proof already
/// exists, version mismatch). The prover should retry later.
RetryLater,
/// The coordinator permanently rejected this prover's type.
/// The prover should skip this coordinator and continue with others.
ProverTypeNotNeeded(ProverType),
}

struct Prover<B: ProverBackend> {
backend: B,
proof_coordinator_endpoints: Vec<Url>,
proving_time_ms: u64,
timed: bool,
commit_hash: String,
}

Expand All @@ -70,6 +83,7 @@ impl<B: ProverBackend> Prover<B> {
backend,
proof_coordinator_endpoints: cfg.proof_coordinators.clone(),
proving_time_ms: cfg.proving_time_ms,
timed: cfg.timed,
commit_hash: get_git_commit_hash(),
}
}
Expand All @@ -82,27 +96,55 @@ impl<B: ProverBackend> Prover<B> {
.map(|url| url.to_string())
.collect::<Vec<String>>()
);
// Build the prover depending on the prover_type passed as argument.
loop {
sleep(Duration::from_millis(self.proving_time_ms)).await;

for endpoint in &self.proof_coordinator_endpoints {
let Ok(Some(prover_data)) = self
.request_new_input(endpoint)
.await
.inspect_err(|e| error!(%endpoint, "Failed to request new data from: {e}"))
else {
continue;
let prover_data = match self.request_new_input(endpoint).await {
Ok(InputRequest::Batch(data)) => *data,
Ok(InputRequest::RetryLater) => continue,
Ok(InputRequest::ProverTypeNotNeeded(prover_type)) => {
error!(
%endpoint,
"Proof coordinator does not need {prover_type} proofs. \
This prover's backend is not in the required proof types \
for this deployment."
);
continue;
}
Err(e) => {
error!(%endpoint, "Failed to request new data: {e}");
continue;
}
};

// If we get the input
// Generate the Proof
let Ok(batch_proof) = self
.backend
.prove(prover_data.input, prover_data.format)
.and_then(|output| self.backend.to_batch_proof(output, prover_data.format))
.inspect_err(|e| error!("{e}"))
else {
let batch_proof = if self.timed {
self.backend
.prove_timed(prover_data.input, prover_data.format)
.and_then(|(output, elapsed)| {
info!(
batch = prover_data.batch_number,
proving_time_s = elapsed.as_secs(),
proving_time_ms =
u64::try_from(elapsed.as_millis()).unwrap_or(u64::MAX),
"Proved batch {} in {:.2?}",
prover_data.batch_number,
elapsed
);
self.backend.to_batch_proof(output, prover_data.format)
})
} else {
self.backend
.prove(prover_data.input, prover_data.format)
.and_then(|output| {
info!(
batch = prover_data.batch_number,
"Proved batch {}", prover_data.batch_number
);
self.backend.to_batch_proof(output, prover_data.format)
})
};
let Ok(batch_proof) = batch_proof.inspect_err(|e| error!("{e}")) else {
continue;
};

Expand All @@ -116,9 +158,9 @@ impl<B: ProverBackend> Prover<B> {
}
}

async fn request_new_input(&self, endpoint: &Url) -> Result<Option<ProverData>, String> {
// Request the input with the correct batch_number
let request = ProofData::batch_request(self.commit_hash.clone());
async fn request_new_input(&self, endpoint: &Url) -> Result<InputRequest, String> {
let request =
ProofData::batch_request(self.commit_hash.clone(), self.backend.prover_type());
let response = connect_to_prover_server_wr(endpoint, &request)
.await
.map_err(|e| format!("Failed to get Response: {e}"))?;
Expand All @@ -129,22 +171,25 @@ impl<B: ProverBackend> Prover<B> {
input,
format,
} => (batch_number, input, format),
ProofData::NoBatchForVersion { commit_hash } => {
ProofData::VersionMismatch => {
warn!(
"Received no batch available to prove for current version: {}. The prover may be older or newer to the next batch to prove",
commit_hash,
"Version mismatch: the next batch to prove was built with a different code \
version. This prover may need to be updated."
);
return Ok(None);
return Ok(InputRequest::RetryLater);
}
ProofData::ProverTypeNotNeeded { prover_type } => {
return Ok(InputRequest::ProverTypeNotNeeded(prover_type));
}
_ => return Err("Expecting ProofData::Response".to_owned()),
};

let (Some(batch_number), Some(input), Some(format)) = (batch_number, input, format) else {
warn!(
debug!(
%endpoint,
"Received Empty Response, meaning that the ProverServer doesn't have batches to prove.\nThe Prover may be advancing faster than the Proposer."
"No batches to prove right now, the prover may be ahead of the proposer"
);
return Ok(None);
return Ok(InputRequest::RetryLater);
};

info!(%endpoint, "Received Response for batch_number: {batch_number}");
Expand All @@ -162,11 +207,11 @@ impl<B: ProverBackend> Prover<B> {
blocks: input.blocks,
execution_witness: input.execution_witness,
};
Ok(Some(ProverData {
Ok(InputRequest::Batch(Box::new(ProverData {
batch_number,
input,
format,
}))
})))
}

async fn submit_proof(
Expand Down
Loading