From 5d1f4a634430cca723ad878695af32aa90c98b5e Mon Sep 17 00:00:00 2001 From: Noah Citron Date: Thu, 29 Sep 2022 19:35:43 -0400 Subject: [PATCH] refactor: better error handling (#63) * add custom errors to consensus * add BlockNotFoundError * better handling of blocktag parsing * clean up * add execution errors * add rpc errors * add more fields to errors --- Cargo.lock | 11 ++-- client/src/client.rs | 7 +-- client/src/node.rs | 20 ++++---- client/src/rpc.rs | 51 ++++++------------ common/Cargo.toml | 1 + common/src/errors.rs | 27 ++++++++++ common/src/lib.rs | 1 + common/src/types.rs | 51 ++++++++++++++++++ consensus/Cargo.toml | 1 + consensus/src/consensus.rs | 91 ++++++++++++++++++++++----------- consensus/src/errors.rs | 25 +++++++++ consensus/src/lib.rs | 1 + consensus/src/rpc/nimbus_rpc.rs | 40 ++++++++++++--- execution/Cargo.toml | 1 + execution/src/errors.rs | 16 ++++++ execution/src/execution.rs | 19 +++++-- execution/src/lib.rs | 1 + execution/src/rpc/http_rpc.rs | 33 ++++++++++-- 18 files changed, 295 insertions(+), 102 deletions(-) create mode 100644 common/src/errors.rs create mode 100644 consensus/src/errors.rs create mode 100644 execution/src/errors.rs diff --git a/Cargo.lock b/Cargo.lock index 4f288b8..f01c08c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -513,6 +513,7 @@ dependencies = [ "openssl", "serde", "ssz-rs", + "thiserror", "toml", ] @@ -551,6 +552,7 @@ dependencies = [ "serde", "serde_json", "ssz-rs", + "thiserror", "tokio", "toml", ] @@ -1040,6 +1042,7 @@ dependencies = [ "serde", "serde_json", "ssz-rs", + "thiserror", "tokio", "toml", "triehash-ethereum", @@ -3285,18 +3288,18 @@ checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" [[package]] name = "thiserror" -version = "1.0.34" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c1b05ca9d106ba7d2e31a9dab4a64e7be2cce415321966ea3132c49a656e252" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.34" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8f2591983642de85c921015f3f070c665a197ed69e417af436115e3a1407487" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" dependencies = [ "proc-macro2", "quote", diff --git a/client/src/client.rs b/client/src/client.rs index f905e9b..b242bf9 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -4,6 +4,7 @@ use ethers::prelude::{Address, U256}; use ethers::types::{Transaction, TransactionReceipt, H256}; use eyre::{eyre, Result}; +use common::types::BlockTag; use config::Config; use consensus::types::Header; use execution::types::{CallOpts, ExecutionBlock}; @@ -13,7 +14,7 @@ use tokio::sync::RwLock; use tokio::time::sleep; use crate::database::{Database, FileDB}; -use crate::node::{BlockTag, Node}; +use crate::node::Node; use crate::rpc::Rpc; pub struct Client { @@ -49,13 +50,13 @@ impl Client { spawn(async move { let res = node.write().await.sync().await; if let Err(err) = res { - warn!("{}", err); + warn!("consensus error: {}", err); } loop { let res = node.write().await.advance().await; if let Err(err) = res { - warn!("{}", err); + warn!("consensus error: {}", err); } let next_update = node.read().await.duration_until_next_update(); diff --git a/client/src/node.rs b/client/src/node.rs index a40b7c2..0004673 100644 --- a/client/src/node.rs +++ b/client/src/node.rs @@ -6,6 +6,8 @@ use ethers::prelude::{Address, U256}; use ethers::types::{Transaction, TransactionReceipt, H256}; use eyre::{eyre, Result}; +use common::errors::BlockNotFoundError; +use common::types::BlockTag; use config::Config; use consensus::rpc::nimbus_rpc::NimbusRpc; use consensus::types::{ExecutionPayload, Header}; @@ -147,7 +149,7 @@ impl Node { let value = account.slots.get(&slot); match value { Some(value) => Ok(*value), - None => Err(eyre!("Slot Not Found")), + None => Err(eyre!("slot not found")), } } @@ -230,15 +232,17 @@ impl Node { match block { BlockTag::Latest => { let payload = self.payloads.last_key_value(); - Ok(payload.ok_or(eyre!("Block Not Found"))?.1) + Ok(payload.ok_or(BlockNotFoundError::new(BlockTag::Latest))?.1) } BlockTag::Finalized => { let payload = self.finalized_payloads.last_key_value(); - Ok(payload.ok_or(eyre!("Block Not Found"))?.1) + Ok(payload + .ok_or(BlockNotFoundError::new(BlockTag::Finalized))? + .1) } BlockTag::Number(num) => { let payload = self.payloads.get(num); - payload.ok_or(eyre!("Block Not Found")) + payload.ok_or(BlockNotFoundError::new(BlockTag::Number(*num)).into()) } } } @@ -249,7 +253,7 @@ impl Node { let slot_delay = expected_slot - synced_slot; if slot_delay > 10 { - return Err(eyre!("Out of Sync")); + return Err(eyre!("out of sync")); } Ok(()) @@ -263,9 +267,3 @@ impl Node { } } } - -pub enum BlockTag { - Latest, - Finalized, - Number(u64), -} diff --git a/client/src/rpc.rs b/client/src/rpc.rs index deb5a35..7cd89ba 100644 --- a/client/src/rpc.rs +++ b/client/src/rpc.rs @@ -13,9 +13,12 @@ use jsonrpsee::{ proc_macros::rpc, }; -use crate::node::{BlockTag, Node}; +use crate::node::Node; -use common::utils::{hex_str_to_bytes, u64_to_hex_string}; +use common::{ + types::BlockTag, + utils::{hex_str_to_bytes, u64_to_hex_string}, +}; use execution::types::{CallOpts, ExecutionBlock}; pub struct Rpc { @@ -47,16 +50,16 @@ impl Rpc { } } -#[rpc(client, server, namespace = "eth")] +#[rpc(server, namespace = "eth")] trait EthRpc { #[method(name = "getBalance")] - async fn get_balance(&self, address: &str, block: &str) -> Result; + async fn get_balance(&self, address: &str, block: BlockTag) -> Result; #[method(name = "getTransactionCount")] - async fn get_transaction_count(&self, address: &str, block: &str) -> Result; + async fn get_transaction_count(&self, address: &str, block: BlockTag) -> Result; #[method(name = "getCode")] - async fn get_code(&self, address: &str, block: &str) -> Result; + async fn get_code(&self, address: &str, block: BlockTag) -> Result; #[method(name = "call")] - async fn call(&self, opts: CallOpts, block: &str) -> Result; + async fn call(&self, opts: CallOpts, block: BlockTag) -> Result; #[method(name = "estimateGas")] async fn estimate_gas(&self, opts: CallOpts) -> Result; #[method(name = "chainId")] @@ -70,7 +73,7 @@ trait EthRpc { #[method(name = "getBlockByNumber")] async fn get_block_by_number( &self, - num: &str, + block: BlockTag, full_tx: bool, ) -> Result, Error>; #[method(name = "getBlockByHash")] @@ -104,9 +107,8 @@ struct RpcInner { #[async_trait] impl EthRpcServer for RpcInner { - async fn get_balance(&self, address: &str, block: &str) -> Result { + async fn get_balance(&self, address: &str, block: BlockTag) -> Result { debug!("eth_getBalance"); - let block = convert_err(decode_block(block))?; let address = convert_err(Address::from_str(address))?; let node = self.node.read().await; let balance = convert_err(node.get_balance(&address, &block).await)?; @@ -114,8 +116,7 @@ impl EthRpcServer for RpcInner { Ok(balance.encode_hex()) } - async fn get_transaction_count(&self, address: &str, block: &str) -> Result { - let block = convert_err(decode_block(block))?; + async fn get_transaction_count(&self, address: &str, block: BlockTag) -> Result { let address = convert_err(Address::from_str(address))?; let node = self.node.read().await; let nonce = convert_err(node.get_nonce(&address, &block).await)?; @@ -123,8 +124,7 @@ impl EthRpcServer for RpcInner { Ok(nonce.encode_hex()) } - async fn get_code(&self, address: &str, block: &str) -> Result { - let block = convert_err(decode_block(block))?; + async fn get_code(&self, address: &str, block: BlockTag) -> Result { let address = convert_err(Address::from_str(address))?; let node = self.node.read().await; let code = convert_err(node.get_code(&address, &block).await)?; @@ -132,9 +132,8 @@ impl EthRpcServer for RpcInner { Ok(hex::encode(code)) } - async fn call(&self, opts: CallOpts, block: &str) -> Result { + async fn call(&self, opts: CallOpts, block: BlockTag) -> Result { debug!("eth_call"); - let block = convert_err(decode_block(block))?; let node = self.node.read().await; let res = convert_err(node.call(&opts, &block))?; @@ -175,10 +174,9 @@ impl EthRpcServer for RpcInner { async fn get_block_by_number( &self, - block: &str, + block: BlockTag, _full_tx: bool, ) -> Result, Error> { - let block = convert_err(decode_block(block))?; let node = self.node.read().await; let block = convert_err(node.get_block_by_number(&block))?; Ok(block) @@ -251,20 +249,3 @@ fn convert_err(res: Result) -> Result { Error::Custom(err.to_string()) }) } - -fn decode_block(block: &str) -> Result { - match block { - "latest" => Ok(BlockTag::Latest), - "finalized" => Ok(BlockTag::Finalized), - _ => { - if block.starts_with("0x") { - Ok(BlockTag::Number(u64::from_str_radix( - block.strip_prefix("0x").unwrap(), - 16, - )?)) - } else { - Ok(BlockTag::Number(block.parse()?)) - } - } - } -} diff --git a/common/Cargo.toml b/common/Cargo.toml index e56ebb8..71731ee 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -13,3 +13,4 @@ ssz-rs = { git = "https://github.com/ralexstokes/ssz-rs" } ethers = "0.17.0" toml = "0.5.9" openssl = { version = "0.10", features = ["vendored"] } +thiserror = "1.0.37" diff --git a/common/src/errors.rs b/common/src/errors.rs new file mode 100644 index 0000000..3f275ba --- /dev/null +++ b/common/src/errors.rs @@ -0,0 +1,27 @@ +use thiserror::Error; + +use crate::types::BlockTag; + +#[derive(Debug, Error)] +#[error("block not available: {block}")] +pub struct BlockNotFoundError { + block: BlockTag, +} + +impl BlockNotFoundError { + pub fn new(block: BlockTag) -> Self { + Self { block } + } +} + +#[derive(Debug, Error)] +#[error("rpc error: {message}")] +pub struct RpcError { + message: String, +} + +impl RpcError { + pub fn new(message: String) -> Self { + Self { message } + } +} diff --git a/common/src/lib.rs b/common/src/lib.rs index b4ab6a6..9040a8f 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,2 +1,3 @@ +pub mod errors; pub mod types; pub mod utils; diff --git a/common/src/types.rs b/common/src/types.rs index cf51f19..9e375ca 100644 --- a/common/src/types.rs +++ b/common/src/types.rs @@ -1,3 +1,54 @@ +use std::fmt::Display; + +use serde::{de::Error, Deserialize}; use ssz_rs::Vector; pub type Bytes32 = Vector; + +#[derive(Debug)] +pub enum BlockTag { + Latest, + Finalized, + Number(u64), +} + +impl Display for BlockTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let formatted = match self { + Self::Latest => "latest".to_string(), + Self::Finalized => "finalized".to_string(), + Self::Number(num) => num.to_string(), + }; + + write!(f, "{}", formatted) + } +} + +impl<'de> Deserialize<'de> for BlockTag { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let block: String = serde::Deserialize::deserialize(deserializer)?; + let parse_error = D::Error::custom("could not parse block tag"); + + let block_tag = match block.as_str() { + "latest" => BlockTag::Latest, + "finalized" => BlockTag::Finalized, + _ => match block.strip_prefix("0x") { + Some(hex_block) => { + let num = u64::from_str_radix(hex_block, 16).map_err(|_| parse_error)?; + + BlockTag::Number(num) + } + None => { + let num = block.parse().map_err(|_| parse_error)?; + + BlockTag::Number(num) + } + }, + }; + + Ok(block_tag) + } +} diff --git a/consensus/Cargo.toml b/consensus/Cargo.toml index 6125021..35346fc 100644 --- a/consensus/Cargo.toml +++ b/consensus/Cargo.toml @@ -24,6 +24,7 @@ async-trait = "0.1.57" log = "0.4.17" chrono = "0.4.22" openssl = { version = "0.10", features = ["vendored"] } +thiserror = "1.0.37" common = { path = "../common" } config = { path = "../config" } diff --git a/consensus/src/consensus.rs b/consensus/src/consensus.rs index 8605170..9ca3604 100644 --- a/consensus/src/consensus.rs +++ b/consensus/src/consensus.rs @@ -5,7 +5,7 @@ use std::time::UNIX_EPOCH; use blst::min_pk::{PublicKey, Signature}; use blst::BLST_ERROR; use chrono::Duration; -use eyre::{eyre, Result}; +use eyre::Result; use log::{debug, info}; use ssz_rs::prelude::*; @@ -13,6 +13,8 @@ use common::types::*; use common::utils::*; use config::Config; +use crate::errors::ConsensusError; + use super::rpc::Rpc; use super::types::*; @@ -49,12 +51,16 @@ impl ConsensusClient { &bootstrap.current_sync_committee_branch, ); - let header_hash = bootstrap.header.hash_tree_root()?; - let header_valid = - header_hash.to_string() == format!("0x{}", hex::encode(checkpoint_block_root)); + let header_hash = bootstrap.header.hash_tree_root()?.to_string(); + let expected_hash = format!("0x{}", hex::encode(checkpoint_block_root)); + let header_valid = header_hash == expected_hash; - if !(header_valid && committee_valid) { - return Err(eyre!("Invalid Bootstrap")); + if !header_valid { + return Err(ConsensusError::InvalidHeaderHash(expected_hash, header_hash).into()); + } + + if !committee_valid { + return Err(ConsensusError::InvalidCurrentSyncCommitteeProof.into()); } let store = Store { @@ -87,11 +93,15 @@ impl ConsensusClient { } else if slot == finalized_slot { self.store.finalized_header.clone().hash_tree_root()? } else { - return Err(eyre!("Block Not Found")); + return Err(ConsensusError::PayloadNotFound(slot).into()); }; if verified_block_hash != block_hash { - Err(eyre!("Block Root Mismatch")) + Err(ConsensusError::InvalidHeaderHash( + block_hash.to_string(), + verified_block_hash.to_string(), + ) + .into()) } else { Ok(block.body.execution_payload) } @@ -156,7 +166,7 @@ impl ConsensusClient { fn verify_generic_update(&self, update: &GenericUpdate) -> Result<()> { let bits = get_bits(&update.sync_aggregate.sync_committee_bits); if bits == 0 { - return Err(eyre!("Insufficient Participation")); + return Err(ConsensusError::InsufficientParticipation.into()); } let update_finalized_slot = update.finalized_header.clone().unwrap_or_default().slot; @@ -165,7 +175,7 @@ impl ConsensusClient { && update.attested_header.slot >= update_finalized_slot; if !valid_time { - return Err(eyre!("Invalid Timestamp")); + return Err(ConsensusError::InvalidTimestamp.into()); } let store_period = calc_sync_period(self.store.finalized_header.slot); @@ -177,7 +187,7 @@ impl ConsensusClient { }; if !valid_period { - return Err(eyre!("Invalid Period")); + return Err(ConsensusError::InvalidPeriod.into()); } let update_attested_period = calc_sync_period(update.attested_header.slot); @@ -188,7 +198,7 @@ impl ConsensusClient { if update.attested_header.slot <= self.store.finalized_header.slot && !update_has_next_committee { - return Err(eyre!("Update Not Relevent")); + return Err(ConsensusError::NotRelevant.into()); } if update.finalized_header.is_some() && update.finality_branch.is_some() { @@ -199,7 +209,7 @@ impl ConsensusClient { ); if !is_valid { - return Err(eyre!("Invalid Finality Proof")); + return Err(ConsensusError::InvalidFinalityProof.into()); } } @@ -211,7 +221,7 @@ impl ConsensusClient { ); if !is_valid { - return Err(eyre!("Invalid Next Sync Committee Proof")); + return Err(ConsensusError::InvalidNextSyncCommitteeProof.into()); } } @@ -232,7 +242,7 @@ impl ConsensusClient { let is_valid_sig = is_aggregate_valid(sig, signing_root.as_bytes(), &pks); if !is_valid_sig { - return Err(eyre!("Invalid Signature")); + return Err(ConsensusError::InvalidSignature.into()); } Ok(()) @@ -631,6 +641,7 @@ mod tests { use crate::{ consensus::calc_sync_period, + errors::ConsensusError, rpc::{mock_rpc::MockRpc, Rpc}, types::Header, ConsensusClient, @@ -649,7 +660,7 @@ mod tests { #[tokio::test] async fn test_verify_update() { - let mut client = get_client().await; + let client = get_client().await; let period = calc_sync_period(client.store.finalized_header.slot); let updates = client.rpc.get_updates(period).await.unwrap(); @@ -659,41 +670,50 @@ mod tests { #[tokio::test] async fn test_verify_update_invalid_committee() { - let mut client = get_client().await; + let client = get_client().await; let period = calc_sync_period(client.store.finalized_header.slot); let updates = client.rpc.get_updates(period).await.unwrap(); let mut update = updates[0].clone(); update.next_sync_committee.pubkeys[0] = Vector::default(); - let res = client.verify_update(&mut update); - assert!(res.is_err()); + let err = client.verify_update(&mut update).err().unwrap(); + assert_eq!( + err.to_string(), + ConsensusError::InvalidNextSyncCommitteeProof.to_string() + ); } #[tokio::test] async fn test_verify_upadate_invlaid_finality() { - let mut client = get_client().await; + let client = get_client().await; let period = calc_sync_period(client.store.finalized_header.slot); let updates = client.rpc.get_updates(period).await.unwrap(); let mut update = updates[0].clone(); update.finalized_header = Header::default(); - let res = client.verify_update(&mut update); - assert!(res.is_err()); + let err = client.verify_update(&mut update).err().unwrap(); + assert_eq!( + err.to_string(), + ConsensusError::InvalidFinalityProof.to_string() + ); } #[tokio::test] async fn test_verify_update_invalid_sig() { - let mut client = get_client().await; + let client = get_client().await; let period = calc_sync_period(client.store.finalized_header.slot); let updates = client.rpc.get_updates(period).await.unwrap(); let mut update = updates[0].clone(); update.sync_aggregate.sync_committee_signature = Vector::default(); - let res = client.verify_update(&mut update); - assert!(res.is_err()); + let err = client.verify_update(&mut update).err().unwrap(); + assert_eq!( + err.to_string(), + ConsensusError::InvalidSignature.to_string() + ); } #[tokio::test] @@ -714,8 +734,11 @@ mod tests { let mut update = client.rpc.get_finality_update().await.unwrap(); update.finalized_header = Header::default(); - let res = client.verify_finality_update(&update); - assert!(res.is_err()); + let err = client.verify_finality_update(&update).err().unwrap(); + assert_eq!( + err.to_string(), + ConsensusError::InvalidFinalityProof.to_string() + ); } #[tokio::test] @@ -726,8 +749,11 @@ mod tests { let mut update = client.rpc.get_finality_update().await.unwrap(); update.sync_aggregate.sync_committee_signature = Vector::default(); - let res = client.verify_finality_update(&update); - assert!(res.is_err()); + let err = client.verify_finality_update(&update).err().unwrap(); + assert_eq!( + err.to_string(), + ConsensusError::InvalidSignature.to_string() + ); } #[tokio::test] @@ -747,7 +773,10 @@ mod tests { let mut update = client.rpc.get_optimistic_update().await.unwrap(); update.sync_aggregate.sync_committee_signature = Vector::default(); - let res = client.verify_optimistic_update(&update); - assert!(res.is_err()); + let err = client.verify_optimistic_update(&update).err().unwrap(); + assert_eq!( + err.to_string(), + ConsensusError::InvalidSignature.to_string() + ); } } diff --git a/consensus/src/errors.rs b/consensus/src/errors.rs new file mode 100644 index 0000000..87bb923 --- /dev/null +++ b/consensus/src/errors.rs @@ -0,0 +1,25 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum ConsensusError { + #[error("insufficient participation")] + InsufficientParticipation, + #[error("invalid timestamp")] + InvalidTimestamp, + #[error("invalid sync committee period")] + InvalidPeriod, + #[error("update not relevant")] + NotRelevant, + #[error("invalid finality proof")] + InvalidFinalityProof, + #[error("invalid next sync committee proof")] + InvalidNextSyncCommitteeProof, + #[error("invalid current sync committee proof")] + InvalidCurrentSyncCommitteeProof, + #[error("invalid sync committee signature")] + InvalidSignature, + #[error("invalid header hash found: {0}, expected: {1}")] + InvalidHeaderHash(String, String), + #[error("payload not found for slot: {0}")] + PayloadNotFound(u64), +} diff --git a/consensus/src/lib.rs b/consensus/src/lib.rs index d1e6d4b..2dbec4f 100644 --- a/consensus/src/lib.rs +++ b/consensus/src/lib.rs @@ -1,3 +1,4 @@ +pub mod errors; pub mod rpc; pub mod types; diff --git a/consensus/src/rpc/nimbus_rpc.rs b/consensus/src/rpc/nimbus_rpc.rs index 134821b..4594f5e 100644 --- a/consensus/src/rpc/nimbus_rpc.rs +++ b/consensus/src/rpc/nimbus_rpc.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use common::errors::RpcError; use eyre::Result; use super::Rpc; @@ -22,7 +23,14 @@ impl Rpc for NimbusRpc { "{}/eth/v0/beacon/light_client/bootstrap/0x{}", self.rpc, root_hex ); - let res = reqwest::get(req).await?.json::().await?; + + let res = reqwest::get(req) + .await + .map_err(|e| RpcError::new(e.to_string()))? + .json::() + .await + .map_err(|e| RpcError::new(e.to_string()))?; + Ok(res.data.v) } @@ -31,34 +39,50 @@ impl Rpc for NimbusRpc { "{}/eth/v0/beacon/light_client/updates?start_period={}&count=1000", self.rpc, period ); - let res = reqwest::get(req).await?.json::().await?; + + let res = reqwest::get(req) + .await + .map_err(|e| RpcError::new(e.to_string()))? + .json::() + .await + .map_err(|e| RpcError::new(e.to_string()))?; + Ok(res.data) } async fn get_finality_update(&self) -> Result { let req = format!("{}/eth/v0/beacon/light_client/finality_update", self.rpc); let res = reqwest::get(req) - .await? + .await + .map_err(|e| RpcError::new(e.to_string()))? .json::() - .await?; + .await + .map_err(|e| RpcError::new(e.to_string()))?; + Ok(res.data) } async fn get_optimistic_update(&self) -> Result { let req = format!("{}/eth/v0/beacon/light_client/optimistic_update", self.rpc); let res = reqwest::get(req) - .await? + .await + .map_err(|e| RpcError::new(e.to_string()))? .json::() - .await?; + .await + .map_err(|e| RpcError::new(e.to_string()))?; + Ok(res.data) } async fn get_block(&self, slot: u64) -> Result { let req = format!("{}/eth/v2/beacon/blocks/{}", self.rpc, slot); let res = reqwest::get(req) - .await? + .await + .map_err(|e| RpcError::new(e.to_string()))? .json::() - .await?; + .await + .map_err(|e| RpcError::new(e.to_string()))?; + Ok(res.data.message) } } diff --git a/execution/Cargo.toml b/execution/Cargo.toml index 3badcb4..b6d2ca4 100644 --- a/execution/Cargo.toml +++ b/execution/Cargo.toml @@ -24,6 +24,7 @@ triehash-ethereum = { git = "https://github.com/openethereum/parity-ethereum" } async-trait = "0.1.57" openssl = { version = "0.10", features = ["vendored"] } log = "0.4.17" +thiserror = "1.0.37" common = { path = "../common" } consensus = { path = "../consensus" } diff --git a/execution/src/errors.rs b/execution/src/errors.rs new file mode 100644 index 0000000..3fd7c33 --- /dev/null +++ b/execution/src/errors.rs @@ -0,0 +1,16 @@ +use ethers::types::{Address, H256}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum ExecutionError { + #[error("invalid account proof for address: {0}")] + InvalidAccountProof(Address), + #[error("invalid storage proof for address: {0}, slot: {1}")] + InvalidStorageProof(Address, H256), + #[error("code hash mismatch for address: {0}, found: {1}, expected: {2}")] + CodeHashMismatch(Address, String, String), + #[error("receipt root mismatch for tx: {0}")] + ReceiptRootMismatch(String), + #[error("missing transaction for tx: {0}")] + MissingTransaction(String), +} diff --git a/execution/src/execution.rs b/execution/src/execution.rs index 478d3dd..21f06d5 100644 --- a/execution/src/execution.rs +++ b/execution/src/execution.rs @@ -14,6 +14,8 @@ use futures::future::join_all; use revm::KECCAK_EMPTY; use triehash_ethereum::ordered_trie_root; +use crate::errors::ExecutionError; + use super::proof::{encode_account, verify_proof}; use super::rpc::Rpc; use super::types::{Account, ExecutionBlock}; @@ -53,7 +55,7 @@ impl ExecutionClient { ); if !is_valid { - eyre::bail!("Invalid Proof"); + return Err(ExecutionError::InvalidAccountProof(*address).into()); } let mut slot_map = HashMap::new(); @@ -72,7 +74,9 @@ impl ExecutionClient { ); if !is_valid { - eyre::bail!("Invalid Proof"); + return Err( + ExecutionError::InvalidStorageProof(*address, storage_proof.key).into(), + ); } slot_map.insert(storage_proof.key, storage_proof.value); @@ -85,7 +89,12 @@ impl ExecutionClient { let code_hash = keccak256(&code).into(); if proof.code_hash != code_hash { - eyre::bail!("Invalid Proof"); + return Err(ExecutionError::CodeHashMismatch( + *address, + code_hash.to_string(), + proof.code_hash.to_string(), + ) + .into()); } code @@ -183,7 +192,7 @@ impl ExecutionClient { let payload_receipt_root = H256::from_slice(&payload.receipts_root); if expected_receipt_root != payload_receipt_root || !receipts.contains(&receipt) { - return Err(eyre::eyre!("Receipt Proof Invalid")); + return Err(ExecutionError::ReceiptRootMismatch(tx_hash.to_string()).into()); } Ok(Some(receipt)) @@ -222,7 +231,7 @@ impl ExecutionClient { .collect::>(); if !txs_encoded.contains(&tx_encoded) { - return Err(eyre::eyre!("Transaction Proof Invalid")); + return Err(ExecutionError::MissingTransaction(hash.to_string()).into()); } Ok(Some(tx)) diff --git a/execution/src/lib.rs b/execution/src/lib.rs index 4bcf871..8099dc1 100644 --- a/execution/src/lib.rs +++ b/execution/src/lib.rs @@ -1,3 +1,4 @@ +pub mod errors; pub mod evm; pub mod rpc; pub mod types; diff --git a/execution/src/rpc/http_rpc.rs b/execution/src/rpc/http_rpc.rs index 57091dd..a64c647 100644 --- a/execution/src/rpc/http_rpc.rs +++ b/execution/src/rpc/http_rpc.rs @@ -1,6 +1,7 @@ use std::str::FromStr; use async_trait::async_trait; +use common::errors::RpcError; use ethers::prelude::{Address, Http}; use ethers::providers::{HttpRateLimitRetryPolicy, Middleware, Provider, RetryClient}; use ethers::types::transaction::eip2718::TypedTransaction; @@ -51,7 +52,9 @@ impl Rpc for HttpRpc { let proof_response = self .provider .get_proof(*address, slots.to_vec(), block) - .await?; + .await + .map_err(|e| RpcError::new(e.to_string()))?; + Ok(proof_response) } @@ -71,24 +74,44 @@ impl Rpc for HttpRpc { .map(|data| Bytes::from(data.as_slice().to_owned())); let tx = TypedTransaction::Eip1559(raw_tx); - let list = self.provider.create_access_list(&tx, block).await?; + let list = self + .provider + .create_access_list(&tx, block) + .await + .map_err(|e| RpcError::new(e.to_string()))?; Ok(list.access_list) } async fn get_code(&self, address: &Address, block: u64) -> Result> { let block = Some(BlockId::from(block)); - let code = self.provider.get_code(*address, block).await?; + let code = self + .provider + .get_code(*address, block) + .await + .map_err(|e| RpcError::new(e.to_string()))?; + Ok(code.to_vec()) } async fn send_raw_transaction(&self, bytes: &Vec) -> Result { let bytes = Bytes::from(bytes.as_slice().to_owned()); - Ok(self.provider.send_raw_transaction(bytes).await?.tx_hash()) + let tx = self + .provider + .send_raw_transaction(bytes) + .await + .map_err(|e| RpcError::new(e.to_string()))?; + + Ok(tx.tx_hash()) } async fn get_transaction_receipt(&self, tx_hash: &H256) -> Result> { - let receipt = self.provider.get_transaction_receipt(*tx_hash).await?; + let receipt = self + .provider + .get_transaction_receipt(*tx_hash) + .await + .map_err(|e| RpcError::new(e.to_string()))?; + Ok(receipt) }