diff --git a/Cargo.toml b/Cargo.toml index b30dc5c..df2254c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,3 @@ async-trait = "0.1" hex = "0.4" secrecy = "0.8.0" byteorder = "1.5.0" - - - -anyhow = "1" #TODO: Remove diff --git a/src/blob_info.rs b/src/blob_info.rs index 658e3be..38ab97d 100644 --- a/src/blob_info.rs +++ b/src/blob_info.rs @@ -1,7 +1,7 @@ -use std::fmt; - use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream}; +use crate::errors::ConversionError; + use super::{ common::G1Commitment as DisperserG1Commitment, disperser::{ @@ -12,37 +12,12 @@ use super::{ }, }; -#[derive(Debug)] -pub enum ConversionError { - NotPresentError, -} - -impl fmt::Display for ConversionError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - ConversionError::NotPresentError => write!(f, "Failed to convert BlobInfo"), - } - } -} - #[derive(Debug, PartialEq, Clone)] pub struct G1Commitment { pub x: Vec, pub y: Vec, } -impl G1Commitment { - pub fn to_bytes(&self) -> Vec { - let mut bytes = vec![]; - bytes.extend(&self.x.len().to_be_bytes()); - bytes.extend(&self.x); - bytes.extend(&self.y.len().to_be_bytes()); - bytes.extend(&self.y); - - bytes - } -} - impl Decodable for G1Commitment { fn decode(rlp: &Rlp) -> Result { let x: Vec = rlp.val_at(0)?; // Decode first element as Vec @@ -77,18 +52,6 @@ pub struct BlobQuorumParam { pub chunk_length: u32, } -impl BlobQuorumParam { - pub fn to_bytes(&self) -> Vec { - let mut bytes = vec![]; - bytes.extend(&self.quorum_number.to_be_bytes()); - bytes.extend(&self.adversary_threshold_percentage.to_be_bytes()); - bytes.extend(&self.confirmation_threshold_percentage.to_be_bytes()); - bytes.extend(&self.chunk_length.to_be_bytes()); - - bytes - } -} - impl Decodable for BlobQuorumParam { fn decode(rlp: &Rlp) -> Result { Ok(BlobQuorumParam { @@ -128,21 +91,6 @@ pub struct BlobHeader { pub blob_quorum_params: Vec, } -impl BlobHeader { - pub fn to_bytes(&self) -> Vec { - let mut bytes = vec![]; - bytes.extend(self.commitment.to_bytes()); - bytes.extend(&self.data_length.to_be_bytes()); - bytes.extend(&self.blob_quorum_params.len().to_be_bytes()); - - for quorum in &self.blob_quorum_params { - bytes.extend(quorum.to_bytes()); - } - - bytes - } -} - impl Decodable for BlobHeader { fn decode(rlp: &Rlp) -> Result { let commitment: G1Commitment = rlp.val_at(0)?; @@ -170,7 +118,7 @@ impl TryFrom for BlobHeader { type Error = ConversionError; fn try_from(value: DisperserBlobHeader) -> Result { if value.commitment.is_none() { - return Err(ConversionError::NotPresentError); + return Err(ConversionError::NotPresent("BlobHeader".to_string())); } let blob_quorum_params: Vec = value .blob_quorum_params @@ -193,21 +141,6 @@ pub struct BatchHeader { pub reference_block_number: u32, } -impl BatchHeader { - pub fn to_bytes(&self) -> Vec { - let mut bytes = vec![]; - bytes.extend(&self.batch_root.len().to_be_bytes()); - bytes.extend(&self.batch_root); - bytes.extend(&self.quorum_numbers.len().to_be_bytes()); - bytes.extend(&self.quorum_numbers); - bytes.extend(&self.quorum_signed_percentages.len().to_be_bytes()); - bytes.extend(&self.quorum_signed_percentages); - bytes.extend(&self.reference_block_number.to_be_bytes()); - - bytes - } -} - impl Decodable for BatchHeader { fn decode(rlp: &Rlp) -> Result { Ok(BatchHeader { @@ -249,17 +182,6 @@ pub struct BatchMetadata { pub batch_header_hash: Vec, } -impl BatchMetadata { - pub fn to_bytes(&self) -> Vec { - let mut bytes = vec![]; - bytes.extend(self.batch_header.to_bytes()); - bytes.extend(&self.signatory_record_hash); - bytes.extend(&self.confirmation_block_number.to_be_bytes()); - - bytes - } -} - impl Decodable for BatchMetadata { fn decode(rlp: &Rlp) -> Result { let batch_header: BatchHeader = rlp.val_at(0)?; @@ -289,7 +211,7 @@ impl TryFrom for BatchMetadata { type Error = ConversionError; fn try_from(value: DisperserBatchMetadata) -> Result { if value.batch_header.is_none() { - return Err(ConversionError::NotPresentError); + return Err(ConversionError::NotPresent("BatchMetadata".to_string())); } Ok(Self { batch_header: BatchHeader::from(value.batch_header.unwrap()), @@ -310,21 +232,6 @@ pub struct BlobVerificationProof { pub quorum_indexes: Vec, } -impl BlobVerificationProof { - pub fn to_bytes(&self) -> Vec { - let mut bytes = vec![]; - bytes.extend(&self.batch_id.to_be_bytes()); - bytes.extend(&self.blob_index.to_be_bytes()); - bytes.extend(self.batch_medatada.to_bytes()); - bytes.extend(&self.inclusion_proof.len().to_be_bytes()); - bytes.extend(&self.inclusion_proof); - bytes.extend(&self.quorum_indexes.len().to_be_bytes()); - bytes.extend(&self.quorum_indexes); - - bytes - } -} - impl Decodable for BlobVerificationProof { fn decode(rlp: &Rlp) -> Result { Ok(BlobVerificationProof { @@ -352,7 +259,9 @@ impl TryFrom for BlobVerificationProof { type Error = ConversionError; fn try_from(value: DisperserBlobVerificationProof) -> Result { if value.batch_metadata.is_none() { - return Err(ConversionError::NotPresentError); + return Err(ConversionError::NotPresent( + "BlobVerificationProof".to_string(), + )); } Ok(Self { batch_id: value.batch_id, @@ -370,18 +279,6 @@ pub struct BlobInfo { pub blob_verification_proof: BlobVerificationProof, } -impl BlobInfo { - pub fn to_bytes(&self) -> Vec { - let mut bytes = vec![]; - let blob_header_bytes = self.blob_header.to_bytes(); - bytes.extend(blob_header_bytes.len().to_be_bytes()); - bytes.extend(blob_header_bytes); - let blob_verification_proof_bytes = self.blob_verification_proof.to_bytes(); - bytes.extend(blob_verification_proof_bytes); - bytes - } -} - impl Decodable for BlobInfo { fn decode(rlp: &Rlp) -> Result { let blob_header: BlobHeader = rlp.val_at(0)?; @@ -406,7 +303,7 @@ impl TryFrom for BlobInfo { type Error = ConversionError; fn try_from(value: DisperserBlobInfo) -> Result { if value.blob_header.is_none() || value.blob_verification_proof.is_none() { - return Err(ConversionError::NotPresentError); + return Err(ConversionError::NotPresent("BlobInfo".to_string())); } Ok(Self { blob_header: BlobHeader::try_from(value.blob_header.unwrap())?, diff --git a/src/client.rs b/src/client.rs index 6147d73..9ed7daf 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,3 +1,5 @@ +use crate::errors::{CommunicationError, ConfigError, EigenClientError}; + use super::{ blob_info::BlobInfo, config::{EigenConfig, EigenSecrets}, @@ -14,9 +16,9 @@ pub struct EigenClient { } impl EigenClient { - pub async fn new(config: EigenConfig, secrets: EigenSecrets) -> anyhow::Result { + pub async fn new(config: EigenConfig, secrets: EigenSecrets) -> Result { let private_key = SecretKey::from_str(secrets.private_key.0.expose_secret().as_str()) - .map_err(|e| anyhow::anyhow!("Failed to parse private key: {}", e))?; + .map_err(ConfigError::Secp)?; let client = RawEigenClient::new(private_key, config).await?; Ok(Self { @@ -24,26 +26,27 @@ impl EigenClient { }) } - pub async fn get_commitment(&self, blob_id: &str) -> anyhow::Result { + pub async fn get_commitment(&self, blob_id: &str) -> Result { let blob_info = self.client.get_inclusion_data(blob_id).await?; Ok(blob_info) } - async fn dispatch_blob(&self, data: Vec) -> anyhow::Result { + pub async fn dispatch_blob(&self, data: Vec) -> Result { let blob_id = self.client.dispatch_blob(data).await?; Ok(blob_id) } - async fn get_inclusion_data(&self, blob_id: &str) -> anyhow::Result> { + pub async fn get_inclusion_data(&self, blob_id: &str) -> Result, EigenClientError> { let blob_info = self.get_commitment(blob_id).await?; - let rlp_encoded_bytes = hex::decode(blob_info)?; - let blob_info: BlobInfo = rlp::decode(&rlp_encoded_bytes)?; + let rlp_encoded_bytes = hex::decode(blob_info).map_err(CommunicationError::Hex)?; + let blob_info: BlobInfo = + rlp::decode(&rlp_encoded_bytes).map_err(CommunicationError::Rlp)?; let inclusion_data = blob_info.blob_verification_proof.inclusion_proof; Ok(inclusion_data) } - fn blob_size_limit(&self) -> Option { + pub fn blob_size_limit(&self) -> Option { Some(RawEigenClient::blob_size_limit()) } } @@ -61,7 +64,10 @@ mod tests { use crate::blob_info::BlobInfo; impl EigenClient { - pub async fn get_blob_data(&self, blob_id: &str) -> anyhow::Result>> { + pub async fn get_blob_data( + &self, + blob_id: &str, + ) -> Result>, EigenClientError> { self.client.get_blob_data(blob_id).await } } diff --git a/src/config.rs b/src/config.rs index 752ed0e..9abd458 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,8 @@ use secrecy::{ExposeSecret, Secret}; use std::str::FromStr; +use crate::errors::{ConfigError, EigenClientError}; + #[derive(Clone, Debug, PartialEq)] pub enum SRSPointsSource { Path(String), @@ -56,9 +58,9 @@ impl PartialEq for PrivateKey { } impl FromStr for PrivateKey { - type Err = anyhow::Error; + type Err = EigenClientError; fn from_str(s: &str) -> Result { - Ok(PrivateKey(s.parse()?)) + Ok(PrivateKey(s.parse().map_err(|_| ConfigError::PrivateKey)?)) } } diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..9716239 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,114 @@ +use tokio::sync::mpsc::error::SendError; +use tonic::{transport::Error as TonicError, Status}; + +use crate::{disperser, eth_client::RpcErrorResponse}; + +#[derive(Debug, thiserror::Error)] +pub enum EigenClientError { + #[error(transparent)] + EthClient(#[from] EthClientError), + #[error(transparent)] + Verification(#[from] VerificationError), + #[error(transparent)] + Communication(#[from] CommunicationError), + #[error(transparent)] + BlobStatus(#[from] BlobStatusError), + #[error(transparent)] + Conversion(#[from] ConversionError), + #[error(transparent)] + Config(#[from] ConfigError), +} + +#[derive(Debug, thiserror::Error)] +pub enum ConfigError { + #[error("Private Key Error")] + PrivateKey, + #[error(transparent)] + Secp(#[from] secp256k1::Error), + #[error(transparent)] + Tonic(#[from] TonicError), +} + +#[derive(Debug, thiserror::Error)] +pub enum CommunicationError { + #[error("No response from server")] + NoResponseFromServer, + #[error("No payload in response")] + NoPayloadInResponse, + #[error("Failed to get blob data")] + FailedToGetBlobData, + #[error("Failed to send DisperseBlobRequest: {0}")] + DisperseBlob(SendError), + #[error("Failed to send AuthenticationData: {0}")] + AuthenticationData(SendError), + #[error("Error from server: {0}")] + ErrorFromServer(String), + #[error(transparent)] + Secp(#[from] secp256k1::Error), + #[error(transparent)] + Hex(#[from] hex::FromHexError), + #[error(transparent)] + Rlp(#[from] rlp::DecoderError), +} + +#[derive(Debug, thiserror::Error)] +pub enum BlobStatusError { + #[error("Blob still processing")] + BlobStillProcessing, + #[error("Blob dispatched failed")] + BlobDispatchedFailed, + #[error("Insufficient signatures")] + InsufficientSignatures, + #[error("No blob header in response")] + NoBlobHeaderInResponse, + #[error("Received unknown blob status")] + ReceivedUnknownBlobStatus, + #[error(transparent)] + Prost(#[from] prost::DecodeError), + #[error(transparent)] + Status(#[from] Status), +} + +#[derive(Debug, thiserror::Error)] +pub enum ConversionError { + #[error("Failed to convert {0}")] + NotPresent(String), +} + +#[derive(Debug, thiserror::Error)] +pub enum EthClientError { + #[error(transparent)] + HTTPClient(#[from] reqwest::Error), + #[error(transparent)] + SerdeJSON(#[from] serde_json::Error), + #[error("RPC: {0}")] + RPC(RpcErrorResponse), +} + +#[derive(Debug, thiserror::Error)] +pub enum VerificationError { + #[error("Service Manager Error: {0}")] + ServiceManager(String), + #[error("Kzg Error: {0}")] + Kzg(String), + #[error("Wrong proof")] + WrongProof, + #[error("Different commitments")] + DifferentCommitments, + #[error("Different roots")] + DifferentRoots, + #[error("Empty hashes")] + EmptyHash, + #[error("Different hashes")] + DifferentHashes, + #[error("Wrong quorum params")] + WrongQuorumParams, + #[error("Quorum not confirmed")] + QuorumNotConfirmed, + #[error("Commitment not on curve")] + CommitmentNotOnCurve, + #[error("Commitment not on correct subgroup")] + CommitmentNotOnCorrectSubgroup, + #[error("Link Error: {0}")] + Link(String), +} diff --git a/src/eth_client.rs b/src/eth_client.rs index be96bc7..2f91dd0 100644 --- a/src/eth_client.rs +++ b/src/eth_client.rs @@ -5,17 +5,7 @@ use reqwest::Client; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; -#[derive(Debug, thiserror::Error)] -pub enum EthClientError { - #[error("Failed to serialize request body: {0}")] - FailedToSerializeRequestBody(String), - #[error("reqwest error: {0}")] - ReqwestError(#[from] reqwest::Error), - #[error("{0}")] - SerdeJSONError(#[from] serde_json::Error), - #[error("{0}")] - RPCError(String), -} +use crate::errors::EthClientError; #[derive(Debug, Serialize, Deserialize)] #[serde(untagged)] @@ -46,6 +36,12 @@ pub struct RpcErrorResponse { pub error: RpcErrorMetadata, } +impl std::fmt::Display for RpcErrorResponse { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "RpcErrorResponse: {:?}", self) + } +} + #[derive(Deserialize, Debug)] #[serde(untagged)] pub enum RpcResponse { @@ -79,9 +75,7 @@ impl EthClient { self.client .post(&self.url) .header("content-type", "application/json") - .body(serde_json::ser::to_string(&request).map_err(|error| { - EthClientError::FailedToSerializeRequestBody(format!("{error}: {request:?}")) - })?) + .body(serde_json::ser::to_string(&request).map_err(EthClientError::SerdeJSON)?) .send() .await? .json::() @@ -99,11 +93,9 @@ impl EthClient { match self.send_request(request).await { Ok(RpcResponse::Success(result)) => { - serde_json::from_value(result.result).map_err(EthClientError::SerdeJSONError) - } - Ok(RpcResponse::Error(error_response)) => { - Err(EthClientError::RPCError(error_response.error.message)) + serde_json::from_value(result.result).map_err(EthClientError::SerdeJSON) } + Ok(RpcResponse::Error(error_response)) => Err(EthClientError::RPC(error_response)), Err(error) => Err(error), } } @@ -134,11 +126,9 @@ impl EthClient { match self.send_request(request).await { Ok(RpcResponse::Success(result)) => { - serde_json::from_value(result.result).map_err(EthClientError::SerdeJSONError) - } - Ok(RpcResponse::Error(error_response)) => { - Err(EthClientError::RPCError(error_response.error.message)) + serde_json::from_value(result.result).map_err(EthClientError::SerdeJSON) } + Ok(RpcResponse::Error(error_response)) => Err(EthClientError::RPC(error_response)), Err(error) => Err(error), } } diff --git a/src/lib.rs b/src/lib.rs index be4edd5..27211f7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,12 +5,13 @@ pub const BATCH_ID_TO_METADATA_HASH_FUNCTION_SELECTOR: [u8; 4] = [236, 203, 191, pub const QUORUM_ADVERSARY_THRESHOLD_PERCENTAGES_FUNCTION_SELECTOR: [u8; 4] = [134, 135, 254, 174]; pub const QUORUM_NUMBERS_REQUIRED_FUNCTION_SELECTOR: [u8; 4] = [225, 82, 52, 255]; -mod blob_info; -mod client; -mod config; -mod eth_client; -mod sdk; -mod verifier; +pub mod blob_info; +pub mod client; +pub mod config; +pub mod errors; +pub mod eth_client; +pub mod sdk; +pub mod verifier; pub use self::client::EigenClient; diff --git a/src/sdk.rs b/src/sdk.rs index 314e236..35bcb3a 100644 --- a/src/sdk.rs +++ b/src/sdk.rs @@ -15,6 +15,7 @@ use crate::{ disperser_client::DisperserClient, AuthenticatedReply, BlobAuthHeader, }, + errors::{BlobStatusError, CommunicationError, ConfigError, EigenClientError}, }; use backon::{ConstantBuilder, Retryable}; use byteorder::{BigEndian, ByteOrder}; @@ -44,10 +45,19 @@ pub(crate) const AVG_BLOCK_TIME: u64 = 12; impl RawEigenClient { const BLOB_SIZE_LIMIT: usize = 1024 * 1024 * 2; // 2 MB - pub async fn new(private_key: SecretKey, config: EigenConfig) -> anyhow::Result { - let endpoint = - Endpoint::from_str(config.disperser_rpc.as_str())?.tls_config(ClientTlsConfig::new())?; - let client = Arc::new(Mutex::new(DisperserClient::connect(endpoint).await?)); + pub async fn new( + private_key: SecretKey, + config: EigenConfig, + ) -> Result { + let endpoint = Endpoint::from_str(config.disperser_rpc.as_str()) + .map_err(ConfigError::Tonic)? + .tls_config(ClientTlsConfig::new()) + .map_err(ConfigError::Tonic)?; + let client = Arc::new(Mutex::new( + DisperserClient::connect(endpoint) + .await + .map_err(ConfigError::Tonic)?, + )); let verifier_config = VerifierConfig { svc_manager_addr: config.eigenda_svc_manager_address.clone(), @@ -58,9 +68,7 @@ impl RawEigenClient { }; let eth_client = eth_client::EthClient::new(&config.eth_rpc); - let verifier = Verifier::new(verifier_config, eth_client) - .await - .map_err(|e| anyhow::anyhow!(format!("Failed to create verifier {:?}", e)))?; + let verifier = Verifier::new(verifier_config, eth_client).await?; Ok(RawEigenClient { client, private_key, @@ -73,7 +81,10 @@ impl RawEigenClient { Self::BLOB_SIZE_LIMIT } - async fn dispatch_blob_non_authenticated(&self, data: Vec) -> anyhow::Result { + async fn dispatch_blob_non_authenticated( + &self, + data: Vec, + ) -> Result { let padded_data = convert_by_padding_empty_byte(&data); let request = disperser::DisperseBlobRequest { data: padded_data, @@ -86,7 +97,8 @@ impl RawEigenClient { .lock() .await .disperse_blob(request) - .await? + .await + .map_err(BlobStatusError::Status)? .into_inner(); Ok(hex::encode(disperse_reply.request_id)) @@ -96,7 +108,7 @@ impl RawEigenClient { &self, blob_info: BlobInfo, disperse_elapsed: Duration, - ) -> anyhow::Result<()> { + ) -> Result<(), EigenClientError> { (|| async { self.verifier.verify_certificate(blob_info.clone()).await }) .retry( &ConstantBuilder::default() @@ -107,11 +119,12 @@ impl RawEigenClient { as usize, ), ) - .await - .map_err(|_| anyhow::anyhow!("Failed to verify certificate")) + .await?; + + Ok(()) } - async fn dispatch_blob_authenticated(&self, data: Vec) -> anyhow::Result { + async fn dispatch_blob_authenticated(&self, data: Vec) -> Result { let (tx, rx) = mpsc::unbounded_channel(); // 1. send DisperseBlobRequest @@ -125,7 +138,8 @@ impl RawEigenClient { .lock() .await .disperse_blob_authenticated(UnboundedReceiverStream::new(rx)) - .await?; + .await + .map_err(BlobStatusError::Status)?; let response_stream = response_stream.get_mut(); // 2. receive BlobAuthHeader @@ -138,43 +152,42 @@ impl RawEigenClient { let reply = response_stream .next() .await - .ok_or_else(|| anyhow::anyhow!("No response from server"))? + .ok_or(CommunicationError::NoResponseFromServer)? .unwrap() .payload - .ok_or_else(|| anyhow::anyhow!("No payload in response"))?; + .ok_or(CommunicationError::NoPayloadInResponse)?; let disperser::authenticated_reply::Payload::DisperseReply(disperse_reply) = reply else { - return Err(anyhow::anyhow!("Unexpected response from server")); + return Err(CommunicationError::ErrorFromServer( + "Unexpected response".to_string(), + ))?; }; Ok(hex::encode(disperse_reply.request_id)) } - pub async fn get_inclusion_data(&self, blob_id: &str) -> anyhow::Result { + pub async fn get_inclusion_data(&self, blob_id: &str) -> Result { let disperse_time = Instant::now(); let blob_info = self.await_for_inclusion(blob_id.to_string()).await?; - let blob_info = blob_info::BlobInfo::try_from(blob_info) - .map_err(|e| anyhow::anyhow!("Failed to convert blob info: {}", e))?; + let blob_info = blob_info::BlobInfo::try_from(blob_info)?; let disperse_elapsed = Instant::now() - disperse_time; let data = self .get_blob_data(&hex::encode(rlp::encode(&blob_info))) .await?; if data.is_none() { - return Err(anyhow::anyhow!("Failed to get blob data")); + return Err(CommunicationError::FailedToGetBlobData)?; } self.verifier - .verify_commitment(blob_info.blob_header.commitment.clone(), data.unwrap()) - .map_err(|_| anyhow::anyhow!("Failed to verify commitment"))?; + .verify_commitment(blob_info.blob_header.commitment.clone(), data.unwrap())?; self.perform_verification(blob_info.clone(), disperse_elapsed) .await?; - let verification_proof = blob_info.blob_verification_proof.clone(); Ok(hex::encode(rlp::encode(&blob_info))) } - pub async fn dispatch_blob(&self, data: Vec) -> anyhow::Result { + pub async fn dispatch_blob(&self, data: Vec) -> Result { if self.config.authenticated { self.dispatch_blob_authenticated(data).await } else { @@ -186,7 +199,7 @@ impl RawEigenClient { &self, data: Vec, tx: &mpsc::UnboundedSender, - ) -> anyhow::Result<()> { + ) -> Result<(), EigenClientError> { let req = disperser::AuthenticatedRequest { payload: Some(DisperseRequest(disperser::DisperseBlobRequest { data, @@ -195,8 +208,8 @@ impl RawEigenClient { })), }; - tx.send(req) - .map_err(|e| anyhow::anyhow!("Failed to send DisperseBlobRequest: {}", e)) + tx.send(req).map_err(CommunicationError::DisperseBlob)?; + Ok(()) } fn keccak256(&self, input: &[u8]) -> [u8; 32] { @@ -211,14 +224,14 @@ impl RawEigenClient { &self, blob_auth_header: BlobAuthHeader, tx: &mpsc::UnboundedSender, - ) -> anyhow::Result<()> { + ) -> Result<(), EigenClientError> { // TODO: replace challenge_parameter with actual auth header when it is available let mut buf = [0u8; 4]; BigEndian::write_u32(&mut buf, blob_auth_header.challenge_parameter); let digest = self.keccak256(&buf); let signature: RecoverableSignature = secp256k1::Secp256k1::signing_only() .sign_ecdsa_recoverable( - &secp256k1::Message::from_slice(&digest[..])?, + &secp256k1::Message::from_slice(&digest[..]).map_err(CommunicationError::Secp)?, &self.private_key, ); let (recovery_id, sig) = signature.serialize_compact(); @@ -234,36 +247,42 @@ impl RawEigenClient { }; tx.send(req) - .map_err(|e| anyhow::anyhow!("Failed to send AuthenticationData: {}", e)) + .map_err(CommunicationError::AuthenticationData)?; + Ok(()) } async fn receive_blob_auth_header( &self, response_stream: &mut Streaming, - ) -> anyhow::Result { + ) -> Result { let reply = response_stream .next() .await - .ok_or_else(|| anyhow::anyhow!("No response from server"))?; + .ok_or(CommunicationError::NoResponseFromServer)?; let Ok(reply) = reply else { - return Err(anyhow::anyhow!("Err from server: {:?}", reply)); + return Err(CommunicationError::ErrorFromServer(format!("{:?}", reply)))?; }; let reply = reply .payload - .ok_or_else(|| anyhow::anyhow!("No payload in response"))?; + .ok_or(CommunicationError::NoPayloadInResponse)?; if let disperser::authenticated_reply::Payload::BlobAuthHeader(blob_auth_header) = reply { Ok(blob_auth_header) } else { - Err(anyhow::anyhow!("Unexpected response from server")) + Err(CommunicationError::ErrorFromServer( + "Unexpected Response".to_string(), + ))? } } - async fn await_for_inclusion(&self, request_id: String) -> anyhow::Result { + async fn await_for_inclusion( + &self, + request_id: String, + ) -> Result { let polling_request = disperser::BlobStatusRequest { - request_id: hex::decode(request_id)?, + request_id: hex::decode(request_id).map_err(CommunicationError::Hex)?, }; let blob_info = (|| async { @@ -277,29 +296,29 @@ impl RawEigenClient { match disperser::BlobStatus::try_from(resp.status)? { disperser::BlobStatus::Processing | disperser::BlobStatus::Dispersing => { - Err(anyhow::anyhow!("Blob is still processing")) + Err(BlobStatusError::BlobStillProcessing) } - disperser::BlobStatus::Failed => Err(anyhow::anyhow!("Blob dispatch failed")), + disperser::BlobStatus::Failed => Err(BlobStatusError::BlobDispatchedFailed), disperser::BlobStatus::InsufficientSignatures => { - Err(anyhow::anyhow!("Insufficient signatures")) + Err(BlobStatusError::InsufficientSignatures) } disperser::BlobStatus::Confirmed => { if !self.config.wait_for_finalization { let blob_info = resp .info - .ok_or_else(|| anyhow::anyhow!("No blob header in response"))?; + .ok_or_else(|| BlobStatusError::NoBlobHeaderInResponse)?; return Ok(blob_info); } - Err(anyhow::anyhow!("Blob is still processing")) + Err(BlobStatusError::BlobStillProcessing) } disperser::BlobStatus::Finalized => { let blob_info = resp .info - .ok_or_else(|| anyhow::anyhow!("No blob header in response"))?; + .ok_or_else(|| BlobStatusError::NoBlobHeaderInResponse)?; Ok(blob_info) } - _ => Err(anyhow::anyhow!("Received unknown blob status")), + _ => Err(BlobStatusError::ReceivedUnknownBlobStatus), } }) .retry( @@ -310,15 +329,18 @@ impl RawEigenClient { as usize, ), ) - .when(|e| e.to_string().contains("Blob is still processing")) + .when(|e| matches!(e, BlobStatusError::BlobStillProcessing)) .await?; Ok(blob_info) } - pub async fn get_blob_data(&self, blob_info: &str) -> anyhow::Result>> { - let commit = hex::decode(blob_info)?; - let blob_info: BlobInfo = rlp::decode(&commit)?; + pub async fn get_blob_data( + &self, + blob_info: &str, + ) -> Result>, EigenClientError> { + let commit = hex::decode(blob_info).map_err(CommunicationError::Hex)?; + let blob_info: BlobInfo = rlp::decode(&commit).map_err(CommunicationError::Rlp)?; let blob_index = blob_info.blob_verification_proof.blob_index; let batch_header_hash = blob_info .blob_verification_proof @@ -332,11 +354,12 @@ impl RawEigenClient { batch_header_hash, blob_index, }) - .await? + .await + .map_err(BlobStatusError::Status)? .into_inner(); if get_response.data.is_empty() { - return Err(anyhow::anyhow!("Failed to get blob data")); + return Err(CommunicationError::FailedToGetBlobData)?; } let data = remove_empty_byte_from_padded_bytes(&get_response.data); diff --git a/src/verifier.rs b/src/verifier.rs index 9468d5e..c3609ba 100644 --- a/src/verifier.rs +++ b/src/verifier.rs @@ -7,31 +7,17 @@ use ethereum_types::{Address, U256}; use rust_kzg_bn254::{blob::Blob, kzg::Kzg, polynomial::PolynomialFormat}; use tiny_keccak::{Hasher, Keccak}; +use crate::errors::VerificationError; + use super::{ blob_info::{BatchHeader, BlobHeader, BlobInfo, G1Commitment}, config::SRSPointsSource, - eth_client::{EthClient, EthClientError}, + errors::EthClientError, + eth_client::EthClient, BATCH_ID_TO_METADATA_HASH_FUNCTION_SELECTOR, QUORUM_ADVERSARY_THRESHOLD_PERCENTAGES_FUNCTION_SELECTOR, QUORUM_NUMBERS_REQUIRED_FUNCTION_SELECTOR, }; - -#[derive(Debug)] -pub enum VerificationError { - ServiceManagerError, - KzgError, - WrongProof, - DifferentCommitments, - DifferentRoots, - EmptyHash, - DifferentHashes, - WrongQuorumParams, - QuorumNotConfirmed, - CommitmentNotOnCurve, - CommitmentNotOnCorrectSubgroup, - LinkError, -} - #[async_trait::async_trait] pub trait VerifierClient: Sync + Send + std::fmt::Debug { fn clone_boxed(&self) -> Box; @@ -102,26 +88,28 @@ impl Verifier { let url_g1 = format!("{}{}", link, "/g1.point"); let response = reqwest::get(url_g1) .await - .map_err(|_| VerificationError::LinkError)?; + .map_err(|e| VerificationError::Link(e.to_string()))?; let path = Path::new("./g1.point"); - let mut file = File::create(path).map_err(|_| VerificationError::LinkError)?; + let mut file = File::create(path).map_err(|e| VerificationError::Link(e.to_string()))?; let content = response .bytes() .await - .map_err(|_| VerificationError::LinkError)?; - copy(&mut content.as_ref(), &mut file).map_err(|_| VerificationError::LinkError)?; + .map_err(|e| VerificationError::Link(e.to_string()))?; + copy(&mut content.as_ref(), &mut file) + .map_err(|e| VerificationError::Link(e.to_string()))?; let url_g2 = format!("{}{}", link, "/g2.point.powerOf2"); let response = reqwest::get(url_g2) .await - .map_err(|_| VerificationError::LinkError)?; + .map_err(|e| VerificationError::Link(e.to_string()))?; let path = Path::new("./g2.point.powerOf2"); - let mut file = File::create(path).map_err(|_| VerificationError::LinkError)?; + let mut file = File::create(path).map_err(|e| VerificationError::Link(e.to_string()))?; let content = response .bytes() .await - .map_err(|_| VerificationError::LinkError)?; - copy(&mut content.as_ref(), &mut file).map_err(|_| VerificationError::LinkError)?; + .map_err(|e| VerificationError::Link(e.to_string()))?; + copy(&mut content.as_ref(), &mut file) + .map_err(|e| VerificationError::Link(e.to_string()))?; Ok(".".to_string()) } @@ -142,7 +130,7 @@ impl Verifier { srs_points_to_load, "".to_string(), ); - let kzg = kzg.map_err(|e| VerificationError::KzgError)?; + let kzg = kzg.map_err(|e| VerificationError::Kzg(e.to_string()))?; Ok(Self { kzg, @@ -156,7 +144,7 @@ impl Verifier { let blob = Blob::from_bytes_and_pad(&blob.to_vec()); self.kzg .blob_to_kzg_commitment(&blob, PolynomialFormat::InEvaluationForm) - .map_err(|_| VerificationError::KzgError) + .map_err(|e| VerificationError::Kzg(e.to_string())) } /// Compare the given commitment with the commitment generated with the blob @@ -332,15 +320,16 @@ impl Verifier { .eth_client .call( Address::from_str(&self.cfg.svc_manager_addr) - .map_err(|_| VerificationError::ServiceManagerError)?, + .map_err(|e| VerificationError::ServiceManager(e.to_string()))?, bytes::Bytes::copy_from_slice(&data), Some(context_block), ) .await - .map_err(|_| VerificationError::ServiceManagerError)?; + .map_err(|e| VerificationError::ServiceManager(e.to_string()))?; let res = res.trim_start_matches("0x"); - let expected_hash = hex::decode(res).map_err(|_| VerificationError::ServiceManagerError)?; + let expected_hash = + hex::decode(res).map_err(|e| VerificationError::ServiceManager(e.to_string()))?; if expected_hash == vec![0u8; 32] { return Err(VerificationError::EmptyHash); @@ -415,21 +404,21 @@ impl Verifier { .eth_client .call( Address::from_str(&self.cfg.svc_manager_addr) - .map_err(|_| VerificationError::ServiceManagerError)?, + .map_err(|e| VerificationError::ServiceManager(e.to_string()))?, bytes::Bytes::copy_from_slice(&data), None, ) .await - .map_err(|_| VerificationError::ServiceManagerError)?; + .map_err(|e| VerificationError::ServiceManager(e.to_string()))?; let res = res.trim_start_matches("0x"); let percentages_vec = - hex::decode(res).map_err(|_| VerificationError::ServiceManagerError)?; + hex::decode(res).map_err(|e| VerificationError::ServiceManager(e.to_string()))?; let percentages = self .decode_bytes(percentages_vec) - .map_err(|_| VerificationError::ServiceManagerError)?; + .map_err(|e| VerificationError::ServiceManager(e.to_string()))?; if percentages.len() > quorum_number as usize { return Ok(percentages[quorum_number as usize]); @@ -480,21 +469,21 @@ impl Verifier { .eth_client .call( Address::from_str(&self.cfg.svc_manager_addr) - .map_err(|_| VerificationError::ServiceManagerError)?, + .map_err(|e| VerificationError::ServiceManager(e.to_string()))?, bytes::Bytes::copy_from_slice(&data), None, ) .await - .map_err(|_| VerificationError::ServiceManagerError)?; + .map_err(|e| VerificationError::ServiceManager(e.to_string()))?; let res = res.trim_start_matches("0x"); let required_quorums_vec = - hex::decode(res).map_err(|_| VerificationError::ServiceManagerError)?; + hex::decode(res).map_err(|e| VerificationError::ServiceManager(e.to_string()))?; let required_quorums = self .decode_bytes(required_quorums_vec) - .map_err(|_| VerificationError::ServiceManagerError)?; + .map_err(|e| VerificationError::ServiceManager(e.to_string()))?; for quorum in required_quorums { if !confirmed_quorums.contains_key(&(quorum as u32)) {