Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion anchor/common/ssv_types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ pub use cluster::{Cluster, ClusterId, ClusterMember, ValidatorIndex, ValidatorMe
pub use committee::{CommitteeId, CommitteeInfo};
pub use operator::{Operator, OperatorId};
pub use share::Share;
pub use util::parse_rsa;
pub use util::{RsaParseError, parse_rsa};
mod cluster;
mod committee;
pub mod consensus;
Expand Down
8 changes: 6 additions & 2 deletions anchor/common/ssv_types/src/operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use openssl::{pkey::Public, rsa::Rsa};
use ssz_derive::{Decode, Encode};
use types::Address;

use crate::util::parse_rsa;
use crate::{RsaParseError, util::parse_rsa};

/// Unique identifier for an Operator.
#[derive(
Expand Down Expand Up @@ -41,7 +41,11 @@ pub struct Operator {

impl Operator {
/// Creates a new operator from its OperatorId and PEM-encoded public key string
pub fn new(pem_data: &str, operator_id: OperatorId, owner: Address) -> Result<Self, String> {
pub fn new(
pem_data: &str,
operator_id: OperatorId,
owner: Address,
) -> Result<Self, RsaParseError> {
let rsa_pubkey = parse_rsa(pem_data)?;
Ok(Self::new_with_pubkey(rsa_pubkey, operator_id, owner))
}
Expand Down
26 changes: 18 additions & 8 deletions anchor/common/ssv_types/src/util.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
use base64::prelude::*;
use openssl::{pkey::Public, rsa::Rsa};
use thiserror::Error;

/// Errors that can occur during RSA key parsing
#[derive(Error, Debug, Clone)]
pub enum RsaParseError {
#[error("Unable to decode base64 PEM data: {0}")]
Base64Decode(#[from] base64::DecodeError),

#[error("Unable to convert decoded PEM data into a string: {0}")]
Utf8Conversion(#[from] std::string::FromUtf8Error),

#[error("Failed to parse RSA public key: {0}")]
RsaParsing(#[from] openssl::error::ErrorStack),
}

// Parse from a RSA public key string into the associated RSA representation
pub fn parse_rsa(pem_data: &str) -> Result<Rsa<Public>, String> {
pub fn parse_rsa(pem_data: &str) -> Result<Rsa<Public>, RsaParseError> {
// First decode the base64 data
let pem_decoded = BASE64_STANDARD
.decode(pem_data)
.map_err(|e| format!("Unable to decode base64 pem data: {e}"))?;
let pem_decoded = BASE64_STANDARD.decode(pem_data)?;

// Convert the decoded data to a string
let mut pem_string = String::from_utf8(pem_decoded)
.map_err(|e| format!("Unable to convert decoded pem data into a string: {e}"))?;
let mut pem_string = String::from_utf8(pem_decoded)?;

// Fix the header - replace PKCS1 header with PKCS8 header
pem_string = pem_string
Expand All @@ -21,8 +32,7 @@ pub fn parse_rsa(pem_data: &str) -> Result<Rsa<Public>, String> {
.replace("-----END RSA PUBLIC KEY-----", "-----END PUBLIC KEY-----");

// Parse the PEM string into an RSA public key using PKCS8 format
let rsa_pubkey = Rsa::public_key_from_pem(pem_string.as_bytes())
.map_err(|e| format!("Failed to parse RSA public key: {e}"))?;
let rsa_pubkey = Rsa::public_key_from_pem(pem_string.as_bytes())?;

Ok(rsa_pubkey)
}
1 change: 1 addition & 0 deletions anchor/eth/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ slot_clock = { workspace = true }
ssv_network_config = { workspace = true }
ssv_types = { workspace = true }
task_executor = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true }
tower = "0.5.2"
tracing = { workspace = true }
Expand Down
113 changes: 107 additions & 6 deletions anchor/eth/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,121 @@
use std::fmt::Display;
use thiserror::Error;

use crate::util::ShareParseError;

// Custom execution integration layer errors
#[derive(Debug)]
#[derive(Debug, Error)]
pub enum ExecutionError {
#[error("Sync error: {0}")]
SyncError(String),
InvalidEvent(String),

#[error("Invalid event for operator {operator_id:?} (owner: {owner:?}): {message}")]
InvalidOperatorEvent {
operator_id: ssv_types::OperatorId,
owner: alloy::primitives::Address,
message: String,
Copy link
Member

Choose a reason for hiding this comment

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

Having a message: String field in here feels like an anti pattern. I'd rather have a non Optional sub error for context

#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
Comment on lines +16 to +17
Copy link
Member

Choose a reason for hiding this comment

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

This is kind of emulating anyhow's context, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think anyhow's context is a message describing what we were trying to do when the function called failed. The source is the error that the function called returned.

},

#[error(
"Invalid event for validator {validator_pubkey:?} (cluster: {cluster_id:?}): {message}"
)]
InvalidValidatorEvent {
validator_pubkey: Option<String>,
cluster_id: Option<ssv_types::ClusterId>,
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},

#[error("Invalid event for cluster {cluster_id:?} (owner: {owner:?}): {message}")]
InvalidClusterEvent {
cluster_id: Option<ssv_types::ClusterId>,
owner: Option<alloy::primitives::Address>,
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},

#[error("Invalid event: {message}")]
InvalidEvent {
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
},
Comment on lines +40 to +45
Copy link
Member

Choose a reason for hiding this comment

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

Instead of a variant per event type, maybe embed the event into this variant?


#[error("RPC error: {0}")]
RpcError(String),

#[error("WebSocket error: {0}")]
WsError(String),

#[error("Decode error: {0}")]
DecodeError(String),

#[error("Miscellaneous error: {0}")]
Misc(String),

#[error("Duplicate error: {0}")]
Duplicate(String),

#[error("Database error: {0}")]
Database(String),

#[error("Share parse error: {0}")]
ShareParseError(#[from] ShareParseError),
}

impl Display for ExecutionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
impl ExecutionError {
pub fn invalid_operator_event(
operator_id: ssv_types::OperatorId,
owner: alloy::primitives::Address,
message: impl Into<String>,
source: Option<Box<dyn std::error::Error + Send + Sync>>,
) -> Self {
Self::InvalidOperatorEvent {
operator_id,
owner,
message: message.into(),
source,
}
}

pub fn invalid_validator_event(
validator_pubkey: Option<String>,
cluster_id: Option<ssv_types::ClusterId>,
message: impl Into<String>,
source: Option<Box<dyn std::error::Error + Send + Sync>>,
) -> Self {
Self::InvalidValidatorEvent {
validator_pubkey,
cluster_id,
message: message.into(),
source,
}
}

pub fn invalid_cluster_event(
cluster_id: Option<ssv_types::ClusterId>,
owner: Option<alloy::primitives::Address>,
message: impl Into<String>,
source: Option<Box<dyn std::error::Error + Send + Sync>>,
) -> Self {
Self::InvalidClusterEvent {
cluster_id,
owner,
message: message.into(),
source,
}
}

pub fn invalid_event(
message: impl Into<String>,
source: Option<Box<dyn std::error::Error + Send + Sync>>,
) -> Self {
Self::InvalidEvent {
message: message.into(),
source,
}
}
}
Loading