mirror of
https://github.com/matter-labs/teepot.git
synced 2025-07-26 09:04:47 +02:00
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:
parent
1e853f653a
commit
2605e2ae3a
34 changed files with 2918 additions and 2304 deletions
66
bin/verify-era-proof-attestation/src/client/http.rs
Normal file
66
bin/verify-era-proof-attestation/src/client/http.rs
Normal file
|
@ -0,0 +1,66 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023-2025 Matter Labs
|
||||
|
||||
//! HTTP client for making requests to external services
|
||||
|
||||
use reqwest::Client;
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
core::DEFAULT_HTTP_REQUEST_TIMEOUT,
|
||||
error::{Error, Result},
|
||||
};
|
||||
|
||||
/// Client for making HTTP requests
|
||||
#[derive(Clone)]
|
||||
pub struct HttpClient {
|
||||
client: Client,
|
||||
}
|
||||
|
||||
impl HttpClient {
|
||||
/// Create a new HTTP client with default configuration
|
||||
pub fn new() -> Self {
|
||||
let client = Client::builder()
|
||||
.timeout(Duration::from_secs(DEFAULT_HTTP_REQUEST_TIMEOUT))
|
||||
.build()
|
||||
.expect("Failed to create HTTP client");
|
||||
|
||||
Self { client }
|
||||
}
|
||||
|
||||
/// Make a POST request to the specified URL with the provided body
|
||||
pub async fn post<T: Serialize>(&self, url: &Url, body: T) -> Result<String> {
|
||||
let response = self.client.post(url.clone()).json(&body).send().await?;
|
||||
self.handle_response(response).await
|
||||
}
|
||||
|
||||
/// Send a JSON request and parse the response
|
||||
pub async fn send_json<T: Serialize, R: DeserializeOwned>(
|
||||
&self,
|
||||
url: &Url,
|
||||
body: T,
|
||||
) -> Result<R> {
|
||||
let response_text = self.post(url, body).await?;
|
||||
let response: R = serde_json::from_str(&response_text)
|
||||
.map_err(|e| Error::JsonRpcInvalidResponse(e.to_string()))?;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
/// Handle the HTTP response
|
||||
async fn handle_response(&self, response: reqwest::Response) -> Result<String> {
|
||||
let status = response.status();
|
||||
let body = response.text().await?;
|
||||
|
||||
if status.is_success() {
|
||||
Ok(body)
|
||||
} else {
|
||||
Err(Error::Http {
|
||||
status_code: status.as_u16(),
|
||||
message: body,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
58
bin/verify-era-proof-attestation/src/client/json_rpc.rs
Normal file
58
bin/verify-era-proof-attestation/src/client/json_rpc.rs
Normal file
|
@ -0,0 +1,58 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023-2025 Matter Labs
|
||||
|
||||
use url::Url;
|
||||
use zksync_basic_types::{L1BatchNumber, H256};
|
||||
use zksync_types::L2ChainId;
|
||||
use zksync_web3_decl::{
|
||||
client::{Client as NodeClient, L2},
|
||||
error::ClientRpcContext,
|
||||
namespaces::ZksNamespaceClient,
|
||||
};
|
||||
|
||||
use crate::error;
|
||||
|
||||
/// Trait for interacting with the JSON-RPC API
|
||||
pub trait JsonRpcClient {
|
||||
/// Get the root hash for a specific batch
|
||||
async fn get_root_hash(&self, batch_number: L1BatchNumber) -> error::Result<H256>;
|
||||
// TODO implement get_tee_proofs(batch_number, tee_type) once https://crates.io/crates/zksync_web3_decl crate is updated
|
||||
}
|
||||
|
||||
/// Client for interacting with the main node
|
||||
pub struct MainNodeClient(NodeClient<L2>);
|
||||
|
||||
impl MainNodeClient {
|
||||
/// Create a new client for the main node
|
||||
pub fn new(rpc_url: Url, chain_id: u64) -> error::Result<Self> {
|
||||
let chain_id = L2ChainId::try_from(chain_id)
|
||||
.map_err(|e| error::Error::Internal(format!("Invalid chain ID: {}", e)))?;
|
||||
|
||||
let node_client = NodeClient::http(rpc_url.into())
|
||||
.map_err(|e| {
|
||||
error::Error::Internal(format!("Failed to create JSON-RPC client: {}", e))
|
||||
})?
|
||||
.for_network(chain_id.into())
|
||||
.build();
|
||||
|
||||
Ok(MainNodeClient(node_client))
|
||||
}
|
||||
}
|
||||
|
||||
impl JsonRpcClient for MainNodeClient {
|
||||
async fn get_root_hash(&self, batch_number: L1BatchNumber) -> error::Result<H256> {
|
||||
let batch_details = self
|
||||
.0
|
||||
.get_l1_batch_details(batch_number)
|
||||
.rpc_context("get_l1_batch_details")
|
||||
.await
|
||||
.map_err(|e| error::Error::JsonRpc(format!("Failed to get batch details: {}", e)))?
|
||||
.ok_or_else(|| {
|
||||
error::Error::JsonRpc(format!("No details found for batch #{}", batch_number))
|
||||
})?;
|
||||
|
||||
batch_details.base.root_hash.ok_or_else(|| {
|
||||
error::Error::JsonRpc(format!("No root hash found for batch #{}", batch_number))
|
||||
})
|
||||
}
|
||||
}
|
12
bin/verify-era-proof-attestation/src/client/mod.rs
Normal file
12
bin/verify-era-proof-attestation/src/client/mod.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023-2025 Matter Labs
|
||||
|
||||
//! Client modules for external API communication
|
||||
|
||||
mod http;
|
||||
mod json_rpc;
|
||||
mod retry;
|
||||
|
||||
pub use http::HttpClient;
|
||||
pub use json_rpc::{JsonRpcClient, MainNodeClient};
|
||||
pub use retry::{RetryConfig, RetryHelper};
|
107
bin/verify-era-proof-attestation/src/client/retry.rs
Normal file
107
bin/verify-era-proof-attestation/src/client/retry.rs
Normal file
|
@ -0,0 +1,107 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2023-2025 Matter Labs
|
||||
|
||||
//! Retry mechanism for handling transient failures
|
||||
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
|
||||
use crate::{
|
||||
core::{DEFAULT_RETRY_DELAY_MS, MAX_PROOF_FETCH_RETRIES},
|
||||
error::{Error, Result},
|
||||
};
|
||||
|
||||
/// Configuration for retry behavior
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RetryConfig {
|
||||
/// Maximum number of retry attempts
|
||||
pub max_attempts: u32,
|
||||
/// Delay between retry attempts
|
||||
pub delay: Duration,
|
||||
/// Whether to use exponential backoff
|
||||
pub use_exponential_backoff: bool,
|
||||
}
|
||||
|
||||
impl Default for RetryConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
max_attempts: MAX_PROOF_FETCH_RETRIES,
|
||||
delay: Duration::from_millis(DEFAULT_RETRY_DELAY_MS),
|
||||
use_exponential_backoff: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper for executing operations with retries
|
||||
pub struct RetryHelper {
|
||||
config: RetryConfig,
|
||||
}
|
||||
|
||||
impl RetryHelper {
|
||||
/// Create a new retry helper with the given configuration
|
||||
pub fn new(config: RetryConfig) -> Self {
|
||||
Self { config }
|
||||
}
|
||||
|
||||
/// Execute an operation with retries
|
||||
pub async fn execute<T, F, Fut>(&self, operation_name: &str, operation: F) -> Result<T>
|
||||
where
|
||||
F: Fn() -> Fut,
|
||||
Fut: std::future::Future<Output = Result<T>>,
|
||||
{
|
||||
let mut attempt = 0;
|
||||
let mut last_error;
|
||||
|
||||
loop {
|
||||
attempt += 1;
|
||||
tracing::debug!(
|
||||
"Executing operation '{}' (attempt {}/{})",
|
||||
operation_name,
|
||||
attempt,
|
||||
self.config.max_attempts
|
||||
);
|
||||
|
||||
match operation().await {
|
||||
Ok(result) => {
|
||||
tracing::debug!(
|
||||
"Operation '{}' succeeded on attempt {}",
|
||||
operation_name,
|
||||
attempt
|
||||
);
|
||||
return Ok(result);
|
||||
}
|
||||
Err(Error::Interrupted) => return Err(Error::Interrupted),
|
||||
Err(e) => {
|
||||
last_error = e;
|
||||
|
||||
if attempt >= self.config.max_attempts {
|
||||
tracing::warn!(
|
||||
"Operation '{}' failed after {} attempts. Giving up.",
|
||||
operation_name,
|
||||
attempt
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
let delay = if self.config.use_exponential_backoff {
|
||||
self.config.delay.mul_f32(2.0_f32.powi(attempt as i32 - 1))
|
||||
} else {
|
||||
self.config.delay
|
||||
};
|
||||
|
||||
tracing::warn!(
|
||||
"Operation '{}' failed on attempt {}: {}. Retrying in {:?}...",
|
||||
operation_name,
|
||||
attempt,
|
||||
last_error,
|
||||
delay
|
||||
);
|
||||
|
||||
sleep(delay).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(last_error)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue