refactor(verify-era-proof-attestation): modularize and restructure proof verification logic

- Split `verify-era-proof-attestation` into modular subcomponents for maintainability.
- Moved client, proof handling, and core types into dedicated modules.
This commit is contained in:
Harald Hoyer 2025-04-02 16:03:01 +02:00
parent 1e853f653a
commit 2605e2ae3a
Signed by: harald
GPG key ID: F519A1143B3FBE32
34 changed files with 2918 additions and 2304 deletions

View file

@ -0,0 +1,139 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2023-2025 Matter Labs
use crate::{
client::{HttpClient, RetryConfig, RetryHelper},
error::{Error, Result},
proof::{
parsing::ProofResponseParser,
types::{GetProofsRequest, GetProofsResponse, Proof},
},
};
use std::time::Duration;
use tokio::sync::watch;
use url::Url;
use zksync_basic_types::{tee_types::TeeType, L1BatchNumber};
/// Handles fetching proofs from the server with retry logic
pub struct ProofFetcher {
http_client: HttpClient,
rpc_url: Url,
retry_config: RetryConfig,
}
impl ProofFetcher {
/// Create a new proof fetcher
pub fn new(http_client: HttpClient, rpc_url: Url, retry_config: RetryConfig) -> Self {
Self {
http_client,
rpc_url,
retry_config,
}
}
/// Get proofs for a batch number with retry logic
pub async fn get_proofs(
&self,
stop_receiver: &mut watch::Receiver<bool>,
batch_number: L1BatchNumber,
tee_type: &TeeType,
) -> Result<Vec<Proof>> {
let mut proofs_request = GetProofsRequest::new(batch_number, tee_type);
let mut backoff = Duration::from_secs(1);
let max_backoff = Duration::from_secs(128);
let retry_backoff_multiplier: f32 = 2.0;
while !*stop_receiver.borrow() {
match self.send_request(&proofs_request, stop_receiver).await {
Ok(response) => {
// Parse the response using the ProofResponseParser
match ProofResponseParser::parse_response(response) {
Ok(proofs) => {
// Filter valid proofs
let valid_proofs = ProofResponseParser::filter_valid_proofs(&proofs);
if !valid_proofs.is_empty() {
return Ok(valid_proofs);
}
// No valid proofs found, retry
let error_msg = format!(
"No valid TEE proofs found for batch #{}. They may not be ready yet. Retrying in {} milliseconds.",
batch_number.0,
backoff.as_millis()
);
tracing::warn!(batch_no = batch_number.0, "{}", error_msg);
// Here we could use the ProofFetching error if we needed to return immediately
// return Err(Error::ProofFetching(error_msg));
}
Err(e) => {
// Handle specific error for Sgx variant
if let Error::JsonRpc(msg) = &e {
if msg.contains("RPC requires 'Sgx' variant") {
tracing::debug!("Switching to 'Sgx' variant for RPC");
proofs_request.params.1 = "Sgx".to_string();
continue;
}
}
return Err(e);
}
}
}
Err(e) => {
return Err(e);
}
}
tokio::time::timeout(backoff, stop_receiver.changed())
.await
.ok();
backoff = std::cmp::min(
Duration::from_millis(
(backoff.as_millis() as f32 * retry_backoff_multiplier) as u64,
),
max_backoff,
);
if *stop_receiver.borrow() {
break;
}
}
// If we've reached this point, we've either been stopped or exhausted retries
if *stop_receiver.borrow() {
// Return empty vector if stopped
Ok(vec![])
} else {
// Use the ProofFetching error variant if we've exhausted retries
Err(Error::proof_fetch(batch_number, "exhausted retries"))
}
}
/// Send a request to the server with retry logic
async fn send_request(
&self,
request: &GetProofsRequest,
stop_receiver: &mut watch::Receiver<bool>,
) -> Result<GetProofsResponse> {
let retry_helper = RetryHelper::new(self.retry_config.clone());
let request_clone = request.clone();
let http_client = self.http_client.clone();
let rpc_url = self.rpc_url.clone();
retry_helper
.execute(&format!("get_proofs_{}", request.params.0), || async {
let result = http_client
.send_json::<_, GetProofsResponse>(&rpc_url, &request_clone)
.await;
// Check if we need to abort due to stop signal
if *stop_receiver.borrow() {
return Err(Error::Interrupted);
}
result
})
.await
}
}